diff options
Diffstat (limited to 'dom/base/CharacterData.cpp')
-rw-r--r-- | dom/base/CharacterData.cpp | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/dom/base/CharacterData.cpp b/dom/base/CharacterData.cpp new file mode 100644 index 0000000000..b4809a0293 --- /dev/null +++ b/dom/base/CharacterData.cpp @@ -0,0 +1,591 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Base class for DOM Core's Comment, DocumentType, Text, + * CDATASection and ProcessingInstruction nodes. + */ + +#include "mozilla/dom/CharacterData.h" + +#include "mozilla/DebugOnly.h" + +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/dom/BindContext.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLSlotElement.h" +#include "mozilla/dom/MutationObservers.h" +#include "mozilla/dom/ShadowRoot.h" +#include "mozilla/dom/Document.h" +#include "nsReadableUtils.h" +#include "mozilla/InternalMutationEvent.h" +#include "nsCOMPtr.h" +#include "nsDOMString.h" +#include "nsChangeHint.h" +#include "nsCOMArray.h" +#include "mozilla/dom/DirectionalityUtils.h" +#include "nsCCUncollectableMarker.h" +#include "mozAutoDocUpdate.h" +#include "nsIContentInlines.h" +#include "nsTextNode.h" +#include "nsBidiUtils.h" +#include "PLDHashTable.h" +#include "mozilla/Sprintf.h" +#include "nsWindowSizes.h" +#include "nsWrapperCacheInlines.h" + +#if defined(ACCESSIBILITY) && defined(DEBUG) +# include "nsAccessibilityService.h" +#endif + +namespace mozilla::dom { + +CharacterData::CharacterData(already_AddRefed<dom::NodeInfo>&& aNodeInfo) + : nsIContent(std::move(aNodeInfo)) { + MOZ_ASSERT(mNodeInfo->NodeType() == TEXT_NODE || + mNodeInfo->NodeType() == CDATA_SECTION_NODE || + mNodeInfo->NodeType() == COMMENT_NODE || + mNodeInfo->NodeType() == PROCESSING_INSTRUCTION_NODE || + mNodeInfo->NodeType() == DOCUMENT_TYPE_NODE, + "Bad NodeType in aNodeInfo"); +} + +CharacterData::~CharacterData() { + MOZ_ASSERT(!IsInUncomposedDoc(), + "Please remove this from the document properly"); + if (GetParent()) { + NS_RELEASE(mParent); + } +} + +Element* CharacterData::GetNameSpaceElement() { + return Element::FromNodeOrNull(GetParentNode()); +} + +// Note, _INHERITED macro isn't used here since nsINode implementations are +// rather special. +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CharacterData) + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CharacterData) + return Element::CanSkip(tmp, aRemovingAllowed); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CharacterData) + return Element::CanSkipInCC(tmp); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CharacterData) + return Element::CanSkipThis(tmp); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END + +// We purposefully don't TRAVERSE_BEGIN_INHERITED here. All the bits +// we should traverse should be added here or in nsINode::Traverse. +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(CharacterData) + if (MOZ_UNLIKELY(cb.WantDebugInfo())) { + char name[40]; + SprintfLiteral(name, "CharacterData (len=%d)", tmp->mText.GetLength()); + cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name); + } else { + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(CharacterData, tmp->mRefCnt.get()) + } + + if (!nsIContent::Traverse(tmp, cb)) { + return NS_SUCCESS_INTERRUPTED_TRAVERSE; + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +// We purposefully don't UNLINK_BEGIN_INHERITED here. +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CharacterData) + nsIContent::Unlink(tmp); + + if (nsContentSlots* slots = tmp->GetExistingContentSlots()) { + slots->Unlink(*tmp); + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN(CharacterData) + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(CharacterData) +NS_INTERFACE_MAP_END_INHERITING(nsIContent) + +void CharacterData::GetNodeValueInternal(nsAString& aNodeValue) { + GetData(aNodeValue); +} + +void CharacterData::SetNodeValueInternal(const nsAString& aNodeValue, + ErrorResult& aError) { + aError = SetTextInternal(0, mText.GetLength(), aNodeValue.BeginReading(), + aNodeValue.Length(), true); +} + +//---------------------------------------------------------------------- + +// Implementation of CharacterData + +void CharacterData::SetTextContentInternal(const nsAString& aTextContent, + nsIPrincipal* aSubjectPrincipal, + ErrorResult& aError) { + // Batch possible DOMSubtreeModified events. + mozAutoSubtreeModified subtree(OwnerDoc(), nullptr); + return SetNodeValue(aTextContent, aError); +} + +void CharacterData::GetData(nsAString& aData) const { + if (mText.Is2b()) { + aData.Truncate(); + mText.AppendTo(aData); + } else { + // Must use Substring() since nsDependentCString() requires null + // terminated strings. + + const char* data = mText.Get1b(); + + if (data) { + CopyASCIItoUTF16(Substring(data, data + mText.GetLength()), aData); + } else { + aData.Truncate(); + } + } +} + +void CharacterData::SetData(const nsAString& aData, ErrorResult& aRv) { + nsresult rv = SetTextInternal(0, mText.GetLength(), aData.BeginReading(), + aData.Length(), true); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + } +} + +void CharacterData::SubstringData(uint32_t aStart, uint32_t aCount, + nsAString& aReturn, ErrorResult& rv) { + aReturn.Truncate(); + + uint32_t textLength = mText.GetLength(); + if (aStart > textLength) { + rv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + uint32_t amount = aCount; + if (amount > textLength - aStart) { + amount = textLength - aStart; + } + + if (mText.Is2b()) { + aReturn.Assign(mText.Get2b() + aStart, amount); + } else { + // Must use Substring() since nsDependentCString() requires null + // terminated strings. + + const char* data = mText.Get1b() + aStart; + CopyASCIItoUTF16(Substring(data, data + amount), aReturn); + } +} + +//---------------------------------------------------------------------- + +void CharacterData::AppendData(const nsAString& aData, ErrorResult& aRv) { + InsertData(mText.GetLength(), aData, aRv); +} + +void CharacterData::InsertData(uint32_t aOffset, const nsAString& aData, + ErrorResult& aRv) { + nsresult rv = + SetTextInternal(aOffset, 0, aData.BeginReading(), aData.Length(), true); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + } +} + +void CharacterData::DeleteData(uint32_t aOffset, uint32_t aCount, + ErrorResult& aRv) { + nsresult rv = SetTextInternal(aOffset, aCount, nullptr, 0, true); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + } +} + +void CharacterData::ReplaceData(uint32_t aOffset, uint32_t aCount, + const nsAString& aData, ErrorResult& aRv) { + nsresult rv = SetTextInternal(aOffset, aCount, aData.BeginReading(), + aData.Length(), true); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + } +} + +nsresult CharacterData::SetTextInternal( + uint32_t aOffset, uint32_t aCount, const char16_t* aBuffer, + uint32_t aLength, bool aNotify, + CharacterDataChangeInfo::Details* aDetails) { + MOZ_ASSERT(aBuffer || !aLength, "Null buffer passed to SetTextInternal!"); + + // sanitize arguments + uint32_t textLength = mText.GetLength(); + if (aOffset > textLength) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + if (aCount > textLength - aOffset) { + aCount = textLength - aOffset; + } + + uint32_t endOffset = aOffset + aCount; + + // Make sure the text fragment can hold the new data. + if (aLength > aCount && !mText.CanGrowBy(aLength - aCount)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + Document* document = GetComposedDoc(); + mozAutoDocUpdate updateBatch(document, aNotify); + + bool haveMutationListeners = + aNotify && nsContentUtils::HasMutationListeners( + this, NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED, this); + + RefPtr<nsAtom> oldValue; + if (haveMutationListeners) { + oldValue = GetCurrentValueAtom(); + } + + if (aNotify) { + CharacterDataChangeInfo info = {aOffset == textLength, aOffset, endOffset, + aLength, aDetails}; + MutationObservers::NotifyCharacterDataWillChange(this, info); + } + + auto oldDir = Directionality::Unset; + const bool dirAffectsAncestor = + NodeType() == TEXT_NODE && + TextNodeWillChangeDirection(static_cast<nsTextNode*>(this), &oldDir, + aOffset); + + if (aOffset == 0 && endOffset == textLength) { + // Replacing whole text or old text was empty. + // If this is marked as "maybe modified frequently", the text should be + // stored as char16_t since converting char* to char16_t* is expensive. + bool ok = mText.SetTo(aBuffer, aLength, true, + HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY)); + NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); + } else if (aOffset == textLength) { + // Appending to existing. + bool ok = mText.Append(aBuffer, aLength, !mText.IsBidi(), + HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY)); + NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); + } else { + // Merging old and new + + bool bidi = mText.IsBidi(); + + // Allocate new buffer + const uint32_t newLength = textLength - aCount + aLength; + // Use nsString and not nsAutoString so that we get a nsStringBuffer which + // can be just AddRefed in nsTextFragment. + nsString to; + to.SetCapacity(newLength); + + // Copy over appropriate data + if (aOffset) { + mText.AppendTo(to, 0, aOffset); + } + if (aLength) { + to.Append(aBuffer, aLength); + if (!bidi) { + bidi = HasRTLChars(Span(aBuffer, aLength)); + } + } + if (endOffset != textLength) { + mText.AppendTo(to, endOffset, textLength - endOffset); + } + + // If this is marked as "maybe modified frequently", the text should be + // stored as char16_t since converting char* to char16_t* is expensive. + // Use char16_t also when we have bidi characters. + bool use2b = HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY) || bidi; + bool ok = mText.SetTo(to, false, use2b); + mText.SetBidi(bidi); + + NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); + } + + UnsetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE); + + if (document && mText.IsBidi()) { + // If we found bidi characters in mText.SetTo() above, indicate that the + // document contains bidi characters. + document->SetBidiEnabled(); + } + + if (dirAffectsAncestor) { + // dirAffectsAncestor being true implies that we have a text node, see + // above. + MOZ_ASSERT(NodeType() == TEXT_NODE); + TextNodeChangedDirection(static_cast<nsTextNode*>(this), oldDir, aNotify); + } + + // Notify observers + if (aNotify) { + CharacterDataChangeInfo info = {aOffset == textLength, aOffset, endOffset, + aLength, aDetails}; + MutationObservers::NotifyCharacterDataChanged(this, info); + + if (haveMutationListeners) { + InternalMutationEvent mutation(true, eLegacyCharacterDataModified); + + mutation.mPrevAttrValue = oldValue; + if (aLength > 0) { + nsAutoString val; + mText.AppendTo(val); + mutation.mNewAttrValue = NS_Atomize(val); + } + + mozAutoSubtreeModified subtree(OwnerDoc(), this); + AsyncEventDispatcher::RunDOMEventWhenSafe(*this, mutation); + } + } + + return NS_OK; +} + +//---------------------------------------------------------------------- + +// Implementation of nsIContent + +#ifdef MOZ_DOM_LIST +void CharacterData::ToCString(nsAString& aBuf, int32_t aOffset, + int32_t aLen) const { + if (mText.Is2b()) { + const char16_t* cp = mText.Get2b() + aOffset; + const char16_t* end = cp + aLen; + + while (cp < end) { + char16_t ch = *cp++; + if (ch == '&') { + aBuf.AppendLiteral("&"); + } else if (ch == '<') { + aBuf.AppendLiteral("<"); + } else if (ch == '>') { + aBuf.AppendLiteral(">"); + } else if ((ch < ' ') || (ch >= 127)) { + aBuf.AppendPrintf("\\u%04x", ch); + } else { + aBuf.Append(ch); + } + } + } else { + unsigned char* cp = (unsigned char*)mText.Get1b() + aOffset; + const unsigned char* end = cp + aLen; + + while (cp < end) { + char16_t ch = *cp++; + if (ch == '&') { + aBuf.AppendLiteral("&"); + } else if (ch == '<') { + aBuf.AppendLiteral("<"); + } else if (ch == '>') { + aBuf.AppendLiteral(">"); + } else if ((ch < ' ') || (ch >= 127)) { + aBuf.AppendPrintf("\\u%04x", ch); + } else { + aBuf.Append(ch); + } + } + } +} +#endif + +nsresult CharacterData::BindToTree(BindContext& aContext, nsINode& aParent) { + MOZ_ASSERT(aParent.IsContent() || aParent.IsDocument(), + "Must have content or document parent!"); + MOZ_ASSERT(aParent.OwnerDoc() == OwnerDoc(), + "Must have the same owner document"); + MOZ_ASSERT(OwnerDoc() == &aContext.OwnerDoc(), "These should match too"); + MOZ_ASSERT(!IsInUncomposedDoc(), "Already have a document. Unbind first!"); + MOZ_ASSERT(!IsInComposedDoc(), "Already have a document. Unbind first!"); + // Note that as we recurse into the kids, they'll have a non-null parent. So + // only assert if our parent is _changing_ while we have a parent. + MOZ_ASSERT(!GetParentNode() || &aParent == GetParentNode(), + "Already have a parent. Unbind first!"); + + const bool hadParent = !!GetParentNode(); + + if (aParent.IsInNativeAnonymousSubtree()) { + SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE); + } + if (IsRootOfNativeAnonymousSubtree()) { + aParent.SetMayHaveAnonymousChildren(); + } else if (aParent.HasFlag(NODE_HAS_BEEN_IN_UA_WIDGET)) { + SetFlags(NODE_HAS_BEEN_IN_UA_WIDGET); + } + + // Set parent + mParent = &aParent; + if (!hadParent && aParent.IsContent()) { + SetParentIsContent(true); + NS_ADDREF(mParent); + } + MOZ_ASSERT(!!GetParent() == aParent.IsContent()); + + if (aParent.IsInUncomposedDoc() || aParent.IsInShadowTree()) { + // We no longer need to track the subtree pointer (and in fact we'll assert + // if we do this any later). + ClearSubtreeRootPointer(); + SetIsConnected(aParent.IsInComposedDoc()); + + if (aParent.IsInUncomposedDoc()) { + SetIsInDocument(); + } else { + SetFlags(NODE_IS_IN_SHADOW_TREE); + MOZ_ASSERT(aParent.IsContent() && + aParent.AsContent()->GetContainingShadow()); + ExtendedContentSlots()->mContainingShadow = + aParent.AsContent()->GetContainingShadow(); + } + + if (IsInComposedDoc() && mText.IsBidi()) { + aContext.OwnerDoc().SetBidiEnabled(); + } + + // Clear the lazy frame construction bits. + UnsetFlags(NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES); + } else { + // If we're not in the doc and not in a shadow tree, + // update our subtree pointer. + SetSubtreeRootPointer(aParent.SubtreeRoot()); + } + + MutationObservers::NotifyParentChainChanged(this); + + UpdateEditableState(false); + + // Ensure we only do these once, in the case we move the shadow host around. + if (aContext.SubtreeRootChanges()) { + HandleShadowDOMRelatedInsertionSteps(hadParent); + } + + MOZ_ASSERT(OwnerDoc() == aParent.OwnerDoc(), "Bound to wrong document"); + MOZ_ASSERT(IsInComposedDoc() == aContext.InComposedDoc()); + MOZ_ASSERT(IsInUncomposedDoc() == aContext.InUncomposedDoc()); + MOZ_ASSERT(&aParent == GetParentNode(), "Bound to wrong parent node"); + MOZ_ASSERT(aParent.IsInUncomposedDoc() == IsInUncomposedDoc()); + MOZ_ASSERT(aParent.IsInComposedDoc() == IsInComposedDoc()); + MOZ_ASSERT(aParent.IsInShadowTree() == IsInShadowTree()); + MOZ_ASSERT(aParent.SubtreeRoot() == SubtreeRoot()); + return NS_OK; +} + +void CharacterData::UnbindFromTree(bool aNullParent) { + // Unset frame flags; if we need them again later, they'll get set again. + UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE | NS_REFRAME_IF_WHITESPACE); + + HandleShadowDOMRelatedRemovalSteps(aNullParent); + + if (aNullParent) { + if (GetParent()) { + NS_RELEASE(mParent); + } else { + mParent = nullptr; + } + SetParentIsContent(false); + } + ClearInDocument(); + SetIsConnected(false); + + if (aNullParent || !mParent->IsInShadowTree()) { + UnsetFlags(NODE_IS_IN_SHADOW_TREE); + + // Begin keeping track of our subtree root. + SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot()); + } + + if (nsExtendedContentSlots* slots = GetExistingExtendedContentSlots()) { + if (aNullParent || !mParent->IsInShadowTree()) { + slots->mContainingShadow = nullptr; + } + } + + MutationObservers::NotifyParentChainChanged(this); + +#if defined(ACCESSIBILITY) && defined(DEBUG) + MOZ_ASSERT(!GetAccService() || !GetAccService()->HasAccessible(this), + "An accessible for this element still exists!"); +#endif +} + +//---------------------------------------------------------------------- + +// Implementation of the nsIContent interface text functions + +nsresult CharacterData::SetText(const char16_t* aBuffer, uint32_t aLength, + bool aNotify) { + return SetTextInternal(0, mText.GetLength(), aBuffer, aLength, aNotify); +} + +nsresult CharacterData::AppendText(const char16_t* aBuffer, uint32_t aLength, + bool aNotify) { + return SetTextInternal(mText.GetLength(), 0, aBuffer, aLength, aNotify); +} + +bool CharacterData::TextIsOnlyWhitespace() { + MOZ_ASSERT(NS_IsMainThread()); + if (!ThreadSafeTextIsOnlyWhitespace()) { + UnsetFlags(NS_TEXT_IS_ONLY_WHITESPACE); + SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE); + return false; + } + + SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE | NS_TEXT_IS_ONLY_WHITESPACE); + return true; +} + +bool CharacterData::ThreadSafeTextIsOnlyWhitespace() const { + // FIXME: should this method take content language into account? + if (mText.Is2b()) { + // The fragment contains non-8bit characters and such characters + // are never considered whitespace. + // + // FIXME(emilio): This is not quite true in presence of the + // NS_MAYBE_MODIFIED_FREQUENTLY flag... But looks like we only set that on + // anonymous nodes, so should be fine... + return false; + } + + if (HasFlag(NS_CACHED_TEXT_IS_ONLY_WHITESPACE)) { + return HasFlag(NS_TEXT_IS_ONLY_WHITESPACE); + } + + const char* cp = mText.Get1b(); + const char* end = cp + mText.GetLength(); + + while (cp < end) { + char ch = *cp; + + // NOTE(emilio): If you ever change the definition of "whitespace" here, you + // need to change it too in RestyleManager::CharacterDataChanged. + if (!dom::IsSpaceCharacter(ch)) { + return false; + } + + ++cp; + } + + return true; +} + +already_AddRefed<nsAtom> CharacterData::GetCurrentValueAtom() { + nsAutoString val; + GetData(val); + return NS_Atomize(val); +} + +void CharacterData::AddSizeOfExcludingThis(nsWindowSizes& aSizes, + size_t* aNodeSize) const { + nsIContent::AddSizeOfExcludingThis(aSizes, aNodeSize); + *aNodeSize += mText.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf); +} + +} // namespace mozilla::dom |