michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 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 DOM Core's nsIDOMComment, nsIDOMDocumentType, nsIDOMText, michael@0: * nsIDOMCDATASection, and nsIDOMProcessingInstruction nodes. michael@0: */ michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: michael@0: #include "nsGenericDOMDataNode.h" michael@0: #include "mozilla/AsyncEventDispatcher.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/ShadowRoot.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "mozilla/InternalMutationEvent.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIDOMEvent.h" michael@0: #include "nsIDOMText.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsDOMString.h" michael@0: #include "nsIDOMUserDataHandler.h" michael@0: #include "nsChangeHint.h" michael@0: #include "nsCOMArray.h" michael@0: #include "nsNodeUtils.h" michael@0: #include "mozilla/dom/DirectionalityUtils.h" michael@0: #include "nsBindingManager.h" michael@0: #include "nsCCUncollectableMarker.h" michael@0: #include "mozAutoDocUpdate.h" michael@0: michael@0: #include "pldhash.h" michael@0: #include "prprf.h" michael@0: #include "nsWrapperCacheInlines.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: nsGenericDOMDataNode::nsGenericDOMDataNode(already_AddRefed& aNodeInfo) michael@0: : nsIContent(aNodeInfo) michael@0: { michael@0: NS_ABORT_IF_FALSE(mNodeInfo->NodeType() == nsIDOMNode::TEXT_NODE || michael@0: mNodeInfo->NodeType() == nsIDOMNode::CDATA_SECTION_NODE || michael@0: mNodeInfo->NodeType() == nsIDOMNode::COMMENT_NODE || michael@0: mNodeInfo->NodeType() == michael@0: nsIDOMNode::PROCESSING_INSTRUCTION_NODE || michael@0: mNodeInfo->NodeType() == nsIDOMNode::DOCUMENT_TYPE_NODE, michael@0: "Bad NodeType in aNodeInfo"); michael@0: } michael@0: michael@0: nsGenericDOMDataNode::nsGenericDOMDataNode(already_AddRefed&& aNodeInfo) michael@0: : nsIContent(aNodeInfo) michael@0: { michael@0: NS_ABORT_IF_FALSE(mNodeInfo->NodeType() == nsIDOMNode::TEXT_NODE || michael@0: mNodeInfo->NodeType() == nsIDOMNode::CDATA_SECTION_NODE || michael@0: mNodeInfo->NodeType() == nsIDOMNode::COMMENT_NODE || michael@0: mNodeInfo->NodeType() == michael@0: nsIDOMNode::PROCESSING_INSTRUCTION_NODE || michael@0: mNodeInfo->NodeType() == nsIDOMNode::DOCUMENT_TYPE_NODE, michael@0: "Bad NodeType in aNodeInfo"); michael@0: } michael@0: michael@0: nsGenericDOMDataNode::~nsGenericDOMDataNode() 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: NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericDOMDataNode) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsGenericDOMDataNode) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGenericDOMDataNode) michael@0: return Element::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(nsGenericDOMDataNode) michael@0: return Element::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(nsGenericDOMDataNode) michael@0: return Element::CanSkipThis(tmp); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsGenericDOMDataNode) 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: nsDataSlots *slots = tmp->GetExistingDataSlots(); michael@0: if (slots) { michael@0: slots->Traverse(cb); michael@0: } michael@0: michael@0: tmp->OwnerDoc()->BindingManager()->Traverse(tmp, cb); michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGenericDOMDataNode) michael@0: nsINode::Unlink(tmp); michael@0: michael@0: nsDataSlots *slots = tmp->GetExistingDataSlots(); michael@0: if (slots) { michael@0: slots->Unlink(); michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsGenericDOMDataNode) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsGenericDOMDataNode) 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(nsGenericDOMDataNode) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsGenericDOMDataNode, michael@0: nsNodeUtils::LastRelease(this)) michael@0: michael@0: michael@0: void michael@0: nsGenericDOMDataNode::GetNodeValueInternal(nsAString& aNodeValue) michael@0: { michael@0: DebugOnly rv = GetData(aNodeValue); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "GetData() failed!"); michael@0: } michael@0: michael@0: void michael@0: nsGenericDOMDataNode::SetNodeValueInternal(const nsAString& aNodeValue, michael@0: ErrorResult& aError) michael@0: { michael@0: aError = SetTextInternal(0, mText.GetLength(), aNodeValue.BeginReading(), michael@0: aNodeValue.Length(), true); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: // Implementation of nsIDOMCharacterData michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::GetData(nsAString& aData) const michael@0: { michael@0: if (mText.Is2b()) { michael@0: aData.Assign(mText.Get2b(), mText.GetLength()); michael@0: } else { michael@0: // Must use Substring() since nsDependentCString() requires null michael@0: // terminated strings. michael@0: michael@0: const char *data = mText.Get1b(); michael@0: michael@0: if (data) { michael@0: CopyASCIItoUTF16(Substring(data, data + mText.GetLength()), aData); michael@0: } else { michael@0: aData.Truncate(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::SetData(const nsAString& aData) michael@0: { michael@0: return SetTextInternal(0, mText.GetLength(), aData.BeginReading(), michael@0: aData.Length(), true); michael@0: } michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::GetLength(uint32_t* aLength) michael@0: { michael@0: *aLength = mText.GetLength(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::SubstringData(uint32_t aStart, uint32_t aCount, michael@0: nsAString& aReturn) michael@0: { michael@0: ErrorResult rv; michael@0: SubstringData(aStart, aCount, aReturn, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: void michael@0: nsGenericDOMDataNode::SubstringData(uint32_t aStart, uint32_t aCount, michael@0: nsAString& aReturn, ErrorResult& rv) michael@0: { michael@0: aReturn.Truncate(); michael@0: michael@0: uint32_t textLength = mText.GetLength(); michael@0: if (aStart > textLength) { michael@0: rv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return; michael@0: } michael@0: michael@0: uint32_t amount = aCount; michael@0: if (amount > textLength - aStart) { michael@0: amount = textLength - aStart; michael@0: } michael@0: michael@0: if (mText.Is2b()) { michael@0: aReturn.Assign(mText.Get2b() + aStart, amount); michael@0: } else { michael@0: // Must use Substring() since nsDependentCString() requires null michael@0: // terminated strings. michael@0: michael@0: const char *data = mText.Get1b() + aStart; michael@0: CopyASCIItoUTF16(Substring(data, data + amount), aReturn); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsGenericDOMDataNode::MozRemove() michael@0: { michael@0: Remove(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::AppendData(const nsAString& aData) michael@0: { michael@0: return SetTextInternal(mText.GetLength(), 0, aData.BeginReading(), michael@0: aData.Length(), true); michael@0: } michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::InsertData(uint32_t aOffset, michael@0: const nsAString& aData) michael@0: { michael@0: return SetTextInternal(aOffset, 0, aData.BeginReading(), michael@0: aData.Length(), true); michael@0: } michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::DeleteData(uint32_t aOffset, uint32_t aCount) michael@0: { michael@0: return SetTextInternal(aOffset, aCount, nullptr, 0, true); michael@0: } michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::ReplaceData(uint32_t aOffset, uint32_t aCount, michael@0: const nsAString& aData) michael@0: { michael@0: return SetTextInternal(aOffset, aCount, aData.BeginReading(), michael@0: aData.Length(), true); michael@0: } michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::SetTextInternal(uint32_t aOffset, uint32_t aCount, michael@0: const char16_t* aBuffer, michael@0: uint32_t aLength, bool aNotify, michael@0: CharacterDataChangeInfo::Details* aDetails) michael@0: { michael@0: NS_PRECONDITION(aBuffer || !aLength, michael@0: "Null buffer passed to SetTextInternal!"); michael@0: michael@0: // sanitize arguments michael@0: uint32_t textLength = mText.GetLength(); michael@0: if (aOffset > textLength) { michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: } michael@0: michael@0: if (aCount > textLength - aOffset) { michael@0: aCount = textLength - aOffset; michael@0: } michael@0: michael@0: uint32_t endOffset = aOffset + aCount; michael@0: michael@0: // Make sure the text fragment can hold the new data. michael@0: if (aLength > aCount && !mText.CanGrowBy(aLength - aCount)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: nsIDocument *document = GetCurrentDoc(); michael@0: mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify); michael@0: michael@0: bool haveMutationListeners = aNotify && michael@0: nsContentUtils::HasMutationListeners(this, michael@0: NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED, michael@0: this); michael@0: michael@0: nsCOMPtr oldValue; michael@0: if (haveMutationListeners) { michael@0: oldValue = GetCurrentValueAtom(); michael@0: } michael@0: michael@0: if (aNotify) { michael@0: CharacterDataChangeInfo info = { michael@0: aOffset == textLength, michael@0: aOffset, michael@0: endOffset, michael@0: aLength, michael@0: aDetails michael@0: }; michael@0: nsNodeUtils::CharacterDataWillChange(this, &info); michael@0: } michael@0: michael@0: Directionality oldDir = eDir_NotSet; michael@0: bool dirAffectsAncestor = (NodeType() == nsIDOMNode::TEXT_NODE && michael@0: TextNodeWillChangeDirection(this, &oldDir, aOffset)); michael@0: michael@0: if (aOffset == 0 && endOffset == textLength) { michael@0: // Replacing whole text or old text was empty. Don't bother to check for michael@0: // bidi in this string if the document already has bidi enabled. michael@0: bool ok = mText.SetTo(aBuffer, aLength, !document || !document->GetBidiEnabled()); michael@0: NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); michael@0: } michael@0: else if (aOffset == textLength) { michael@0: // Appending to existing michael@0: bool ok = mText.Append(aBuffer, aLength, !document || !document->GetBidiEnabled()); michael@0: NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); michael@0: } michael@0: else { michael@0: // Merging old and new michael@0: michael@0: // Allocate new buffer michael@0: int32_t newLength = textLength - aCount + aLength; michael@0: char16_t* to = new char16_t[newLength]; michael@0: NS_ENSURE_TRUE(to, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: // Copy over appropriate data michael@0: if (aOffset) { michael@0: mText.CopyTo(to, 0, aOffset); michael@0: } michael@0: if (aLength) { michael@0: memcpy(to + aOffset, aBuffer, aLength * sizeof(char16_t)); michael@0: } michael@0: if (endOffset != textLength) { michael@0: mText.CopyTo(to + aOffset + aLength, endOffset, textLength - endOffset); michael@0: } michael@0: michael@0: bool ok = mText.SetTo(to, newLength, !document || !document->GetBidiEnabled()); michael@0: michael@0: delete [] to; michael@0: michael@0: NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); michael@0: } michael@0: michael@0: UnsetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE); michael@0: michael@0: if (document && mText.IsBidi()) { michael@0: // If we found bidi characters in mText.SetTo() above, indicate that the michael@0: // document contains bidi characters. michael@0: document->SetBidiEnabled(); michael@0: } michael@0: michael@0: if (dirAffectsAncestor) { michael@0: TextNodeChangedDirection(this, oldDir, aNotify); michael@0: } michael@0: michael@0: // Notify observers michael@0: if (aNotify) { michael@0: CharacterDataChangeInfo info = { michael@0: aOffset == textLength, michael@0: aOffset, michael@0: endOffset, michael@0: aLength, michael@0: aDetails michael@0: }; michael@0: nsNodeUtils::CharacterDataChanged(this, &info); michael@0: michael@0: if (haveMutationListeners) { michael@0: InternalMutationEvent mutation(true, NS_MUTATION_CHARACTERDATAMODIFIED); michael@0: michael@0: mutation.mPrevAttrValue = oldValue; michael@0: if (aLength > 0) { michael@0: nsAutoString val; michael@0: mText.AppendTo(val); michael@0: mutation.mNewAttrValue = do_GetAtom(val); michael@0: } michael@0: michael@0: mozAutoSubtreeModified subtree(OwnerDoc(), this); michael@0: (new AsyncEventDispatcher(this, mutation))->RunDOMEventWhenSafe(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: // Implementation of nsIContent michael@0: michael@0: #ifdef DEBUG michael@0: void michael@0: nsGenericDOMDataNode::ToCString(nsAString& aBuf, int32_t aOffset, michael@0: int32_t aLen) const michael@0: { michael@0: if (mText.Is2b()) { michael@0: const char16_t* cp = mText.Get2b() + aOffset; michael@0: const char16_t* end = cp + aLen; michael@0: michael@0: while (cp < end) { michael@0: char16_t ch = *cp++; michael@0: if (ch == '&') { michael@0: aBuf.AppendLiteral("&"); michael@0: } else if (ch == '<') { michael@0: aBuf.AppendLiteral("<"); michael@0: } else if (ch == '>') { michael@0: aBuf.AppendLiteral(">"); michael@0: } else if ((ch < ' ') || (ch >= 127)) { michael@0: char buf[10]; michael@0: PR_snprintf(buf, sizeof(buf), "\\u%04x", ch); michael@0: AppendASCIItoUTF16(buf, aBuf); michael@0: } else { michael@0: aBuf.Append(ch); michael@0: } michael@0: } michael@0: } else { michael@0: unsigned char* cp = (unsigned char*)mText.Get1b() + aOffset; michael@0: const unsigned char* end = cp + aLen; michael@0: michael@0: while (cp < end) { michael@0: char16_t ch = *cp++; michael@0: if (ch == '&') { michael@0: aBuf.AppendLiteral("&"); michael@0: } else if (ch == '<') { michael@0: aBuf.AppendLiteral("<"); michael@0: } else if (ch == '>') { michael@0: aBuf.AppendLiteral(">"); michael@0: } else if ((ch < ' ') || (ch >= 127)) { michael@0: char buf[10]; michael@0: PR_snprintf(buf, sizeof(buf), "\\u%04x", ch); michael@0: AppendASCIItoUTF16(buf, aBuf); michael@0: } else { michael@0: aBuf.Append(ch); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent, michael@0: nsIContent* aBindingParent, michael@0: bool aCompileEventHandlers) michael@0: { michael@0: NS_PRECONDITION(aParent || aDocument, "Must have document if no parent!"); michael@0: NS_PRECONDITION(NODE_FROM(aParent, aDocument)->OwnerDoc() == OwnerDoc(), michael@0: "Must have the same owner document"); michael@0: NS_PRECONDITION(!aParent || aDocument == aParent->GetCurrentDoc(), michael@0: "aDocument must be current doc of aParent"); michael@0: NS_PRECONDITION(!GetCurrentDoc() && !IsInDoc(), michael@0: "Already have a document. Unbind first!"); michael@0: // Note that as we recurse into the kids, they'll have a non-null parent. So michael@0: // only assert if our parent is _changing_ while we have a parent. michael@0: NS_PRECONDITION(!GetParent() || aParent == GetParent(), michael@0: "Already have a parent. Unbind first!"); michael@0: NS_PRECONDITION(!GetBindingParent() || michael@0: aBindingParent == GetBindingParent() || michael@0: (!aBindingParent && aParent && michael@0: aParent->GetBindingParent() == GetBindingParent()), michael@0: "Already have a binding parent. Unbind first!"); michael@0: NS_PRECONDITION(aBindingParent != this, michael@0: "Content must not be its own binding parent"); michael@0: NS_PRECONDITION(!IsRootOfNativeAnonymousSubtree() || michael@0: aBindingParent == aParent, michael@0: "Native anonymous content must have its parent as its " michael@0: "own binding parent"); michael@0: michael@0: if (!aBindingParent && aParent) { michael@0: aBindingParent = aParent->GetBindingParent(); michael@0: } michael@0: michael@0: // First set the binding parent michael@0: if (aBindingParent) { michael@0: NS_ASSERTION(IsRootOfNativeAnonymousSubtree() || michael@0: !HasFlag(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE) || michael@0: (aParent && aParent->IsInNativeAnonymousSubtree()), michael@0: "Trying to re-bind content from native anonymous subtree to " michael@0: "non-native anonymous parent!"); michael@0: DataSlots()->mBindingParent = aBindingParent; // Weak, so no addref happens. michael@0: if (aParent->IsInNativeAnonymousSubtree()) { michael@0: SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE); michael@0: } michael@0: if (aParent->HasFlag(NODE_CHROME_ONLY_ACCESS)) { michael@0: SetFlags(NODE_CHROME_ONLY_ACCESS); michael@0: } michael@0: if (aParent->HasFlag(NODE_IS_IN_SHADOW_TREE)) { michael@0: SetFlags(NODE_IS_IN_SHADOW_TREE); michael@0: } michael@0: ShadowRoot* parentContainingShadow = aParent->GetContainingShadow(); michael@0: if (parentContainingShadow) { michael@0: DataSlots()->mContainingShadow = parentContainingShadow; michael@0: } michael@0: } michael@0: michael@0: // Set parent michael@0: if (aParent) { michael@0: if (!GetParent()) { michael@0: NS_ADDREF(aParent); michael@0: } michael@0: mParent = aParent; michael@0: } michael@0: else { michael@0: mParent = aDocument; michael@0: } michael@0: SetParentIsContent(aParent); michael@0: michael@0: // XXXbz sXBL/XBL2 issue! michael@0: michael@0: // Set document michael@0: if (aDocument) { michael@0: // We no longer need to track the subtree pointer (and in fact we'll assert michael@0: // if we do this any later). michael@0: ClearSubtreeRootPointer(); michael@0: michael@0: // XXX See the comment in Element::BindToTree michael@0: SetInDocument(); michael@0: if (mText.IsBidi()) { michael@0: aDocument->SetBidiEnabled(); michael@0: } michael@0: // Clear the lazy frame construction bits. michael@0: UnsetFlags(NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES); michael@0: } else { michael@0: // If we're not in the doc, update our subtree pointer. michael@0: SetSubtreeRootPointer(aParent->SubtreeRoot()); michael@0: } michael@0: michael@0: nsNodeUtils::ParentChainChanged(this); michael@0: michael@0: UpdateEditableState(false); michael@0: michael@0: NS_POSTCONDITION(aDocument == GetCurrentDoc(), "Bound to wrong document"); michael@0: NS_POSTCONDITION(aParent == GetParent(), "Bound to wrong parent"); michael@0: NS_POSTCONDITION(aBindingParent == GetBindingParent(), michael@0: "Bound to wrong binding parent"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsGenericDOMDataNode::UnbindFromTree(bool aDeep, bool aNullParent) michael@0: { michael@0: // Unset frame flags; if we need them again later, they'll get set again. michael@0: UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE | michael@0: NS_REFRAME_IF_WHITESPACE | michael@0: // Also unset the shadow tree flag because it can michael@0: // no longer be a descendant of a ShadowRoot. michael@0: NODE_IS_IN_SHADOW_TREE); michael@0: michael@0: nsIDocument *document = GetCurrentDoc(); michael@0: if (document) { michael@0: // Notify XBL- & nsIAnonymousContentCreator-generated michael@0: // anonymous content that the document is changing. michael@0: // This is needed to update the insertion point. michael@0: document->BindingManager()->RemovedFromDocument(this, document); michael@0: } michael@0: michael@0: if (aNullParent) { michael@0: if (GetParent()) { michael@0: NS_RELEASE(mParent); michael@0: } else { michael@0: mParent = nullptr; michael@0: } michael@0: SetParentIsContent(false); michael@0: } michael@0: ClearInDocument(); michael@0: michael@0: // Begin keeping track of our subtree root. michael@0: SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot()); michael@0: michael@0: nsDataSlots *slots = GetExistingDataSlots(); michael@0: if (slots) { michael@0: slots->mBindingParent = nullptr; michael@0: slots->mContainingShadow = nullptr; michael@0: } michael@0: michael@0: nsNodeUtils::ParentChainChanged(this); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsGenericDOMDataNode::GetChildren(uint32_t aFilter) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIAtom * michael@0: nsGenericDOMDataNode::GetIDAttributeName() const michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::SetAttr(int32_t aNameSpaceID, nsIAtom* aAttr, michael@0: nsIAtom* aPrefix, const nsAString& aValue, michael@0: bool aNotify) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttr, michael@0: bool aNotify) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: const nsAttrName* michael@0: nsGenericDOMDataNode::GetAttrNameAt(uint32_t aIndex) const michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: uint32_t michael@0: nsGenericDOMDataNode::GetAttrCount() const michael@0: { michael@0: return 0; michael@0: } michael@0: michael@0: uint32_t michael@0: nsGenericDOMDataNode::GetChildCount() const michael@0: { michael@0: return 0; michael@0: } michael@0: michael@0: nsIContent * michael@0: nsGenericDOMDataNode::GetChildAt(uint32_t aIndex) const michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIContent * const * michael@0: nsGenericDOMDataNode::GetChildArray(uint32_t* aChildCount) const michael@0: { michael@0: *aChildCount = 0; michael@0: return nullptr; michael@0: } michael@0: michael@0: int32_t michael@0: nsGenericDOMDataNode::IndexOf(const nsINode* aPossibleChild) const michael@0: { michael@0: return -1; michael@0: } michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::InsertChildAt(nsIContent* aKid, uint32_t aIndex, michael@0: bool aNotify) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsGenericDOMDataNode::RemoveChildAt(uint32_t aIndex, bool aNotify) michael@0: { michael@0: } michael@0: michael@0: nsIContent * michael@0: nsGenericDOMDataNode::GetBindingParent() const michael@0: { michael@0: nsDataSlots *slots = GetExistingDataSlots(); michael@0: return slots ? slots->mBindingParent : nullptr; michael@0: } michael@0: michael@0: ShadowRoot * michael@0: nsGenericDOMDataNode::GetShadowRoot() const michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: ShadowRoot * michael@0: nsGenericDOMDataNode::GetContainingShadow() const michael@0: { michael@0: nsDataSlots *slots = GetExistingDataSlots(); michael@0: return slots ? slots->mContainingShadow : nullptr; michael@0: } michael@0: michael@0: void michael@0: nsGenericDOMDataNode::SetShadowRoot(ShadowRoot* aShadowRoot) michael@0: { michael@0: } michael@0: michael@0: nsXBLBinding * michael@0: nsGenericDOMDataNode::GetXBLBinding() const michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsGenericDOMDataNode::SetXBLBinding(nsXBLBinding* aBinding, michael@0: nsBindingManager* aOldBindingManager) michael@0: { michael@0: } michael@0: michael@0: nsIContent * michael@0: nsGenericDOMDataNode::GetXBLInsertionParent() const michael@0: { michael@0: if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) { michael@0: nsDataSlots *slots = GetExistingDataSlots(); 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: void michael@0: nsGenericDOMDataNode::SetXBLInsertionParent(nsIContent* aContent) michael@0: { michael@0: nsDataSlots *slots = DataSlots(); 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: nsGenericDOMDataNode::GetCustomElementData() const michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsGenericDOMDataNode::SetCustomElementData(CustomElementData* aData) michael@0: { michael@0: } michael@0: michael@0: bool michael@0: nsGenericDOMDataNode::IsNodeOfType(uint32_t aFlags) const michael@0: { michael@0: return !(aFlags & ~(eCONTENT | eDATA_NODE)); michael@0: } michael@0: michael@0: void michael@0: nsGenericDOMDataNode::SaveSubtreeState() michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsGenericDOMDataNode::DestroyContent() 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: michael@0: #ifdef DEBUG michael@0: void michael@0: nsGenericDOMDataNode::List(FILE* out, int32_t aIndent) const michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsGenericDOMDataNode::DumpContent(FILE* out, int32_t aIndent, michael@0: bool aDumpAll) const michael@0: { michael@0: } michael@0: #endif michael@0: michael@0: bool michael@0: nsGenericDOMDataNode::IsLink(nsIURI** aURI) const michael@0: { michael@0: *aURI = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: nsINode::nsSlots* michael@0: nsGenericDOMDataNode::CreateSlots() michael@0: { michael@0: return new nsDataSlots(); michael@0: } michael@0: michael@0: nsGenericDOMDataNode::nsDataSlots::nsDataSlots() michael@0: : nsINode::nsSlots(), mBindingParent(nullptr) michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsGenericDOMDataNode::nsDataSlots::Traverse(nsCycleCollectionTraversalCallback &cb) 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->mContainingShadow"); michael@0: cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mContainingShadow)); michael@0: } michael@0: michael@0: void michael@0: nsGenericDOMDataNode::nsDataSlots::Unlink() michael@0: { michael@0: mXBLInsertionParent = nullptr; michael@0: mContainingShadow = nullptr; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: // Implementation of the nsIDOMText interface michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::SplitData(uint32_t aOffset, nsIContent** aReturn, michael@0: bool aCloneAfterOriginal) michael@0: { michael@0: *aReturn = nullptr; michael@0: nsresult rv = NS_OK; michael@0: nsAutoString cutText; michael@0: uint32_t length = TextLength(); michael@0: michael@0: if (aOffset > length) { michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: } michael@0: michael@0: uint32_t cutStartOffset = aCloneAfterOriginal ? aOffset : 0; michael@0: uint32_t cutLength = aCloneAfterOriginal ? length - aOffset : aOffset; michael@0: rv = SubstringData(cutStartOffset, cutLength, cutText); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: nsIDocument* document = GetCurrentDoc(); michael@0: mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, true); michael@0: michael@0: // Use Clone for creating the new node so that the new node is of same class michael@0: // as this node! michael@0: nsCOMPtr newContent = CloneDataNode(mNodeInfo, false); michael@0: if (!newContent) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: newContent->SetText(cutText, true); // XXX should be false? michael@0: michael@0: CharacterDataChangeInfo::Details details = { michael@0: CharacterDataChangeInfo::Details::eSplit, newContent michael@0: }; michael@0: rv = SetTextInternal(cutStartOffset, cutLength, nullptr, 0, true, michael@0: aCloneAfterOriginal ? &details : nullptr); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr parent = GetParentNode(); michael@0: if (parent) { michael@0: int32_t insertionIndex = parent->IndexOf(this); michael@0: if (aCloneAfterOriginal) { michael@0: ++insertionIndex; michael@0: } michael@0: parent->InsertChildAt(newContent, insertionIndex, true); michael@0: } michael@0: michael@0: newContent.swap(*aReturn); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::SplitText(uint32_t aOffset, nsIDOMText** aReturn) michael@0: { michael@0: nsCOMPtr newChild; michael@0: nsresult rv = SplitData(aOffset, getter_AddRefs(newChild)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = CallQueryInterface(newChild, aReturn); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: /* static */ int32_t michael@0: nsGenericDOMDataNode::FirstLogicallyAdjacentTextNode(nsIContent* aParent, michael@0: int32_t aIndex) michael@0: { michael@0: while (aIndex-- > 0) { michael@0: nsIContent* sibling = aParent->GetChildAt(aIndex); michael@0: if (!sibling->IsNodeOfType(nsINode::eTEXT)) michael@0: return aIndex + 1; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: /* static */ int32_t michael@0: nsGenericDOMDataNode::LastLogicallyAdjacentTextNode(nsIContent* aParent, michael@0: int32_t aIndex, michael@0: uint32_t aCount) michael@0: { michael@0: while (++aIndex < int32_t(aCount)) { michael@0: nsIContent* sibling = aParent->GetChildAt(aIndex); michael@0: if (!sibling->IsNodeOfType(nsINode::eTEXT)) michael@0: return aIndex - 1; michael@0: } michael@0: return aCount - 1; michael@0: } michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::GetWholeText(nsAString& aWholeText) michael@0: { michael@0: nsIContent* parent = GetParent(); michael@0: michael@0: // Handle parent-less nodes michael@0: if (!parent) michael@0: return GetData(aWholeText); michael@0: michael@0: int32_t index = parent->IndexOf(this); michael@0: NS_WARN_IF_FALSE(index >= 0, michael@0: "Trying to use .wholeText with an anonymous" michael@0: "text node child of a binding parent?"); michael@0: NS_ENSURE_TRUE(index >= 0, NS_ERROR_DOM_NOT_SUPPORTED_ERR); michael@0: int32_t first = michael@0: FirstLogicallyAdjacentTextNode(parent, index); michael@0: int32_t last = michael@0: LastLogicallyAdjacentTextNode(parent, index, parent->GetChildCount()); michael@0: michael@0: aWholeText.Truncate(); michael@0: michael@0: nsCOMPtr node; michael@0: nsAutoString tmp; michael@0: do { michael@0: node = do_QueryInterface(parent->GetChildAt(first)); michael@0: node->GetData(tmp); michael@0: aWholeText.Append(tmp); michael@0: } while (first++ < last); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: // Implementation of the nsIContent interface text functions michael@0: michael@0: const nsTextFragment * michael@0: nsGenericDOMDataNode::GetText() michael@0: { michael@0: return &mText; michael@0: } michael@0: michael@0: uint32_t michael@0: nsGenericDOMDataNode::TextLength() const michael@0: { michael@0: return mText.GetLength(); michael@0: } michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::SetText(const char16_t* aBuffer, michael@0: uint32_t aLength, michael@0: bool aNotify) michael@0: { michael@0: return SetTextInternal(0, mText.GetLength(), aBuffer, aLength, aNotify); michael@0: } michael@0: michael@0: nsresult michael@0: nsGenericDOMDataNode::AppendText(const char16_t* aBuffer, michael@0: uint32_t aLength, michael@0: bool aNotify) michael@0: { michael@0: return SetTextInternal(mText.GetLength(), 0, aBuffer, aLength, aNotify); michael@0: } michael@0: michael@0: bool michael@0: nsGenericDOMDataNode::TextIsOnlyWhitespace() michael@0: { michael@0: // FIXME: should this method take content language into account? michael@0: if (mText.Is2b()) { michael@0: // The fragment contains non-8bit characters and such characters michael@0: // are never considered whitespace. michael@0: return false; michael@0: } michael@0: michael@0: if (HasFlag(NS_CACHED_TEXT_IS_ONLY_WHITESPACE)) { michael@0: return HasFlag(NS_TEXT_IS_ONLY_WHITESPACE); michael@0: } michael@0: michael@0: const char* cp = mText.Get1b(); michael@0: const char* end = cp + mText.GetLength(); michael@0: michael@0: while (cp < end) { michael@0: char ch = *cp; michael@0: michael@0: if (!dom::IsSpaceCharacter(ch)) { michael@0: UnsetFlags(NS_TEXT_IS_ONLY_WHITESPACE); michael@0: SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE); michael@0: return false; michael@0: } michael@0: michael@0: ++cp; michael@0: } michael@0: michael@0: SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE | NS_TEXT_IS_ONLY_WHITESPACE); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsGenericDOMDataNode::HasTextForTranslation() michael@0: { michael@0: if (mText.Is2b()) { michael@0: // The fragment contains non-8bit characters which means there michael@0: // was at least one "interesting" character to trigger non-8bit. michael@0: return true; michael@0: } michael@0: michael@0: if (HasFlag(NS_CACHED_TEXT_IS_ONLY_WHITESPACE) && michael@0: HasFlag(NS_TEXT_IS_ONLY_WHITESPACE)) { michael@0: return false; michael@0: } michael@0: michael@0: const char* cp = mText.Get1b(); michael@0: const char* end = cp + mText.GetLength(); michael@0: michael@0: unsigned char ch; michael@0: for (; cp < end; cp++) { michael@0: ch = *cp; michael@0: michael@0: // These are the characters that are letters michael@0: // in the first 256 UTF-8 codepoints. michael@0: if ((ch >= 'a' && ch <= 'z') || michael@0: (ch >= 'A' && ch <= 'Z') || michael@0: (ch >= 192 && ch <= 214) || michael@0: (ch >= 216 && ch <= 246) || michael@0: (ch >= 248)) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsGenericDOMDataNode::AppendTextTo(nsAString& aResult) michael@0: { michael@0: mText.AppendTo(aResult); michael@0: } michael@0: michael@0: bool michael@0: nsGenericDOMDataNode::AppendTextTo(nsAString& aResult, const mozilla::fallible_t&) michael@0: { michael@0: return mText.AppendTo(aResult, mozilla::fallible_t()); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsGenericDOMDataNode::GetCurrentValueAtom() michael@0: { michael@0: nsAutoString val; michael@0: GetData(val); michael@0: return NS_NewAtom(val); michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsGenericDOMDataNode::DoGetID() const michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: const nsAttrValue* michael@0: nsGenericDOMDataNode::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: nsGenericDOMDataNode::WalkContentStyleRules(nsRuleWalker* aRuleWalker) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(bool) michael@0: nsGenericDOMDataNode::IsAttributeMapped(const nsIAtom* aAttribute) const michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: nsChangeHint michael@0: nsGenericDOMDataNode::GetAttributeChangeHint(const nsIAtom* aAttribute, michael@0: int32_t aModType) const michael@0: { michael@0: NS_NOTREACHED("Shouldn't be calling this!"); michael@0: return nsChangeHint(0); michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsGenericDOMDataNode::GetClassAttributeName() const michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: size_t michael@0: nsGenericDOMDataNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t n = nsIContent::SizeOfExcludingThis(aMallocSizeOf); michael@0: n += mText.SizeOfExcludingThis(aMallocSizeOf); michael@0: return n; michael@0: } michael@0: