Bug 1852740: add tests for the `fetchpriority` attribute in Link headers. r=necko...
[gecko.git] / dom / base / CharacterData.cpp
blobda197aaacba7166c717e1a0b1ba63bb9bd538c3a
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/DebugOnly.h"
16 #include "mozilla/AsyncEventDispatcher.h"
17 #include "mozilla/MemoryReporting.h"
18 #include "mozilla/dom/BindContext.h"
19 #include "mozilla/dom/Element.h"
20 #include "mozilla/dom/HTMLSlotElement.h"
21 #include "mozilla/dom/MutationObservers.h"
22 #include "mozilla/dom/ShadowRoot.h"
23 #include "mozilla/dom/Document.h"
24 #include "nsReadableUtils.h"
25 #include "mozilla/InternalMutationEvent.h"
26 #include "nsCOMPtr.h"
27 #include "nsDOMString.h"
28 #include "nsChangeHint.h"
29 #include "nsCOMArray.h"
30 #include "mozilla/dom/DirectionalityUtils.h"
31 #include "nsCCUncollectableMarker.h"
32 #include "mozAutoDocUpdate.h"
33 #include "nsIContentInlines.h"
34 #include "nsTextNode.h"
35 #include "nsBidiUtils.h"
36 #include "PLDHashTable.h"
37 #include "mozilla/Sprintf.h"
38 #include "nsWindowSizes.h"
39 #include "nsWrapperCacheInlines.h"
41 #if defined(ACCESSIBILITY) && defined(DEBUG)
42 # include "nsAccessibilityService.h"
43 #endif
45 namespace mozilla::dom {
47 CharacterData::CharacterData(already_AddRefed<dom::NodeInfo>&& aNodeInfo)
48 : nsIContent(std::move(aNodeInfo)) {
49 MOZ_ASSERT(mNodeInfo->NodeType() == TEXT_NODE ||
50 mNodeInfo->NodeType() == CDATA_SECTION_NODE ||
51 mNodeInfo->NodeType() == COMMENT_NODE ||
52 mNodeInfo->NodeType() == PROCESSING_INSTRUCTION_NODE ||
53 mNodeInfo->NodeType() == DOCUMENT_TYPE_NODE,
54 "Bad NodeType in aNodeInfo");
57 CharacterData::~CharacterData() {
58 MOZ_ASSERT(!IsInUncomposedDoc(),
59 "Please remove this from the document properly");
60 if (GetParent()) {
61 NS_RELEASE(mParent);
65 Element* CharacterData::GetNameSpaceElement() {
66 return Element::FromNodeOrNull(GetParentNode());
69 // Note, _INHERITED macro isn't used here since nsINode implementations are
70 // rather special.
71 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CharacterData)
73 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CharacterData)
74 return Element::CanSkip(tmp, aRemovingAllowed);
75 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
77 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CharacterData)
78 return Element::CanSkipInCC(tmp);
79 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
81 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CharacterData)
82 return Element::CanSkipThis(tmp);
83 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
85 // We purposefully don't TRAVERSE_BEGIN_INHERITED here. All the bits
86 // we should traverse should be added here or in nsINode::Traverse.
87 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(CharacterData)
88 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
89 char name[40];
90 SprintfLiteral(name, "CharacterData (len=%d)", tmp->mText.GetLength());
91 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
92 } else {
93 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(CharacterData, tmp->mRefCnt.get())
96 if (!nsIContent::Traverse(tmp, cb)) {
97 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
99 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
101 // We purposefully don't UNLINK_BEGIN_INHERITED here.
102 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CharacterData)
103 nsIContent::Unlink(tmp);
105 if (nsContentSlots* slots = tmp->GetExistingContentSlots()) {
106 slots->Unlink(*tmp);
108 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
110 NS_INTERFACE_MAP_BEGIN(CharacterData)
111 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(CharacterData)
112 NS_INTERFACE_MAP_END_INHERITING(nsIContent)
114 void CharacterData::GetNodeValueInternal(nsAString& aNodeValue) {
115 GetData(aNodeValue);
118 void CharacterData::SetNodeValueInternal(const nsAString& aNodeValue,
119 ErrorResult& aError) {
120 aError = SetTextInternal(0, mText.GetLength(), aNodeValue.BeginReading(),
121 aNodeValue.Length(), true);
124 //----------------------------------------------------------------------
126 // Implementation of CharacterData
128 void CharacterData::SetTextContentInternal(const nsAString& aTextContent,
129 nsIPrincipal* aSubjectPrincipal,
130 ErrorResult& aError) {
131 // Batch possible DOMSubtreeModified events.
132 mozAutoSubtreeModified subtree(OwnerDoc(), nullptr);
133 return SetNodeValue(aTextContent, aError);
136 void CharacterData::GetData(nsAString& aData) const {
137 if (mText.Is2b()) {
138 aData.Truncate();
139 mText.AppendTo(aData);
140 } else {
141 // Must use Substring() since nsDependentCString() requires null
142 // terminated strings.
144 const char* data = mText.Get1b();
146 if (data) {
147 CopyASCIItoUTF16(Substring(data, data + mText.GetLength()), aData);
148 } else {
149 aData.Truncate();
154 void CharacterData::SetData(const nsAString& aData, ErrorResult& aRv) {
155 nsresult rv = SetTextInternal(0, mText.GetLength(), aData.BeginReading(),
156 aData.Length(), true);
157 if (NS_FAILED(rv)) {
158 aRv.Throw(rv);
162 void CharacterData::SubstringData(uint32_t aStart, uint32_t aCount,
163 nsAString& aReturn, ErrorResult& rv) {
164 aReturn.Truncate();
166 uint32_t textLength = mText.GetLength();
167 if (aStart > textLength) {
168 rv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
169 return;
172 uint32_t amount = aCount;
173 if (amount > textLength - aStart) {
174 amount = textLength - aStart;
177 if (mText.Is2b()) {
178 aReturn.Assign(mText.Get2b() + aStart, amount);
179 } else {
180 // Must use Substring() since nsDependentCString() requires null
181 // terminated strings.
183 const char* data = mText.Get1b() + aStart;
184 CopyASCIItoUTF16(Substring(data, data + amount), aReturn);
188 //----------------------------------------------------------------------
190 void CharacterData::AppendData(const nsAString& aData, ErrorResult& aRv) {
191 InsertData(mText.GetLength(), aData, aRv);
194 void CharacterData::InsertData(uint32_t aOffset, const nsAString& aData,
195 ErrorResult& aRv) {
196 nsresult rv =
197 SetTextInternal(aOffset, 0, aData.BeginReading(), aData.Length(), true);
198 if (NS_FAILED(rv)) {
199 aRv.Throw(rv);
203 void CharacterData::DeleteData(uint32_t aOffset, uint32_t aCount,
204 ErrorResult& aRv) {
205 nsresult rv = SetTextInternal(aOffset, aCount, nullptr, 0, true);
206 if (NS_FAILED(rv)) {
207 aRv.Throw(rv);
211 void CharacterData::ReplaceData(uint32_t aOffset, uint32_t aCount,
212 const nsAString& aData, ErrorResult& aRv) {
213 nsresult rv = SetTextInternal(aOffset, aCount, aData.BeginReading(),
214 aData.Length(), true);
215 if (NS_FAILED(rv)) {
216 aRv.Throw(rv);
220 nsresult CharacterData::SetTextInternal(
221 uint32_t aOffset, uint32_t aCount, const char16_t* aBuffer,
222 uint32_t aLength, bool aNotify,
223 CharacterDataChangeInfo::Details* aDetails) {
224 MOZ_ASSERT(aBuffer || !aLength, "Null buffer passed to SetTextInternal!");
226 // sanitize arguments
227 uint32_t textLength = mText.GetLength();
228 if (aOffset > textLength) {
229 return NS_ERROR_DOM_INDEX_SIZE_ERR;
232 if (aCount > textLength - aOffset) {
233 aCount = textLength - aOffset;
236 uint32_t endOffset = aOffset + aCount;
238 // Make sure the text fragment can hold the new data.
239 if (aLength > aCount && !mText.CanGrowBy(aLength - aCount)) {
240 return NS_ERROR_OUT_OF_MEMORY;
243 Document* document = GetComposedDoc();
244 mozAutoDocUpdate updateBatch(document, aNotify);
246 bool haveMutationListeners =
247 aNotify && nsContentUtils::HasMutationListeners(
248 this, NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED, this);
250 RefPtr<nsAtom> oldValue;
251 if (haveMutationListeners) {
252 oldValue = GetCurrentValueAtom();
255 if (aNotify) {
256 CharacterDataChangeInfo info = {aOffset == textLength, aOffset, endOffset,
257 aLength, aDetails};
258 MutationObservers::NotifyCharacterDataWillChange(this, info);
261 Directionality oldDir = eDir_NotSet;
262 bool dirAffectsAncestor =
263 (NodeType() == TEXT_NODE &&
264 TextNodeWillChangeDirection(static_cast<nsTextNode*>(this), &oldDir,
265 aOffset));
267 if (aOffset == 0 && endOffset == textLength) {
268 // Replacing whole text or old text was empty.
269 // If this is marked as "maybe modified frequently", the text should be
270 // stored as char16_t since converting char* to char16_t* is expensive.
271 bool ok = mText.SetTo(aBuffer, aLength, true,
272 HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY));
273 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
274 } else if (aOffset == textLength) {
275 // Appending to existing.
276 bool ok = mText.Append(aBuffer, aLength, !mText.IsBidi(),
277 HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY));
278 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
279 } else {
280 // Merging old and new
282 bool bidi = mText.IsBidi();
284 // Allocate new buffer
285 const uint32_t newLength = textLength - aCount + aLength;
286 // Use nsString and not nsAutoString so that we get a nsStringBuffer which
287 // can be just AddRefed in nsTextFragment.
288 nsString to;
289 to.SetCapacity(newLength);
291 // Copy over appropriate data
292 if (aOffset) {
293 mText.AppendTo(to, 0, aOffset);
295 if (aLength) {
296 to.Append(aBuffer, aLength);
297 if (!bidi) {
298 bidi = HasRTLChars(Span(aBuffer, aLength));
301 if (endOffset != textLength) {
302 mText.AppendTo(to, endOffset, textLength - endOffset);
305 // If this is marked as "maybe modified frequently", the text should be
306 // stored as char16_t since converting char* to char16_t* is expensive.
307 // Use char16_t also when we have bidi characters.
308 bool use2b = HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY) || bidi;
309 bool ok = mText.SetTo(to, false, use2b);
310 mText.SetBidi(bidi);
312 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
315 UnsetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE);
317 if (document && mText.IsBidi()) {
318 // If we found bidi characters in mText.SetTo() above, indicate that the
319 // document contains bidi characters.
320 document->SetBidiEnabled();
323 if (dirAffectsAncestor) {
324 // dirAffectsAncestor being true implies that we have a text node, see
325 // above.
326 MOZ_ASSERT(NodeType() == TEXT_NODE);
327 TextNodeChangedDirection(static_cast<nsTextNode*>(this), oldDir, aNotify);
330 // Notify observers
331 if (aNotify) {
332 CharacterDataChangeInfo info = {aOffset == textLength, aOffset, endOffset,
333 aLength, aDetails};
334 MutationObservers::NotifyCharacterDataChanged(this, info);
336 if (haveMutationListeners) {
337 InternalMutationEvent mutation(true, eLegacyCharacterDataModified);
339 mutation.mPrevAttrValue = oldValue;
340 if (aLength > 0) {
341 nsAutoString val;
342 mText.AppendTo(val);
343 mutation.mNewAttrValue = NS_Atomize(val);
346 mozAutoSubtreeModified subtree(OwnerDoc(), this);
347 AsyncEventDispatcher::RunDOMEventWhenSafe(*this, mutation);
351 return NS_OK;
354 //----------------------------------------------------------------------
356 // Implementation of nsIContent
358 #ifdef MOZ_DOM_LIST
359 void CharacterData::ToCString(nsAString& aBuf, int32_t aOffset,
360 int32_t aLen) const {
361 if (mText.Is2b()) {
362 const char16_t* cp = mText.Get2b() + aOffset;
363 const char16_t* end = cp + aLen;
365 while (cp < end) {
366 char16_t ch = *cp++;
367 if (ch == '&') {
368 aBuf.AppendLiteral("&amp;");
369 } else if (ch == '<') {
370 aBuf.AppendLiteral("&lt;");
371 } else if (ch == '>') {
372 aBuf.AppendLiteral("&gt;");
373 } else if ((ch < ' ') || (ch >= 127)) {
374 aBuf.AppendPrintf("\\u%04x", ch);
375 } else {
376 aBuf.Append(ch);
379 } else {
380 unsigned char* cp = (unsigned char*)mText.Get1b() + aOffset;
381 const unsigned char* end = cp + aLen;
383 while (cp < end) {
384 char16_t ch = *cp++;
385 if (ch == '&') {
386 aBuf.AppendLiteral("&amp;");
387 } else if (ch == '<') {
388 aBuf.AppendLiteral("&lt;");
389 } else if (ch == '>') {
390 aBuf.AppendLiteral("&gt;");
391 } else if ((ch < ' ') || (ch >= 127)) {
392 aBuf.AppendPrintf("\\u%04x", ch);
393 } else {
394 aBuf.Append(ch);
399 #endif
401 nsresult CharacterData::BindToTree(BindContext& aContext, nsINode& aParent) {
402 MOZ_ASSERT(aParent.IsContent() || aParent.IsDocument(),
403 "Must have content or document parent!");
404 MOZ_ASSERT(aParent.OwnerDoc() == OwnerDoc(),
405 "Must have the same owner document");
406 MOZ_ASSERT(OwnerDoc() == &aContext.OwnerDoc(), "These should match too");
407 MOZ_ASSERT(!IsInUncomposedDoc(), "Already have a document. Unbind first!");
408 MOZ_ASSERT(!IsInComposedDoc(), "Already have a document. Unbind first!");
409 // Note that as we recurse into the kids, they'll have a non-null parent. So
410 // only assert if our parent is _changing_ while we have a parent.
411 MOZ_ASSERT(!GetParentNode() || &aParent == GetParentNode(),
412 "Already have a parent. Unbind first!");
414 const bool hadParent = !!GetParentNode();
416 if (aParent.IsInNativeAnonymousSubtree()) {
417 SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
419 if (IsRootOfNativeAnonymousSubtree()) {
420 aParent.SetMayHaveAnonymousChildren();
421 } else if (aParent.HasFlag(NODE_HAS_BEEN_IN_UA_WIDGET)) {
422 SetFlags(NODE_HAS_BEEN_IN_UA_WIDGET);
425 // Set parent
426 mParent = &aParent;
427 if (!hadParent && aParent.IsContent()) {
428 SetParentIsContent(true);
429 NS_ADDREF(mParent);
431 MOZ_ASSERT(!!GetParent() == aParent.IsContent());
433 if (aParent.IsInUncomposedDoc() || aParent.IsInShadowTree()) {
434 // We no longer need to track the subtree pointer (and in fact we'll assert
435 // if we do this any later).
436 ClearSubtreeRootPointer();
437 SetIsConnected(aParent.IsInComposedDoc());
439 if (aParent.IsInUncomposedDoc()) {
440 SetIsInDocument();
441 } else {
442 SetFlags(NODE_IS_IN_SHADOW_TREE);
443 MOZ_ASSERT(aParent.IsContent() &&
444 aParent.AsContent()->GetContainingShadow());
445 ExtendedContentSlots()->mContainingShadow =
446 aParent.AsContent()->GetContainingShadow();
449 if (IsInComposedDoc() && mText.IsBidi()) {
450 aContext.OwnerDoc().SetBidiEnabled();
453 // Clear the lazy frame construction bits.
454 UnsetFlags(NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES);
455 } else {
456 // If we're not in the doc and not in a shadow tree,
457 // update our subtree pointer.
458 SetSubtreeRootPointer(aParent.SubtreeRoot());
461 MutationObservers::NotifyParentChainChanged(this);
463 UpdateEditableState(false);
465 // Ensure we only do these once, in the case we move the shadow host around.
466 if (aContext.SubtreeRootChanges()) {
467 HandleShadowDOMRelatedInsertionSteps(hadParent);
470 MOZ_ASSERT(OwnerDoc() == aParent.OwnerDoc(), "Bound to wrong document");
471 MOZ_ASSERT(IsInComposedDoc() == aContext.InComposedDoc());
472 MOZ_ASSERT(IsInUncomposedDoc() == aContext.InUncomposedDoc());
473 MOZ_ASSERT(&aParent == GetParentNode(), "Bound to wrong parent node");
474 MOZ_ASSERT(aParent.IsInUncomposedDoc() == IsInUncomposedDoc());
475 MOZ_ASSERT(aParent.IsInComposedDoc() == IsInComposedDoc());
476 MOZ_ASSERT(aParent.IsInShadowTree() == IsInShadowTree());
477 MOZ_ASSERT(aParent.SubtreeRoot() == SubtreeRoot());
478 return NS_OK;
481 void CharacterData::UnbindFromTree(bool aNullParent) {
482 // Unset frame flags; if we need them again later, they'll get set again.
483 UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE | NS_REFRAME_IF_WHITESPACE);
485 HandleShadowDOMRelatedRemovalSteps(aNullParent);
487 if (aNullParent) {
488 if (GetParent()) {
489 NS_RELEASE(mParent);
490 } else {
491 mParent = nullptr;
493 SetParentIsContent(false);
495 ClearInDocument();
496 SetIsConnected(false);
498 if (aNullParent || !mParent->IsInShadowTree()) {
499 UnsetFlags(NODE_IS_IN_SHADOW_TREE);
501 // Begin keeping track of our subtree root.
502 SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
505 if (nsExtendedContentSlots* slots = GetExistingExtendedContentSlots()) {
506 if (aNullParent || !mParent->IsInShadowTree()) {
507 slots->mContainingShadow = nullptr;
511 MutationObservers::NotifyParentChainChanged(this);
513 #if defined(ACCESSIBILITY) && defined(DEBUG)
514 MOZ_ASSERT(!GetAccService() || !GetAccService()->HasAccessible(this),
515 "An accessible for this element still exists!");
516 #endif
519 //----------------------------------------------------------------------
521 // Implementation of the nsIContent interface text functions
523 nsresult CharacterData::SetText(const char16_t* aBuffer, uint32_t aLength,
524 bool aNotify) {
525 return SetTextInternal(0, mText.GetLength(), aBuffer, aLength, aNotify);
528 nsresult CharacterData::AppendText(const char16_t* aBuffer, uint32_t aLength,
529 bool aNotify) {
530 return SetTextInternal(mText.GetLength(), 0, aBuffer, aLength, aNotify);
533 bool CharacterData::TextIsOnlyWhitespace() {
534 MOZ_ASSERT(NS_IsMainThread());
535 if (!ThreadSafeTextIsOnlyWhitespace()) {
536 UnsetFlags(NS_TEXT_IS_ONLY_WHITESPACE);
537 SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE);
538 return false;
541 SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE | NS_TEXT_IS_ONLY_WHITESPACE);
542 return true;
545 bool CharacterData::ThreadSafeTextIsOnlyWhitespace() const {
546 // FIXME: should this method take content language into account?
547 if (mText.Is2b()) {
548 // The fragment contains non-8bit characters and such characters
549 // are never considered whitespace.
551 // FIXME(emilio): This is not quite true in presence of the
552 // NS_MAYBE_MODIFIED_FREQUENTLY flag... But looks like we only set that on
553 // anonymous nodes, so should be fine...
554 return false;
557 if (HasFlag(NS_CACHED_TEXT_IS_ONLY_WHITESPACE)) {
558 return HasFlag(NS_TEXT_IS_ONLY_WHITESPACE);
561 const char* cp = mText.Get1b();
562 const char* end = cp + mText.GetLength();
564 while (cp < end) {
565 char ch = *cp;
567 // NOTE(emilio): If you ever change the definition of "whitespace" here, you
568 // need to change it too in RestyleManager::CharacterDataChanged.
569 if (!dom::IsSpaceCharacter(ch)) {
570 return false;
573 ++cp;
576 return true;
579 already_AddRefed<nsAtom> CharacterData::GetCurrentValueAtom() {
580 nsAutoString val;
581 GetData(val);
582 return NS_Atomize(val);
585 void CharacterData::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
586 size_t* aNodeSize) const {
587 nsIContent::AddSizeOfExcludingThis(aSizes, aNodeSize);
588 *aNodeSize += mText.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
591 } // namespace mozilla::dom