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/. */
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"
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");
55 Element
* CharacterData::GetNameSpaceElement() {
56 return Element::FromNodeOrNull(GetParentNode());
59 // Note, _INHERITED macro isn't used here since nsINode implementations are
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())) {
80 SprintfLiteral(name
, "CharacterData (len=%d)", tmp
->mText
.GetLength());
81 cb
.DescribeRefCountedNode(tmp
->mRefCnt
.get(), name
);
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()) {
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
) {
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 {
129 mText
.AppendTo(aData
);
131 // Must use Substring() since nsDependentCString() requires null
132 // terminated strings.
134 const char* data
= mText
.Get1b();
137 CopyASCIItoUTF16(Substring(data
, data
+ mText
.GetLength()), aData
);
144 void CharacterData::SetData(const nsAString
& aData
, ErrorResult
& aRv
) {
145 nsresult rv
= SetTextInternal(0, mText
.GetLength(), aData
.BeginReading(),
146 aData
.Length(), true);
152 void CharacterData::SubstringData(uint32_t aStart
, uint32_t aCount
,
153 nsAString
& aReturn
, ErrorResult
& rv
) {
156 uint32_t textLength
= mText
.GetLength();
157 if (aStart
> textLength
) {
158 rv
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
162 uint32_t amount
= aCount
;
163 if (amount
> textLength
- aStart
) {
164 amount
= textLength
- aStart
;
168 aReturn
.Assign(mText
.Get2b() + aStart
, amount
);
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
,
187 SetTextInternal(aOffset
, 0, aData
.BeginReading(), aData
.Length(), true);
193 void CharacterData::DeleteData(uint32_t aOffset
, uint32_t aCount
,
195 nsresult rv
= SetTextInternal(aOffset
, aCount
, nullptr, 0, true);
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);
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::WantMutationEvents(
238 this, NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED
, this);
240 RefPtr
<nsAtom
> oldValue
;
241 if (haveMutationListeners
) {
242 oldValue
= GetCurrentValueAtom();
246 CharacterDataChangeInfo info
= {aOffset
== textLength
, aOffset
, endOffset
,
248 MutationObservers::NotifyCharacterDataWillChange(this, info
);
251 auto oldDir
= Directionality::Unset
;
252 const bool dirAffectsAncestor
=
253 IsText() && TextNodeWillChangeDirection(AsText(), &oldDir
, aOffset
);
255 if (aOffset
== 0 && endOffset
== textLength
) {
256 // Replacing whole text or old text was empty.
257 // If this is marked as "maybe modified frequently", the text should be
258 // stored as char16_t since converting char* to char16_t* is expensive.
259 bool ok
= mText
.SetTo(aBuffer
, aLength
, true,
260 HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY
));
261 NS_ENSURE_TRUE(ok
, NS_ERROR_OUT_OF_MEMORY
);
262 } else if (aOffset
== textLength
) {
263 // Appending to existing.
264 bool ok
= mText
.Append(aBuffer
, aLength
, !mText
.IsBidi(),
265 HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY
));
266 NS_ENSURE_TRUE(ok
, NS_ERROR_OUT_OF_MEMORY
);
268 // Merging old and new
270 bool bidi
= mText
.IsBidi();
272 // Allocate new buffer
273 const uint32_t newLength
= textLength
- aCount
+ aLength
;
274 // Use nsString and not nsAutoString so that we get a nsStringBuffer which
275 // can be just AddRefed in nsTextFragment.
277 to
.SetCapacity(newLength
);
279 // Copy over appropriate data
281 mText
.AppendTo(to
, 0, aOffset
);
284 to
.Append(aBuffer
, aLength
);
286 bidi
= HasRTLChars(Span(aBuffer
, aLength
));
289 if (endOffset
!= textLength
) {
290 mText
.AppendTo(to
, endOffset
, textLength
- endOffset
);
293 // If this is marked as "maybe modified frequently", the text should be
294 // stored as char16_t since converting char* to char16_t* is expensive.
295 // Use char16_t also when we have bidi characters.
296 bool use2b
= HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY
) || bidi
;
297 bool ok
= mText
.SetTo(to
, false, use2b
);
300 NS_ENSURE_TRUE(ok
, NS_ERROR_OUT_OF_MEMORY
);
303 UnsetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE
);
305 if (document
&& mText
.IsBidi()) {
306 // If we found bidi characters in mText.SetTo() above, indicate that the
307 // document contains bidi characters.
308 document
->SetBidiEnabled();
311 if (dirAffectsAncestor
) {
312 // dirAffectsAncestor being true implies that we have a text node, see
314 MOZ_ASSERT(IsText());
315 TextNodeChangedDirection(AsText(), oldDir
, aNotify
);
320 CharacterDataChangeInfo info
= {aOffset
== textLength
, aOffset
, endOffset
,
322 MutationObservers::NotifyCharacterDataChanged(this, info
);
324 if (haveMutationListeners
) {
325 InternalMutationEvent
mutation(true, eLegacyCharacterDataModified
);
327 mutation
.mPrevAttrValue
= oldValue
;
331 mutation
.mNewAttrValue
= NS_Atomize(val
);
334 mozAutoSubtreeModified
subtree(OwnerDoc(), this);
335 AsyncEventDispatcher::RunDOMEventWhenSafe(*this, mutation
);
342 //----------------------------------------------------------------------
344 // Implementation of nsIContent
347 void CharacterData::ToCString(nsAString
& aBuf
, int32_t aOffset
,
348 int32_t aLen
) const {
350 const char16_t
* cp
= mText
.Get2b() + aOffset
;
351 const char16_t
* end
= cp
+ aLen
;
356 aBuf
.AppendLiteral("&");
357 } else if (ch
== '<') {
358 aBuf
.AppendLiteral("<");
359 } else if (ch
== '>') {
360 aBuf
.AppendLiteral(">");
361 } else if ((ch
< ' ') || (ch
>= 127)) {
362 aBuf
.AppendPrintf("\\u%04x", ch
);
368 unsigned char* cp
= (unsigned char*)mText
.Get1b() + aOffset
;
369 const unsigned char* end
= cp
+ aLen
;
374 aBuf
.AppendLiteral("&");
375 } else if (ch
== '<') {
376 aBuf
.AppendLiteral("<");
377 } else if (ch
== '>') {
378 aBuf
.AppendLiteral(">");
379 } else if ((ch
< ' ') || (ch
>= 127)) {
380 aBuf
.AppendPrintf("\\u%04x", ch
);
389 nsresult
CharacterData::BindToTree(BindContext
& aContext
, nsINode
& aParent
) {
390 MOZ_ASSERT(aParent
.IsContent() || aParent
.IsDocument(),
391 "Must have content or document parent!");
392 MOZ_ASSERT(aParent
.OwnerDoc() == OwnerDoc(),
393 "Must have the same owner document");
394 MOZ_ASSERT(OwnerDoc() == &aContext
.OwnerDoc(), "These should match too");
395 MOZ_ASSERT(!IsInUncomposedDoc(), "Already have a document. Unbind first!");
396 MOZ_ASSERT(!IsInComposedDoc(), "Already have a document. Unbind first!");
397 // Note that as we recurse into the kids, they'll have a non-null parent. So
398 // only assert if our parent is _changing_ while we have a parent.
399 MOZ_ASSERT(!GetParentNode() || &aParent
== GetParentNode(),
400 "Already have a parent. Unbind first!");
402 const bool hadParent
= !!GetParentNode();
404 if (aParent
.IsInNativeAnonymousSubtree()) {
405 SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE
);
407 if (IsRootOfNativeAnonymousSubtree()) {
408 aParent
.SetMayHaveAnonymousChildren();
409 } else if (aParent
.HasFlag(NODE_HAS_BEEN_IN_UA_WIDGET
)) {
410 SetFlags(NODE_HAS_BEEN_IN_UA_WIDGET
);
415 if (!hadParent
&& aParent
.IsContent()) {
416 SetParentIsContent(true);
419 MOZ_ASSERT(!!GetParent() == aParent
.IsContent());
421 if (aParent
.IsInUncomposedDoc() || aParent
.IsInShadowTree()) {
422 // We no longer need to track the subtree pointer (and in fact we'll assert
423 // if we do this any later).
424 ClearSubtreeRootPointer();
425 SetIsConnected(aParent
.IsInComposedDoc());
427 if (aParent
.IsInUncomposedDoc()) {
430 SetFlags(NODE_IS_IN_SHADOW_TREE
);
431 MOZ_ASSERT(aParent
.IsContent() &&
432 aParent
.AsContent()->GetContainingShadow());
433 ExtendedContentSlots()->mContainingShadow
=
434 aParent
.AsContent()->GetContainingShadow();
437 if (IsInComposedDoc() && mText
.IsBidi()) {
438 aContext
.OwnerDoc().SetBidiEnabled();
441 // Clear the lazy frame construction bits.
442 UnsetFlags(NODE_NEEDS_FRAME
| NODE_DESCENDANTS_NEED_FRAMES
);
444 // If we're not in the doc and not in a shadow tree,
445 // update our subtree pointer.
446 SetSubtreeRootPointer(aParent
.SubtreeRoot());
449 MutationObservers::NotifyParentChainChanged(this);
451 UpdateEditableState(false);
453 // Ensure we only do these once, in the case we move the shadow host around.
454 if (aContext
.SubtreeRootChanges()) {
455 HandleShadowDOMRelatedInsertionSteps(hadParent
);
458 MOZ_ASSERT(OwnerDoc() == aParent
.OwnerDoc(), "Bound to wrong document");
459 MOZ_ASSERT(IsInComposedDoc() == aContext
.InComposedDoc());
460 MOZ_ASSERT(IsInUncomposedDoc() == aContext
.InUncomposedDoc());
461 MOZ_ASSERT(&aParent
== GetParentNode(), "Bound to wrong parent node");
462 MOZ_ASSERT(aParent
.IsInUncomposedDoc() == IsInUncomposedDoc());
463 MOZ_ASSERT(aParent
.IsInComposedDoc() == IsInComposedDoc());
464 MOZ_ASSERT(aParent
.IsInShadowTree() == IsInShadowTree());
465 MOZ_ASSERT(aParent
.SubtreeRoot() == SubtreeRoot());
469 void CharacterData::UnbindFromTree(UnbindContext
& aContext
) {
470 // Unset frame flags; if we need them again later, they'll get set again.
471 UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE
| NS_REFRAME_IF_WHITESPACE
);
473 const bool nullParent
= aContext
.IsUnbindRoot(this);
474 HandleShadowDOMRelatedRemovalSteps(nullParent
);
482 SetParentIsContent(false);
485 SetIsConnected(false);
487 if (nullParent
|| !mParent
->IsInShadowTree()) {
488 UnsetFlags(NODE_IS_IN_SHADOW_TREE
);
490 // Begin keeping track of our subtree root.
491 SetSubtreeRootPointer(nullParent
? this : mParent
->SubtreeRoot());
493 if (nsExtendedContentSlots
* slots
= GetExistingExtendedContentSlots()) {
494 slots
->mContainingShadow
= nullptr;
498 MutationObservers::NotifyParentChainChanged(this);
500 #if defined(ACCESSIBILITY) && defined(DEBUG)
501 MOZ_ASSERT(!GetAccService() || !GetAccService()->HasAccessible(this),
502 "An accessible for this element still exists!");
506 //----------------------------------------------------------------------
508 // Implementation of the nsIContent interface text functions
510 nsresult
CharacterData::SetText(const char16_t
* aBuffer
, uint32_t aLength
,
512 return SetTextInternal(0, mText
.GetLength(), aBuffer
, aLength
, aNotify
);
515 nsresult
CharacterData::AppendText(const char16_t
* aBuffer
, uint32_t aLength
,
517 return SetTextInternal(mText
.GetLength(), 0, aBuffer
, aLength
, aNotify
);
520 bool CharacterData::TextIsOnlyWhitespace() {
521 MOZ_ASSERT(NS_IsMainThread());
522 if (!ThreadSafeTextIsOnlyWhitespace()) {
523 UnsetFlags(NS_TEXT_IS_ONLY_WHITESPACE
);
524 SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE
);
528 SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE
| NS_TEXT_IS_ONLY_WHITESPACE
);
532 bool CharacterData::ThreadSafeTextIsOnlyWhitespace() const {
533 // FIXME: should this method take content language into account?
535 // The fragment contains non-8bit characters and such characters
536 // are never considered whitespace.
538 // FIXME(emilio): This is not quite true in presence of the
539 // NS_MAYBE_MODIFIED_FREQUENTLY flag... But looks like we only set that on
540 // anonymous nodes, so should be fine...
544 if (HasFlag(NS_CACHED_TEXT_IS_ONLY_WHITESPACE
)) {
545 return HasFlag(NS_TEXT_IS_ONLY_WHITESPACE
);
548 const char* cp
= mText
.Get1b();
549 const char* end
= cp
+ mText
.GetLength();
554 // NOTE(emilio): If you ever change the definition of "whitespace" here, you
555 // need to change it too in RestyleManager::CharacterDataChanged.
556 if (!dom::IsSpaceCharacter(ch
)) {
566 already_AddRefed
<nsAtom
> CharacterData::GetCurrentValueAtom() {
569 return NS_Atomize(val
);
572 void CharacterData::AddSizeOfExcludingThis(nsWindowSizes
& aSizes
,
573 size_t* aNodeSize
) const {
574 nsIContent::AddSizeOfExcludingThis(aSizes
, aNodeSize
);
575 *aNodeSize
+= mText
.SizeOfExcludingThis(aSizes
.mState
.mMallocSizeOf
);
578 } // namespace mozilla::dom