Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / CharacterData.cpp
blobeccf9fe4d90de49f86e88dffc3fc35a0aa059cd5
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8 * Base class for DOM Core's Comment, DocumentType, Text,
9 * CDATASection and ProcessingInstruction nodes.
12 #include "mozilla/dom/CharacterData.h"
14 #include "mozilla/AsyncEventDispatcher.h"
15 #include "mozilla/dom/BindContext.h"
16 #include "mozilla/dom/Element.h"
17 #include "mozilla/dom/MutationObservers.h"
18 #include "mozilla/dom/ShadowRoot.h"
19 #include "mozilla/dom/Document.h"
20 #include "mozilla/dom/UnbindContext.h"
21 #include "nsReadableUtils.h"
22 #include "mozilla/InternalMutationEvent.h"
23 #include "mozilla/dom/DirectionalityUtils.h"
24 #include "mozAutoDocUpdate.h"
25 #include "nsIContentInlines.h"
26 #include "nsTextNode.h"
27 #include "nsBidiUtils.h"
28 #include "mozilla/Sprintf.h"
29 #include "nsWindowSizes.h"
31 #if defined(ACCESSIBILITY) && defined(DEBUG)
32 # include "nsAccessibilityService.h"
33 #endif
35 namespace mozilla::dom {
37 CharacterData::CharacterData(already_AddRefed<dom::NodeInfo>&& aNodeInfo)
38 : nsIContent(std::move(aNodeInfo)) {
39 MOZ_ASSERT(mNodeInfo->NodeType() == TEXT_NODE ||
40 mNodeInfo->NodeType() == CDATA_SECTION_NODE ||
41 mNodeInfo->NodeType() == COMMENT_NODE ||
42 mNodeInfo->NodeType() == PROCESSING_INSTRUCTION_NODE ||
43 mNodeInfo->NodeType() == DOCUMENT_TYPE_NODE,
44 "Bad NodeType in aNodeInfo");
47 CharacterData::~CharacterData() {
48 MOZ_ASSERT(!IsInUncomposedDoc(),
49 "Please remove this from the document properly");
50 if (GetParent()) {
51 NS_RELEASE(mParent);
55 Element* CharacterData::GetNameSpaceElement() {
56 return Element::FromNodeOrNull(GetParentNode());
59 // Note, _INHERITED macro isn't used here since nsINode implementations are
60 // rather special.
61 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CharacterData)
63 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CharacterData)
64 return Element::CanSkip(tmp, aRemovingAllowed);
65 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
67 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CharacterData)
68 return Element::CanSkipInCC(tmp);
69 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
71 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CharacterData)
72 return Element::CanSkipThis(tmp);
73 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
75 // We purposefully don't TRAVERSE_BEGIN_INHERITED here. All the bits
76 // we should traverse should be added here or in nsINode::Traverse.
77 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(CharacterData)
78 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
79 char name[40];
80 SprintfLiteral(name, "CharacterData (len=%d)", tmp->mText.GetLength());
81 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
82 } else {
83 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(CharacterData, tmp->mRefCnt.get())
86 if (!nsIContent::Traverse(tmp, cb)) {
87 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
89 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
91 // We purposefully don't UNLINK_BEGIN_INHERITED here.
92 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CharacterData)
93 nsIContent::Unlink(tmp);
95 if (nsContentSlots* slots = tmp->GetExistingContentSlots()) {
96 slots->Unlink(*tmp);
98 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
100 NS_INTERFACE_MAP_BEGIN(CharacterData)
101 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(CharacterData)
102 NS_INTERFACE_MAP_END_INHERITING(nsIContent)
104 void CharacterData::GetNodeValueInternal(nsAString& aNodeValue) {
105 GetData(aNodeValue);
108 void CharacterData::SetNodeValueInternal(const nsAString& aNodeValue,
109 ErrorResult& aError) {
110 aError = SetTextInternal(0, mText.GetLength(), aNodeValue.BeginReading(),
111 aNodeValue.Length(), true);
114 //----------------------------------------------------------------------
116 // Implementation of CharacterData
118 void CharacterData::SetTextContentInternal(const nsAString& aTextContent,
119 nsIPrincipal* aSubjectPrincipal,
120 ErrorResult& aError) {
121 // Batch possible DOMSubtreeModified events.
122 mozAutoSubtreeModified subtree(OwnerDoc(), nullptr);
123 return SetNodeValue(aTextContent, aError);
126 void CharacterData::GetData(nsAString& aData) const {
127 if (mText.Is2b()) {
128 aData.Truncate();
129 mText.AppendTo(aData);
130 } else {
131 // Must use Substring() since nsDependentCString() requires null
132 // terminated strings.
134 const char* data = mText.Get1b();
136 if (data) {
137 CopyASCIItoUTF16(Substring(data, data + mText.GetLength()), aData);
138 } else {
139 aData.Truncate();
144 void CharacterData::SetData(const nsAString& aData, ErrorResult& aRv) {
145 nsresult rv = SetTextInternal(0, mText.GetLength(), aData.BeginReading(),
146 aData.Length(), true);
147 if (NS_FAILED(rv)) {
148 aRv.Throw(rv);
152 void CharacterData::SubstringData(uint32_t aStart, uint32_t aCount,
153 nsAString& aReturn, ErrorResult& rv) {
154 aReturn.Truncate();
156 uint32_t textLength = mText.GetLength();
157 if (aStart > textLength) {
158 rv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
159 return;
162 uint32_t amount = aCount;
163 if (amount > textLength - aStart) {
164 amount = textLength - aStart;
167 if (mText.Is2b()) {
168 aReturn.Assign(mText.Get2b() + aStart, amount);
169 } else {
170 // Must use Substring() since nsDependentCString() requires null
171 // terminated strings.
173 const char* data = mText.Get1b() + aStart;
174 CopyASCIItoUTF16(Substring(data, data + amount), aReturn);
178 //----------------------------------------------------------------------
180 void CharacterData::AppendData(const nsAString& aData, ErrorResult& aRv) {
181 InsertData(mText.GetLength(), aData, aRv);
184 void CharacterData::InsertData(uint32_t aOffset, const nsAString& aData,
185 ErrorResult& aRv) {
186 nsresult rv =
187 SetTextInternal(aOffset, 0, aData.BeginReading(), aData.Length(), true);
188 if (NS_FAILED(rv)) {
189 aRv.Throw(rv);
193 void CharacterData::DeleteData(uint32_t aOffset, uint32_t aCount,
194 ErrorResult& aRv) {
195 nsresult rv = SetTextInternal(aOffset, aCount, nullptr, 0, true);
196 if (NS_FAILED(rv)) {
197 aRv.Throw(rv);
201 void CharacterData::ReplaceData(uint32_t aOffset, uint32_t aCount,
202 const nsAString& aData, ErrorResult& aRv) {
203 nsresult rv = SetTextInternal(aOffset, aCount, aData.BeginReading(),
204 aData.Length(), true);
205 if (NS_FAILED(rv)) {
206 aRv.Throw(rv);
210 nsresult CharacterData::SetTextInternal(
211 uint32_t aOffset, uint32_t aCount, const char16_t* aBuffer,
212 uint32_t aLength, bool aNotify,
213 CharacterDataChangeInfo::Details* aDetails) {
214 MOZ_ASSERT(aBuffer || !aLength, "Null buffer passed to SetTextInternal!");
216 // sanitize arguments
217 uint32_t textLength = mText.GetLength();
218 if (aOffset > textLength) {
219 return NS_ERROR_DOM_INDEX_SIZE_ERR;
222 if (aCount > textLength - aOffset) {
223 aCount = textLength - aOffset;
226 uint32_t endOffset = aOffset + aCount;
228 // Make sure the text fragment can hold the new data.
229 if (aLength > aCount && !mText.CanGrowBy(aLength - aCount)) {
230 return NS_ERROR_OUT_OF_MEMORY;
233 Document* document = GetComposedDoc();
234 mozAutoDocUpdate updateBatch(document, aNotify);
236 bool haveMutationListeners =
237 aNotify && nsContentUtils::HasMutationListeners(
238 this, NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED, this);
240 RefPtr<nsAtom> oldValue;
241 if (haveMutationListeners) {
242 oldValue = GetCurrentValueAtom();
245 if (aNotify) {
246 CharacterDataChangeInfo info = {aOffset == textLength, aOffset, endOffset,
247 aLength, aDetails};
248 MutationObservers::NotifyCharacterDataWillChange(this, info);
251 auto oldDir = Directionality::Unset;
252 const bool dirAffectsAncestor =
253 NodeType() == TEXT_NODE &&
254 TextNodeWillChangeDirection(static_cast<nsTextNode*>(this), &oldDir,
255 aOffset);
257 if (aOffset == 0 && endOffset == textLength) {
258 // Replacing whole text or old text was empty.
259 // If this is marked as "maybe modified frequently", the text should be
260 // stored as char16_t since converting char* to char16_t* is expensive.
261 bool ok = mText.SetTo(aBuffer, aLength, true,
262 HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY));
263 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
264 } else if (aOffset == textLength) {
265 // Appending to existing.
266 bool ok = mText.Append(aBuffer, aLength, !mText.IsBidi(),
267 HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY));
268 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
269 } else {
270 // Merging old and new
272 bool bidi = mText.IsBidi();
274 // Allocate new buffer
275 const uint32_t newLength = textLength - aCount + aLength;
276 // Use nsString and not nsAutoString so that we get a nsStringBuffer which
277 // can be just AddRefed in nsTextFragment.
278 nsString to;
279 to.SetCapacity(newLength);
281 // Copy over appropriate data
282 if (aOffset) {
283 mText.AppendTo(to, 0, aOffset);
285 if (aLength) {
286 to.Append(aBuffer, aLength);
287 if (!bidi) {
288 bidi = HasRTLChars(Span(aBuffer, aLength));
291 if (endOffset != textLength) {
292 mText.AppendTo(to, endOffset, textLength - endOffset);
295 // If this is marked as "maybe modified frequently", the text should be
296 // stored as char16_t since converting char* to char16_t* is expensive.
297 // Use char16_t also when we have bidi characters.
298 bool use2b = HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY) || bidi;
299 bool ok = mText.SetTo(to, false, use2b);
300 mText.SetBidi(bidi);
302 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
305 UnsetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE);
307 if (document && mText.IsBidi()) {
308 // If we found bidi characters in mText.SetTo() above, indicate that the
309 // document contains bidi characters.
310 document->SetBidiEnabled();
313 if (dirAffectsAncestor) {
314 // dirAffectsAncestor being true implies that we have a text node, see
315 // above.
316 MOZ_ASSERT(NodeType() == TEXT_NODE);
317 TextNodeChangedDirection(static_cast<nsTextNode*>(this), oldDir, aNotify);
320 // Notify observers
321 if (aNotify) {
322 CharacterDataChangeInfo info = {aOffset == textLength, aOffset, endOffset,
323 aLength, aDetails};
324 MutationObservers::NotifyCharacterDataChanged(this, info);
326 if (haveMutationListeners) {
327 InternalMutationEvent mutation(true, eLegacyCharacterDataModified);
329 mutation.mPrevAttrValue = oldValue;
330 if (aLength > 0) {
331 nsAutoString val;
332 mText.AppendTo(val);
333 mutation.mNewAttrValue = NS_Atomize(val);
336 mozAutoSubtreeModified subtree(OwnerDoc(), this);
337 AsyncEventDispatcher::RunDOMEventWhenSafe(*this, mutation);
341 return NS_OK;
344 //----------------------------------------------------------------------
346 // Implementation of nsIContent
348 #ifdef MOZ_DOM_LIST
349 void CharacterData::ToCString(nsAString& aBuf, int32_t aOffset,
350 int32_t aLen) const {
351 if (mText.Is2b()) {
352 const char16_t* cp = mText.Get2b() + aOffset;
353 const char16_t* end = cp + aLen;
355 while (cp < end) {
356 char16_t ch = *cp++;
357 if (ch == '&') {
358 aBuf.AppendLiteral("&amp;");
359 } else if (ch == '<') {
360 aBuf.AppendLiteral("&lt;");
361 } else if (ch == '>') {
362 aBuf.AppendLiteral("&gt;");
363 } else if ((ch < ' ') || (ch >= 127)) {
364 aBuf.AppendPrintf("\\u%04x", ch);
365 } else {
366 aBuf.Append(ch);
369 } else {
370 unsigned char* cp = (unsigned char*)mText.Get1b() + aOffset;
371 const unsigned char* end = cp + aLen;
373 while (cp < end) {
374 char16_t ch = *cp++;
375 if (ch == '&') {
376 aBuf.AppendLiteral("&amp;");
377 } else if (ch == '<') {
378 aBuf.AppendLiteral("&lt;");
379 } else if (ch == '>') {
380 aBuf.AppendLiteral("&gt;");
381 } else if ((ch < ' ') || (ch >= 127)) {
382 aBuf.AppendPrintf("\\u%04x", ch);
383 } else {
384 aBuf.Append(ch);
389 #endif
391 nsresult CharacterData::BindToTree(BindContext& aContext, nsINode& aParent) {
392 MOZ_ASSERT(aParent.IsContent() || aParent.IsDocument(),
393 "Must have content or document parent!");
394 MOZ_ASSERT(aParent.OwnerDoc() == OwnerDoc(),
395 "Must have the same owner document");
396 MOZ_ASSERT(OwnerDoc() == &aContext.OwnerDoc(), "These should match too");
397 MOZ_ASSERT(!IsInUncomposedDoc(), "Already have a document. Unbind first!");
398 MOZ_ASSERT(!IsInComposedDoc(), "Already have a document. Unbind first!");
399 // Note that as we recurse into the kids, they'll have a non-null parent. So
400 // only assert if our parent is _changing_ while we have a parent.
401 MOZ_ASSERT(!GetParentNode() || &aParent == GetParentNode(),
402 "Already have a parent. Unbind first!");
404 const bool hadParent = !!GetParentNode();
406 if (aParent.IsInNativeAnonymousSubtree()) {
407 SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
409 if (IsRootOfNativeAnonymousSubtree()) {
410 aParent.SetMayHaveAnonymousChildren();
411 } else if (aParent.HasFlag(NODE_HAS_BEEN_IN_UA_WIDGET)) {
412 SetFlags(NODE_HAS_BEEN_IN_UA_WIDGET);
415 // Set parent
416 mParent = &aParent;
417 if (!hadParent && aParent.IsContent()) {
418 SetParentIsContent(true);
419 NS_ADDREF(mParent);
421 MOZ_ASSERT(!!GetParent() == aParent.IsContent());
423 if (aParent.IsInUncomposedDoc() || aParent.IsInShadowTree()) {
424 // We no longer need to track the subtree pointer (and in fact we'll assert
425 // if we do this any later).
426 ClearSubtreeRootPointer();
427 SetIsConnected(aParent.IsInComposedDoc());
429 if (aParent.IsInUncomposedDoc()) {
430 SetIsInDocument();
431 } else {
432 SetFlags(NODE_IS_IN_SHADOW_TREE);
433 MOZ_ASSERT(aParent.IsContent() &&
434 aParent.AsContent()->GetContainingShadow());
435 ExtendedContentSlots()->mContainingShadow =
436 aParent.AsContent()->GetContainingShadow();
439 if (IsInComposedDoc() && mText.IsBidi()) {
440 aContext.OwnerDoc().SetBidiEnabled();
443 // Clear the lazy frame construction bits.
444 UnsetFlags(NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES);
445 } else {
446 // If we're not in the doc and not in a shadow tree,
447 // update our subtree pointer.
448 SetSubtreeRootPointer(aParent.SubtreeRoot());
451 MutationObservers::NotifyParentChainChanged(this);
453 UpdateEditableState(false);
455 // Ensure we only do these once, in the case we move the shadow host around.
456 if (aContext.SubtreeRootChanges()) {
457 HandleShadowDOMRelatedInsertionSteps(hadParent);
460 MOZ_ASSERT(OwnerDoc() == aParent.OwnerDoc(), "Bound to wrong document");
461 MOZ_ASSERT(IsInComposedDoc() == aContext.InComposedDoc());
462 MOZ_ASSERT(IsInUncomposedDoc() == aContext.InUncomposedDoc());
463 MOZ_ASSERT(&aParent == GetParentNode(), "Bound to wrong parent node");
464 MOZ_ASSERT(aParent.IsInUncomposedDoc() == IsInUncomposedDoc());
465 MOZ_ASSERT(aParent.IsInComposedDoc() == IsInComposedDoc());
466 MOZ_ASSERT(aParent.IsInShadowTree() == IsInShadowTree());
467 MOZ_ASSERT(aParent.SubtreeRoot() == SubtreeRoot());
468 return NS_OK;
471 void CharacterData::UnbindFromTree(UnbindContext& aContext) {
472 // Unset frame flags; if we need them again later, they'll get set again.
473 UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE | NS_REFRAME_IF_WHITESPACE);
475 const bool nullParent = aContext.IsUnbindRoot(this);
476 HandleShadowDOMRelatedRemovalSteps(nullParent);
478 if (nullParent) {
479 if (GetParent()) {
480 NS_RELEASE(mParent);
481 } else {
482 mParent = nullptr;
484 SetParentIsContent(false);
486 ClearInDocument();
487 SetIsConnected(false);
489 if (nullParent || !mParent->IsInShadowTree()) {
490 UnsetFlags(NODE_IS_IN_SHADOW_TREE);
492 // Begin keeping track of our subtree root.
493 SetSubtreeRootPointer(nullParent ? this : mParent->SubtreeRoot());
495 if (nsExtendedContentSlots* slots = GetExistingExtendedContentSlots()) {
496 slots->mContainingShadow = nullptr;
500 MutationObservers::NotifyParentChainChanged(this);
502 #if defined(ACCESSIBILITY) && defined(DEBUG)
503 MOZ_ASSERT(!GetAccService() || !GetAccService()->HasAccessible(this),
504 "An accessible for this element still exists!");
505 #endif
508 //----------------------------------------------------------------------
510 // Implementation of the nsIContent interface text functions
512 nsresult CharacterData::SetText(const char16_t* aBuffer, uint32_t aLength,
513 bool aNotify) {
514 return SetTextInternal(0, mText.GetLength(), aBuffer, aLength, aNotify);
517 nsresult CharacterData::AppendText(const char16_t* aBuffer, uint32_t aLength,
518 bool aNotify) {
519 return SetTextInternal(mText.GetLength(), 0, aBuffer, aLength, aNotify);
522 bool CharacterData::TextIsOnlyWhitespace() {
523 MOZ_ASSERT(NS_IsMainThread());
524 if (!ThreadSafeTextIsOnlyWhitespace()) {
525 UnsetFlags(NS_TEXT_IS_ONLY_WHITESPACE);
526 SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE);
527 return false;
530 SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE | NS_TEXT_IS_ONLY_WHITESPACE);
531 return true;
534 bool CharacterData::ThreadSafeTextIsOnlyWhitespace() const {
535 // FIXME: should this method take content language into account?
536 if (mText.Is2b()) {
537 // The fragment contains non-8bit characters and such characters
538 // are never considered whitespace.
540 // FIXME(emilio): This is not quite true in presence of the
541 // NS_MAYBE_MODIFIED_FREQUENTLY flag... But looks like we only set that on
542 // anonymous nodes, so should be fine...
543 return false;
546 if (HasFlag(NS_CACHED_TEXT_IS_ONLY_WHITESPACE)) {
547 return HasFlag(NS_TEXT_IS_ONLY_WHITESPACE);
550 const char* cp = mText.Get1b();
551 const char* end = cp + mText.GetLength();
553 while (cp < end) {
554 char ch = *cp;
556 // NOTE(emilio): If you ever change the definition of "whitespace" here, you
557 // need to change it too in RestyleManager::CharacterDataChanged.
558 if (!dom::IsSpaceCharacter(ch)) {
559 return false;
562 ++cp;
565 return true;
568 already_AddRefed<nsAtom> CharacterData::GetCurrentValueAtom() {
569 nsAutoString val;
570 GetData(val);
571 return NS_Atomize(val);
574 void CharacterData::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
575 size_t* aNodeSize) const {
576 nsIContent::AddSizeOfExcludingThis(aSizes, aNodeSize);
577 *aNodeSize += mText.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
580 } // namespace mozilla::dom