tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

CharacterData.cpp (19892B)


      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/. */
      6 
      7 /*
      8 * Base class for DOM Core's Comment, DocumentType, Text,
      9 * CDATASection and ProcessingInstruction nodes.
     10 */
     11 
     12 #include "mozilla/dom/CharacterData.h"
     13 
     14 #include "mozAutoDocUpdate.h"
     15 #include "mozilla/AsyncEventDispatcher.h"
     16 #include "mozilla/Sprintf.h"
     17 #include "mozilla/dom/BindContext.h"
     18 #include "mozilla/dom/DirectionalityUtils.h"
     19 #include "mozilla/dom/Document.h"
     20 #include "mozilla/dom/Element.h"
     21 #include "mozilla/dom/MutationObservers.h"
     22 #include "mozilla/dom/ShadowRoot.h"
     23 #include "mozilla/dom/UnbindContext.h"
     24 #include "nsBidiUtils.h"
     25 #include "nsIContentInlines.h"
     26 #include "nsReadableUtils.h"
     27 #include "nsTextNode.h"
     28 #include "nsWindowSizes.h"
     29 
     30 #if defined(ACCESSIBILITY) && defined(DEBUG)
     31 #  include "nsAccessibilityService.h"
     32 #endif
     33 
     34 namespace mozilla::dom {
     35 
     36 CharacterData::CharacterData(already_AddRefed<dom::NodeInfo>&& aNodeInfo)
     37    : nsIContent(std::move(aNodeInfo)) {
     38  MOZ_ASSERT(mNodeInfo->NodeType() == TEXT_NODE ||
     39                 mNodeInfo->NodeType() == CDATA_SECTION_NODE ||
     40                 mNodeInfo->NodeType() == COMMENT_NODE ||
     41                 mNodeInfo->NodeType() == PROCESSING_INSTRUCTION_NODE ||
     42                 mNodeInfo->NodeType() == DOCUMENT_TYPE_NODE,
     43             "Bad NodeType in aNodeInfo");
     44 }
     45 
     46 CharacterData::~CharacterData() {
     47  MOZ_ASSERT(!IsInUncomposedDoc(),
     48             "Please remove this from the document properly");
     49  if (GetParent()) {
     50    NS_RELEASE(mParent);
     51  }
     52 }
     53 
     54 Element* CharacterData::GetNameSpaceElement() {
     55  return Element::FromNodeOrNull(GetParentNode());
     56 }
     57 
     58 // Note, _INHERITED macro isn't used here since nsINode implementations are
     59 // rather special.
     60 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CharacterData)
     61 
     62 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CharacterData)
     63  return Element::CanSkip(tmp, aRemovingAllowed);
     64 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
     65 
     66 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CharacterData)
     67  return Element::CanSkipInCC(tmp);
     68 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
     69 
     70 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CharacterData)
     71  return Element::CanSkipThis(tmp);
     72 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
     73 
     74 // We purposefully don't TRAVERSE_BEGIN_INHERITED here.  All the bits
     75 // we should traverse should be added here or in nsINode::Traverse.
     76 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(CharacterData)
     77  if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
     78    char name[40];
     79    SprintfLiteral(name, "CharacterData (len=%d)", tmp->mBuffer.GetLength());
     80    cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
     81  } else {
     82    NS_IMPL_CYCLE_COLLECTION_DESCRIBE(CharacterData, tmp->mRefCnt.get())
     83  }
     84 
     85  if (!nsIContent::Traverse(tmp, cb)) {
     86    return NS_SUCCESS_INTERRUPTED_TRAVERSE;
     87  }
     88 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     89 
     90 // We purposefully don't UNLINK_BEGIN_INHERITED here.
     91 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CharacterData)
     92  nsIContent::Unlink(tmp);
     93 
     94  if (nsContentSlots* slots = tmp->GetExistingContentSlots()) {
     95    slots->Unlink(*tmp);
     96  }
     97 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     98 
     99 NS_INTERFACE_MAP_BEGIN(CharacterData)
    100  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(CharacterData)
    101 NS_INTERFACE_MAP_END_INHERITING(nsIContent)
    102 
    103 void CharacterData::GetNodeValueInternal(nsAString& aNodeValue) {
    104  GetData(aNodeValue);
    105 }
    106 
    107 void CharacterData::SetNodeValueInternal(
    108    const nsAString& aNodeValue, ErrorResult& aError,
    109    MutationEffectOnScript aMutationEffectOnScript) {
    110  aError = SetTextInternal(0, mBuffer.GetLength(), aNodeValue.BeginReading(),
    111                           aNodeValue.Length(), true, aMutationEffectOnScript);
    112 }
    113 
    114 //----------------------------------------------------------------------
    115 
    116 // Implementation of CharacterData
    117 
    118 void CharacterData::SetTextContentInternal(
    119    const nsAString& aTextContent, nsIPrincipal* aSubjectPrincipal,
    120    ErrorResult& aError, MutationEffectOnScript aMutationEffectOnScript) {
    121  return SetNodeValueInternal(aTextContent, aError, aMutationEffectOnScript);
    122 }
    123 
    124 void CharacterData::GetData(nsAString& aData) const {
    125  if (mBuffer.Is2b()) {
    126    aData.Truncate();
    127    mBuffer.AppendTo(aData);
    128  } else {
    129    // Must use Substring() since nsDependentCString() requires null
    130    // terminated strings.
    131 
    132    const char* data = mBuffer.Get1b();
    133 
    134    if (data) {
    135      CopyASCIItoUTF16(Substring(data, data + mBuffer.GetLength()), aData);
    136    } else {
    137      aData.Truncate();
    138    }
    139  }
    140 }
    141 
    142 void CharacterData::SetDataInternal(
    143    const nsAString& aData, MutationEffectOnScript aMutationEffectOnScript,
    144    ErrorResult& aRv) {
    145  nsresult rv = SetTextInternal(0, mBuffer.GetLength(), aData.BeginReading(),
    146                                aData.Length(), true, aMutationEffectOnScript);
    147  if (NS_FAILED(rv)) {
    148    aRv.Throw(rv);
    149  }
    150 }
    151 
    152 void CharacterData::SubstringData(uint32_t aStart, uint32_t aCount,
    153                                  nsAString& aReturn, ErrorResult& rv) {
    154  aReturn.Truncate();
    155 
    156  uint32_t textLength = mBuffer.GetLength();
    157  if (aStart > textLength) {
    158    rv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
    159    return;
    160  }
    161 
    162  uint32_t amount = aCount;
    163  if (amount > textLength - aStart) {
    164    amount = textLength - aStart;
    165  }
    166 
    167  if (mBuffer.Is2b()) {
    168    aReturn.Assign(mBuffer.Get2b() + aStart, amount);
    169  } else {
    170    // Must use Substring() since nsDependentCString() requires null
    171    // terminated strings.
    172 
    173    const char* data = mBuffer.Get1b() + aStart;
    174    CopyASCIItoUTF16(Substring(data, data + amount), aReturn);
    175  }
    176 }
    177 
    178 //----------------------------------------------------------------------
    179 
    180 void CharacterData::AppendDataInternal(
    181    const nsAString& aData, MutationEffectOnScript aMutationEffectOnScript,
    182    ErrorResult& aRv) {
    183  InsertDataInternal(mBuffer.GetLength(), aData, aMutationEffectOnScript, aRv);
    184 }
    185 
    186 void CharacterData::InsertDataInternal(
    187    uint32_t aOffset, const nsAString& aData,
    188    MutationEffectOnScript aMutationEffectOnScript, ErrorResult& aRv) {
    189  nsresult rv = SetTextInternal(aOffset, 0, aData.BeginReading(),
    190                                aData.Length(), true, aMutationEffectOnScript);
    191  if (NS_FAILED(rv)) {
    192    aRv.Throw(rv);
    193  }
    194 }
    195 
    196 void CharacterData::DeleteDataInternal(
    197    uint32_t aOffset, uint32_t aCount,
    198    MutationEffectOnScript aMutationEffectOnScript, ErrorResult& aRv) {
    199  nsresult rv = SetTextInternal(aOffset, aCount, nullptr, 0, true,
    200                                aMutationEffectOnScript);
    201  if (NS_FAILED(rv)) {
    202    aRv.Throw(rv);
    203  }
    204 }
    205 
    206 void CharacterData::ReplaceDataInternal(
    207    uint32_t aOffset, uint32_t aCount, const nsAString& aData,
    208    MutationEffectOnScript aMutationEffectOnScript, ErrorResult& aRv) {
    209  nsresult rv = SetTextInternal(aOffset, aCount, aData.BeginReading(),
    210                                aData.Length(), true, aMutationEffectOnScript);
    211  if (NS_FAILED(rv)) {
    212    aRv.Throw(rv);
    213  }
    214 }
    215 
    216 nsresult CharacterData::SetTextInternal(
    217    uint32_t aOffset, uint32_t aCount, const char16_t* aBuffer,
    218    uint32_t aLength, bool aNotify,
    219    MutationEffectOnScript aMutationEffectOnScript,
    220    CharacterDataChangeInfo::Details* aDetails) {
    221  MOZ_ASSERT(aBuffer || !aLength, "Null buffer passed to SetTextInternal!");
    222 
    223  // sanitize arguments
    224  uint32_t textLength = mBuffer.GetLength();
    225  if (aOffset > textLength) {
    226    return NS_ERROR_DOM_INDEX_SIZE_ERR;
    227  }
    228 
    229  if (aCount > textLength - aOffset) {
    230    aCount = textLength - aOffset;
    231  }
    232 
    233  uint32_t endOffset = aOffset + aCount;
    234 
    235  // Make sure the text fragment can hold the new data.
    236  if (aLength > aCount && !mBuffer.CanGrowBy(aLength - aCount)) {
    237    return NS_ERROR_OUT_OF_MEMORY;
    238  }
    239 
    240  Document* document = GetComposedDoc();
    241  mozAutoDocUpdate updateBatch(document, aNotify);
    242 
    243  if (aNotify) {
    244    CharacterDataChangeInfo info = {
    245        aOffset == textLength,   aOffset, endOffset, aLength,
    246        aMutationEffectOnScript, aDetails};
    247    MutationObservers::NotifyCharacterDataWillChange(this, info);
    248  }
    249 
    250  auto oldDir = Directionality::Unset;
    251  const bool dirAffectsAncestor =
    252      IsText() && TextNodeWillChangeDirection(AsText(), &oldDir, aOffset);
    253 
    254  if (aOffset == 0 && endOffset == textLength) {
    255    // Replacing whole text or old text was empty.
    256    // If this is marked as "maybe modified frequently", the text should be
    257    // stored as char16_t since converting char* to char16_t* is expensive.
    258    bool ok = mBuffer.SetTo(aBuffer, aLength, true,
    259                            HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY));
    260    NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
    261  } else if (aOffset == textLength) {
    262    // Appending to existing.
    263    bool ok = mBuffer.Append(aBuffer, aLength, !mBuffer.IsBidi(),
    264                             HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY));
    265    NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
    266  } else {
    267    // Merging old and new
    268 
    269    bool bidi = mBuffer.IsBidi();
    270 
    271    // Allocate new buffer
    272    const uint32_t newLength = textLength - aCount + aLength;
    273    // Use nsString and not nsAutoString so that we get a nsStringBuffer which
    274    // can be just AddRefed in CharacterDataBuffer.
    275    nsString to;
    276    to.SetCapacity(newLength);
    277 
    278    // Copy over appropriate data
    279    if (aOffset) {
    280      mBuffer.AppendTo(to, 0, aOffset);
    281    }
    282    if (aLength) {
    283      to.Append(aBuffer, aLength);
    284      if (!bidi) {
    285        bidi = HasRTLChars(Span(aBuffer, aLength));
    286      }
    287    }
    288    if (endOffset != textLength) {
    289      mBuffer.AppendTo(to, endOffset, textLength - endOffset);
    290    }
    291 
    292    // If this is marked as "maybe modified frequently", the text should be
    293    // stored as char16_t since converting char* to char16_t* is expensive.
    294    // Use char16_t also when we have bidi characters.
    295    bool use2b = HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY) || bidi;
    296    bool ok = mBuffer.SetTo(to, false, use2b);
    297    mBuffer.SetBidi(bidi);
    298 
    299    NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
    300  }
    301 
    302  UnsetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE);
    303 
    304  if (document && mBuffer.IsBidi()) {
    305    // If we found bidi characters in mBuffer.SetTo() above, indicate that the
    306    // document contains bidi characters.
    307    document->SetBidiEnabled();
    308  }
    309 
    310  if (dirAffectsAncestor) {
    311    // dirAffectsAncestor being true implies that we have a text node, see
    312    // above.
    313    MOZ_ASSERT(IsText());
    314    TextNodeChangedDirection(AsText(), oldDir, aNotify);
    315  }
    316 
    317  // Notify observers
    318  if (aNotify) {
    319    CharacterDataChangeInfo info = {
    320        aOffset == textLength,   aOffset, endOffset, aLength,
    321        aMutationEffectOnScript, aDetails};
    322    MutationObservers::NotifyCharacterDataChanged(this, info);
    323  }
    324 
    325  return NS_OK;
    326 }
    327 
    328 //----------------------------------------------------------------------
    329 
    330 // Implementation of nsIContent
    331 
    332 #ifdef MOZ_DOM_LIST
    333 void CharacterData::ToCString(nsAString& aBuf, int32_t aOffset,
    334                              int32_t aLen) const {
    335  if (mBuffer.Is2b()) {
    336    const char16_t* cp = mBuffer.Get2b() + aOffset;
    337    const char16_t* end = cp + aLen;
    338 
    339    while (cp < end) {
    340      char16_t ch = *cp++;
    341      if (ch == '&') {
    342        aBuf.AppendLiteral("&amp;");
    343      } else if (ch == '<') {
    344        aBuf.AppendLiteral("&lt;");
    345      } else if (ch == '>') {
    346        aBuf.AppendLiteral("&gt;");
    347      } else if ((ch < ' ') || (ch >= 127)) {
    348        aBuf.AppendPrintf("\\u%04x", ch);
    349      } else {
    350        aBuf.Append(ch);
    351      }
    352    }
    353  } else {
    354    unsigned char* cp = (unsigned char*)mBuffer.Get1b() + aOffset;
    355    const unsigned char* end = cp + aLen;
    356 
    357    while (cp < end) {
    358      char16_t ch = *cp++;
    359      if (ch == '&') {
    360        aBuf.AppendLiteral("&amp;");
    361      } else if (ch == '<') {
    362        aBuf.AppendLiteral("&lt;");
    363      } else if (ch == '>') {
    364        aBuf.AppendLiteral("&gt;");
    365      } else if ((ch < ' ') || (ch >= 127)) {
    366        aBuf.AppendPrintf("\\u%04x", ch);
    367      } else {
    368        aBuf.Append(ch);
    369      }
    370    }
    371  }
    372 }
    373 #endif
    374 
    375 nsresult CharacterData::BindToTree(BindContext& aContext, nsINode& aParent) {
    376  MOZ_ASSERT(aParent.IsContent() || aParent.IsDocument(),
    377             "Must have content or document parent!");
    378  MOZ_ASSERT(aParent.OwnerDoc() == OwnerDoc(),
    379             "Must have the same owner document");
    380  MOZ_ASSERT(OwnerDoc() == &aContext.OwnerDoc(), "These should match too");
    381  MOZ_ASSERT(!IsInUncomposedDoc(), "Already have a document.  Unbind first!");
    382  MOZ_ASSERT(!IsInComposedDoc(), "Already have a document.  Unbind first!");
    383  // Note that as we recurse into the kids, they'll have a non-null parent.  So
    384  // only assert if our parent is _changing_ while we have a parent.
    385  MOZ_ASSERT(!GetParentNode() || &aParent == GetParentNode(),
    386             "Already have a parent.  Unbind first!");
    387 
    388  const bool hadParent = !!GetParentNode();
    389 
    390  if (aParent.IsInNativeAnonymousSubtree()) {
    391    SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
    392  }
    393  if (IsRootOfNativeAnonymousSubtree()) {
    394    aParent.SetMayHaveAnonymousChildren();
    395  } else if (aParent.HasFlag(NODE_HAS_BEEN_IN_UA_WIDGET)) {
    396    SetFlags(NODE_HAS_BEEN_IN_UA_WIDGET);
    397  }
    398 
    399  // Set parent
    400  mParent = &aParent;
    401  if (!hadParent && aParent.IsContent()) {
    402    SetParentIsContent(true);
    403    NS_ADDREF(mParent);
    404  }
    405  MOZ_ASSERT(!!GetParent() == aParent.IsContent());
    406 
    407  if (aParent.IsInUncomposedDoc() || aParent.IsInShadowTree()) {
    408    // We no longer need to track the subtree pointer (and in fact we'll assert
    409    // if we do this any later).
    410    ClearSubtreeRootPointer();
    411    SetIsConnected(aParent.IsInComposedDoc());
    412 
    413    if (aParent.IsInUncomposedDoc()) {
    414      SetIsInDocument();
    415    } else {
    416      SetFlags(NODE_IS_IN_SHADOW_TREE);
    417      MOZ_ASSERT(aParent.IsContent() &&
    418                 aParent.AsContent()->GetContainingShadow());
    419      ExtendedContentSlots()->mContainingShadow =
    420          aParent.AsContent()->GetContainingShadow();
    421    }
    422 
    423    if (IsInComposedDoc() && mBuffer.IsBidi()) {
    424      aContext.OwnerDoc().SetBidiEnabled();
    425    }
    426 
    427    // Clear the lazy frame construction bits.
    428    UnsetFlags(NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES);
    429  } else {
    430    // If we're not in the doc and not in a shadow tree,
    431    // update our subtree pointer.
    432    SetSubtreeRootPointer(aParent.SubtreeRoot());
    433  }
    434 
    435  MutationObservers::NotifyParentChainChanged(this);
    436 
    437  UpdateEditableState(false);
    438 
    439  // Ensure we only do these once, in the case we move the shadow host around.
    440  if (aContext.SubtreeRootChanges()) {
    441    HandleShadowDOMRelatedInsertionSteps(hadParent);
    442  }
    443 
    444  MOZ_ASSERT(OwnerDoc() == aParent.OwnerDoc(), "Bound to wrong document");
    445  MOZ_ASSERT(IsInComposedDoc() == aContext.InComposedDoc());
    446  MOZ_ASSERT(IsInUncomposedDoc() == aContext.InUncomposedDoc());
    447  MOZ_ASSERT(&aParent == GetParentNode(), "Bound to wrong parent node");
    448  MOZ_ASSERT(aParent.IsInUncomposedDoc() == IsInUncomposedDoc());
    449  MOZ_ASSERT(aParent.IsInComposedDoc() == IsInComposedDoc());
    450  MOZ_ASSERT(aParent.IsInShadowTree() == IsInShadowTree());
    451  MOZ_ASSERT(aParent.SubtreeRoot() == SubtreeRoot());
    452  return NS_OK;
    453 }
    454 
    455 void CharacterData::UnbindFromTree(UnbindContext& aContext) {
    456  // Unset frame flags; if we need them again later, they'll get set again.
    457  UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE | NS_REFRAME_IF_WHITESPACE);
    458 
    459  const bool nullParent = aContext.IsUnbindRoot(this);
    460  HandleShadowDOMRelatedRemovalSteps(nullParent);
    461 
    462  if (nullParent) {
    463    if (GetParent()) {
    464      NS_RELEASE(mParent);
    465    } else {
    466      mParent = nullptr;
    467    }
    468    SetParentIsContent(false);
    469  }
    470  ClearInDocument();
    471  SetIsConnected(false);
    472 
    473  if (nullParent || !mParent->IsInShadowTree()) {
    474    UnsetFlags(NODE_IS_IN_SHADOW_TREE);
    475 
    476    // Begin keeping track of our subtree root.
    477    SetSubtreeRootPointer(nullParent ? this : mParent->SubtreeRoot());
    478 
    479    if (nsExtendedContentSlots* slots = GetExistingExtendedContentSlots()) {
    480      slots->mContainingShadow = nullptr;
    481    }
    482  }
    483 
    484  MutationObservers::NotifyParentChainChanged(this);
    485 
    486 #if defined(ACCESSIBILITY) && defined(DEBUG)
    487  MOZ_ASSERT(!GetAccService() || !GetAccService()->HasAccessible(this),
    488             "An accessible for this element still exists!");
    489 #endif
    490 }
    491 
    492 //----------------------------------------------------------------------
    493 
    494 // Implementation of the nsIContent interface text functions
    495 
    496 nsresult CharacterData::SetText(const char16_t* aBuffer, uint32_t aLength,
    497                                bool aNotify) {
    498  return SetTextInternal(0, mBuffer.GetLength(), aBuffer, aLength, aNotify,
    499                         MutationEffectOnScript::KeepTrustWorthiness);
    500 }
    501 
    502 nsresult CharacterData::AppendText(const char16_t* aBuffer, uint32_t aLength,
    503                                   bool aNotify) {
    504  return SetTextInternal(mBuffer.GetLength(), 0, aBuffer, aLength, aNotify,
    505                         MutationEffectOnScript::KeepTrustWorthiness);
    506 }
    507 
    508 bool CharacterData::TextIsOnlyWhitespace() {
    509  MOZ_ASSERT(NS_IsMainThread());
    510  if (!ThreadSafeTextIsOnlyWhitespace()) {
    511    UnsetFlags(NS_TEXT_IS_ONLY_WHITESPACE);
    512    SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE);
    513    return false;
    514  }
    515 
    516  SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE | NS_TEXT_IS_ONLY_WHITESPACE);
    517  return true;
    518 }
    519 
    520 bool CharacterData::ThreadSafeTextIsOnlyWhitespace() const {
    521  // FIXME: should this method take content language into account?
    522  if (mBuffer.Is2b()) {
    523    // The fragment contains non-8bit characters and such characters
    524    // are never considered whitespace.
    525    //
    526    // FIXME(emilio): This is not quite true in presence of the
    527    // NS_MAYBE_MODIFIED_FREQUENTLY flag... But looks like we only set that on
    528    // anonymous nodes, so should be fine...
    529    return false;
    530  }
    531 
    532  if (HasFlag(NS_CACHED_TEXT_IS_ONLY_WHITESPACE)) {
    533    return HasFlag(NS_TEXT_IS_ONLY_WHITESPACE);
    534  }
    535 
    536  return CheckTextIsOnlyWhitespace(0, mBuffer.GetLength());
    537 }
    538 
    539 bool CharacterData::TextStartsWithOnlyWhitespace(uint32_t aOffset) const {
    540  MOZ_ASSERT(aOffset <= mBuffer.GetLength());
    541 
    542  if (HasFlag(NS_CACHED_TEXT_IS_ONLY_WHITESPACE) &&
    543      HasFlag(NS_TEXT_IS_ONLY_WHITESPACE)) {
    544    return true;
    545  }
    546 
    547  return CheckTextIsOnlyWhitespace(0, aOffset);
    548 }
    549 
    550 bool CharacterData::TextEndsWithOnlyWhitespace(uint32_t aOffset) const {
    551  MOZ_ASSERT(aOffset <= mBuffer.GetLength());
    552 
    553  if (HasFlag(NS_CACHED_TEXT_IS_ONLY_WHITESPACE) &&
    554      HasFlag(NS_TEXT_IS_ONLY_WHITESPACE)) {
    555    return true;
    556  }
    557 
    558  return CheckTextIsOnlyWhitespace(aOffset, mBuffer.GetLength());
    559 }
    560 
    561 bool CharacterData::CheckTextIsOnlyWhitespace(uint32_t aStartOffset,
    562                                              uint32_t aEndOffset) const {
    563  if (mBuffer.Is2b()) {
    564    const char16_t* cp = mBuffer.Get2b() + aStartOffset;
    565    const char16_t* end = mBuffer.Get2b() + aEndOffset;
    566 
    567    while (cp < end) {
    568      char16_t ch = *cp;
    569 
    570      // NOTE(emilio): If you ever change the definition of "whitespace" here,
    571      // you need to change it too in RestyleManager::CharacterDataChanged.
    572      if (!dom::IsSpaceCharacter(ch)) {
    573        return false;
    574      }
    575 
    576      ++cp;
    577    }
    578  } else {
    579    const char* cp = mBuffer.Get1b() + aStartOffset;
    580    const char* end = mBuffer.Get1b() + aEndOffset;
    581 
    582    while (cp < end) {
    583      char ch = *cp;
    584 
    585      // NOTE(emilio): If you ever change the definition of "whitespace" here,
    586      // you need to change it too in RestyleManager::CharacterDataChanged.
    587      if (!dom::IsSpaceCharacter(ch)) {
    588        return false;
    589      }
    590 
    591      ++cp;
    592    }
    593  }
    594  return true;
    595 }
    596 
    597 already_AddRefed<nsAtom> CharacterData::GetCurrentValueAtom() {
    598  nsAutoString val;
    599  GetData(val);
    600  return NS_Atomize(val);
    601 }
    602 
    603 void CharacterData::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
    604                                           size_t* aNodeSize) const {
    605  nsIContent::AddSizeOfExcludingThis(aSizes, aNodeSize);
    606  *aNodeSize += mBuffer.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
    607 }
    608 
    609 }  // namespace mozilla::dom