tor-browser

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

EditorDOMAPIWrapper.h (33829B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #ifndef EditorDOMAPIWrapper_h
      7 #define EditorDOMAPIWrapper_h
      8 
      9 #include "EditorBase.h"  // for EditorBase
     10 #include "HTMLEditor.h"  // for HTMLEditor
     11 
     12 #include "mozilla/Assertions.h"
     13 #include "mozilla/Attributes.h"
     14 #include "mozilla/ErrorResult.h"
     15 #include "mozilla/dom/CharacterData.h"
     16 #include "mozilla/dom/Element.h"
     17 #include "nsAtom.h"
     18 #include "nsDOMCSSDeclaration.h"
     19 #include "nsIContent.h"
     20 #include "nsIMutationObserver.h"
     21 #include "nsINode.h"
     22 #include "nsPrintfCString.h"
     23 #include "nsString.h"
     24 #include "nsStyledElement.h"
     25 
     26 namespace mozilla {
     27 
     28 static void MakeHumanFriendly(nsAutoString& aStr) {
     29  aStr.ReplaceSubstring(u"\n", u"\\n");
     30  aStr.ReplaceSubstring(u"\r", u"\\r");
     31  aStr.ReplaceSubstring(u"\t", u"\\t");
     32  aStr.ReplaceSubstring(u"\f", u"\\f");
     33  aStr.ReplaceSubstring(u"\u00A0", u" ");
     34  for (char16_t ch = 0; ch <= 0x20; ch++) {
     35    aStr.ReplaceSubstring(
     36        nsDependentSubstring(&ch, 1),
     37        NS_ConvertASCIItoUTF16(nsPrintfCString("&#x%X04", ch)));
     38  }
     39 }
     40 
     41 static void MakeHumanFriendly(nsAutoCString& aStr) {
     42  aStr.ReplaceSubstring("\n", "\\n");
     43  aStr.ReplaceSubstring("\r", "\\r");
     44  aStr.ReplaceSubstring("\t", "\\t");
     45  aStr.ReplaceSubstring("\f", "\\f");
     46  aStr.ReplaceSubstring("\u00A0", "&nbsp;");
     47  for (char ch = 0; ch <= 0x20; ch++) {
     48    aStr.ReplaceSubstring(nsDependentCSubstring(&ch, 1),
     49                          nsPrintfCString("&#x%X04", ch));
     50  }
     51 }
     52 
     53 class NodeToString : public nsAutoCString {
     54 public:
     55  explicit NodeToString(const nsINode* aNode) {
     56    if (!aNode) {
     57      Assign("null");
     58      return;
     59    }
     60    if (const dom::CharacterData* const characterData =
     61            dom::CharacterData::FromNode(aNode)) {
     62      nsAutoString data;
     63      characterData->AppendTextTo(data);
     64      if (data.Length() > 10) {
     65        data.Truncate(10);
     66        data.Append(u"...");
     67      }
     68      MakeHumanFriendly(data);
     69      Assign(nsPrintfCString("%s, data=\"%s\" (length=%zu)",
     70                             ToString(*characterData).c_str(),
     71                             NS_ConvertUTF16toUTF8(data).get(), data.Length()));
     72      return;
     73    }
     74    Assign(ToString(*aNode).c_str());
     75  }
     76 };
     77 
     78 class MarkSelectionAndShrinkLongString : public nsAutoString {
     79 public:
     80  MarkSelectionAndShrinkLongString(const nsAutoString& aString,
     81                                   uint32_t aStartOffset, uint32_t aEndOffset)
     82      : nsAutoString(aString) {
     83    if (aStartOffset <= aString.Length() && aEndOffset <= aString.Length() &&
     84        aStartOffset <= aEndOffset) {
     85      Insert(u']', aEndOffset);
     86      Insert(u'[', aStartOffset);
     87      if (aString.Length() > 30) {
     88        if (aEndOffset + 10 <= Length()) {
     89          Replace(aEndOffset + 6, Length(), u"...");
     90        }
     91        if (aStartOffset > 8) {
     92          Replace(0, aStartOffset - 5, u"...");
     93        }
     94      }
     95    } else if (aString.Length() > 30) {
     96      Truncate(30);
     97      Append(u"...");
     98    }
     99  }
    100 };
    101 
    102 /**
    103 * The base class of wrappers of DOM API which modifies the DOM.  Editor should
    104 * update DOM via the following classes unless the node has not been connected
    105 * to any document yet.
    106 */
    107 class MOZ_STACK_CLASS AutoDOMAPIWrapperBase {
    108 protected:
    109  using CharacterData = dom::CharacterData;
    110  using Element = dom::Element;
    111 
    112 public:
    113  // This method is available only while a subclass is calling a DOM API.
    114  [[nodiscard]] virtual bool IsExpectedContentAppended(
    115      nsIContent* aFirstNewContent) const {
    116    return false;
    117  }
    118  // This method is available only while a subclass is calling a DOM API.
    119  [[nodiscard]] virtual bool IsExpectedContentInserted(
    120      nsIContent* aChild) const {
    121    return false;
    122  }
    123  // This method is available only while a subclass is calling a DOM API.
    124  [[nodiscard]] virtual bool IsExpectedContentWillBeRemoved(
    125      nsIContent* aChild) const {
    126    return false;
    127  }
    128  // This method is available only while a subclass is calling a DOM API.
    129  [[nodiscard]] virtual bool IsExpectedAttributeChanged(
    130      Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
    131      AttrModType aModType, const nsAttrValue* aOldValue) const {
    132    return false;
    133  }
    134  // This method is available only while a subclass is calling a DOM API.
    135  [[nodiscard]] virtual bool IsExpectedCharacterDataChanged(
    136      nsIContent* aContent, const CharacterDataChangeInfo& aInfo) const {
    137    return false;
    138  }
    139 
    140  enum class DOMAPI {
    141    // AutoNodeAPIWrapper
    142    nsINode_AppendChild,
    143    nsINode_InsertBefore,
    144    nsINode_Remove,
    145    nsINode_RemoveChild,
    146    // AutoElementAttrAPIWrapper
    147    Element_SetAttr,
    148    Element_UnsetAttr,
    149    // AutoCharacterDataAPIWrapper
    150    CharacterData_DeleteData,
    151    CharacterData_InsertData,
    152    CharacterData_ReplaceData,
    153    CharacterData_SetData,
    154    // AutoCSSDeclarationAPIWrapper
    155    CSSDeclaration_SetProperty,
    156    CSSDeclaration_RemoveProperty,
    157  };
    158 
    159  friend std::ostream& operator<<(std::ostream& aStream, DOMAPI aType) {
    160    switch (aType) {
    161      case DOMAPI::nsINode_AppendChild:
    162        return aStream << "nsINode::AppendChild";
    163      case DOMAPI::nsINode_InsertBefore:
    164        return aStream << "nsINode::InsertBefore";
    165      case DOMAPI::nsINode_Remove:
    166        return aStream << "nsINode::Remove";
    167      case DOMAPI::nsINode_RemoveChild:
    168        return aStream << "nsINode::RemoveChild";
    169      case DOMAPI::Element_SetAttr:
    170        return aStream << "Element::SetAttr";
    171      case DOMAPI::Element_UnsetAttr:
    172        return aStream << "Element::UnsetAttr";
    173      case DOMAPI::CharacterData_DeleteData:
    174        return aStream << "CharacterData::DeleteData";
    175      case DOMAPI::CharacterData_InsertData:
    176        return aStream << "CharacterData::InsertData";
    177      case DOMAPI::CharacterData_ReplaceData:
    178        return aStream << "CharacterData::ReplaceData";
    179      case DOMAPI::CharacterData_SetData:
    180        return aStream << "CharacterData::SetData";
    181      case DOMAPI::CSSDeclaration_SetProperty:
    182        return aStream << "nsICSSDeclaration::SetProperty";
    183      case DOMAPI::CSSDeclaration_RemoveProperty:
    184        return aStream << "nsICSSDeclaration::DeleteProperty";
    185      default:
    186        MOZ_ASSERT_UNREACHABLE("Invalid DOMAPI value");
    187        return aStream << "<invalid value>";
    188    }
    189  }
    190 
    191  [[nodiscard]] DOMAPI Type() const { return *mType; }
    192 
    193 #ifdef DEBUG
    194  virtual ~AutoDOMAPIWrapperBase() { MOZ_ASSERT(mType.isSome()); }
    195 #endif
    196 
    197 protected:
    198  MOZ_CAN_RUN_SCRIPT explicit AutoDOMAPIWrapperBase(EditorBase& aEditorBase)
    199      : mEditorBase(aEditorBase) {}
    200 
    201  class MOZ_STACK_CLASS AutoNotifyEditorOfAPICall final {
    202   public:
    203    MOZ_CAN_RUN_SCRIPT AutoNotifyEditorOfAPICall(AutoDOMAPIWrapperBase& aBase,
    204                                                 DOMAPI aCallingAPI)
    205        : mBase(aBase) {
    206      aBase.mType.emplace(aCallingAPI);
    207      if (HTMLEditor* const htmlEditor = mBase.mEditorBase.GetAsHTMLEditor()) {
    208        mPrevBase = htmlEditor->OnDOMAPICallStart(mBase);
    209      } else {
    210        mPrevBase = nullptr;
    211      }
    212    }
    213    ~AutoNotifyEditorOfAPICall() {
    214      if (HTMLEditor* const htmlEditor = mBase.mEditorBase.GetAsHTMLEditor()) {
    215        htmlEditor->OnDOMAPICallEnd(mPrevBase);
    216      }
    217    }
    218 
    219   private:
    220    const AutoDOMAPIWrapperBase& mBase;
    221    const AutoDOMAPIWrapperBase* mPrevBase;
    222  };
    223 
    224  friend std::ostream& operator<<(std::ostream& aStream,
    225                                  const AutoDOMAPIWrapperBase& aWrapperBase);
    226 
    227  MOZ_KNOWN_LIVE EditorBase& mEditorBase;
    228  Maybe<DOMAPI> mType;
    229 };
    230 
    231 /**
    232 * Wrapper class of nsINode::AppendChild, nsINode::InsertBefore, nsINode::Remove
    233 * and nsINode::RemoveChild.
    234 */
    235 class MOZ_STACK_CLASS AutoNodeAPIWrapper : public AutoDOMAPIWrapperBase {
    236 public:
    237  static AutoNodeAPIWrapper* FromBase(AutoDOMAPIWrapperBase* aBase) {
    238    switch (aBase->Type()) {
    239      case DOMAPI::nsINode_AppendChild:
    240      case DOMAPI::nsINode_InsertBefore:
    241      case DOMAPI::nsINode_Remove:
    242      case DOMAPI::nsINode_RemoveChild:
    243        return static_cast<AutoNodeAPIWrapper*>(aBase);
    244      default:
    245        return nullptr;
    246    }
    247  }
    248  static AutoNodeAPIWrapper* FromBaseOrNull(AutoDOMAPIWrapperBase* aBase) {
    249    return aBase ? FromBase(aBase) : nullptr;
    250  }
    251  static const AutoNodeAPIWrapper* FromBase(
    252      const AutoDOMAPIWrapperBase* aBase) {
    253    return FromBase(const_cast<AutoDOMAPIWrapperBase*>(aBase));
    254  }
    255  static const AutoNodeAPIWrapper* FromBaseOrNull(
    256      const AutoDOMAPIWrapperBase* aBase) {
    257    return FromBaseOrNull(const_cast<AutoDOMAPIWrapperBase*>(aBase));
    258  }
    259 
    260  MOZ_CAN_RUN_SCRIPT AutoNodeAPIWrapper(EditorBase& aEditorBase, nsINode& aNode)
    261      : AutoDOMAPIWrapperBase(aEditorBase), mNode(&aNode) {}
    262 
    263  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult AppendChild(nsIContent& aChild) {
    264    mChild = &aChild;
    265    AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::nsINode_AppendChild);
    266    IgnoredErrorResult error;
    267    MOZ_KnownLive(mNode)->AppendChild(aChild, error);
    268    error.WouldReportJSException();
    269    if (NS_WARN_IF(mEditorBase.Destroyed())) {
    270      return NS_ERROR_EDITOR_DESTROYED;
    271    }
    272    if (MOZ_UNLIKELY(error.Failed())) {
    273      NS_WARNING("nsINode::AppendChild() failed");
    274      return error.StealNSResult();
    275    }
    276    return NS_OK;
    277  }
    278 
    279  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
    280  InsertBefore(nsIContent& aChild, nsIContent* aReferenceChild) {
    281    mChild = &aChild;
    282    mReference = aReferenceChild;
    283    AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::nsINode_InsertBefore);
    284    IgnoredErrorResult error;
    285    MOZ_KnownLive(mNode)->InsertBefore(aChild, aReferenceChild, error);
    286    error.WouldReportJSException();
    287    if (NS_WARN_IF(mEditorBase.Destroyed())) {
    288      return NS_ERROR_EDITOR_DESTROYED;
    289    }
    290    if (MOZ_UNLIKELY(error.Failed())) {
    291      NS_WARNING("nsINode::InsertBefore() failed");
    292      return error.StealNSResult();
    293    }
    294    return NS_OK;
    295  }
    296 
    297  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult RemoveChild(nsIContent& aChild) {
    298    mChild = &aChild;
    299    AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::nsINode_RemoveChild);
    300    IgnoredErrorResult error;
    301    MOZ_KnownLive(mNode)->RemoveChild(aChild, error);
    302    error.WouldReportJSException();
    303    if (NS_WARN_IF(mEditorBase.Destroyed())) {
    304      return NS_ERROR_EDITOR_DESTROYED;
    305    }
    306    if (MOZ_UNLIKELY(error.Failed())) {
    307      NS_WARNING("nsINode::RemoveChild() failed");
    308      return error.StealNSResult();
    309    }
    310    return NS_OK;
    311  }
    312 
    313  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult Remove() {
    314    mChild = nsIContent::FromNode(mNode);
    315    MOZ_ASSERT(mChild);
    316    mNode = mChild->GetParentNode();
    317    AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::nsINode_Remove);
    318    MOZ_KnownLive(mChild)->Remove();
    319    if (NS_WARN_IF(mEditorBase.Destroyed())) {
    320      return NS_ERROR_EDITOR_DESTROYED;
    321    }
    322    return NS_OK;
    323  }
    324 
    325  [[nodiscard]] bool IsExpectedResult() const {
    326    switch (Type()) {
    327      case DOMAPI::nsINode_AppendChild:
    328      case DOMAPI::nsINode_InsertBefore:
    329        return mChild->GetParentNode() == mNode &&
    330               mChild->GetNextSibling() == mReference;
    331      case DOMAPI::nsINode_RemoveChild:
    332      case DOMAPI::nsINode_Remove:
    333        return !mChild->GetParentNode();
    334      default:
    335        MOZ_ASSERT_UNREACHABLE("Instantiated with wrong type?");
    336        return false;
    337    }
    338  }
    339 
    340  [[nodiscard]] bool IsExpectedContentAppended(
    341      nsIContent* aFirstNewContent) const override {
    342    return (Type() == DOMAPI::nsINode_AppendChild ||
    343            Type() == DOMAPI::nsINode_InsertBefore) &&
    344           aFirstNewContent == mChild && IsExpectedResult();
    345  }
    346  [[nodiscard]] bool IsExpectedContentInserted(
    347      nsIContent* aChild) const override {
    348    return (Type() == DOMAPI::nsINode_AppendChild ||
    349            Type() == DOMAPI::nsINode_InsertBefore) &&
    350           aChild == mChild && IsExpectedResult();
    351  }
    352  [[nodiscard]] bool IsExpectedContentWillBeRemoved(
    353      nsIContent* aChild) const override {
    354    if ((Type() == DOMAPI::nsINode_RemoveChild ||
    355         Type() == DOMAPI::nsINode_Remove) &&
    356        aChild == mChild && aChild->GetParentNode() == mNode) {
    357      return true;
    358    }
    359    return (Type() == DOMAPI::nsINode_AppendChild ||
    360            Type() == DOMAPI::nsINode_InsertBefore) &&
    361           aChild == mChild;
    362  }
    363 
    364  friend std::ostream& operator<<(std::ostream& aStream,
    365                                  const AutoNodeAPIWrapper& aWrapper) {
    366    aStream << aWrapper.Type() << "(";
    367    switch (aWrapper.Type()) {
    368      case DOMAPI::nsINode_AppendChild:
    369        aStream << "parent: " << NodeToString(aWrapper.mNode).get()
    370                << ", new child: " << NodeToString(aWrapper.mChild).get();
    371        break;
    372      case DOMAPI::nsINode_InsertBefore:
    373        aStream << "parent: " << NodeToString(aWrapper.mNode).get()
    374                << ", new child: " << NodeToString(aWrapper.mChild).get()
    375                << ", reference node: "
    376                << NodeToString(aWrapper.mReference).get();
    377        break;
    378      case DOMAPI::nsINode_Remove:
    379      case DOMAPI::nsINode_RemoveChild:
    380        aStream << "parent: " << NodeToString(aWrapper.mNode).get()
    381                << ", removing node: " << NodeToString(aWrapper.mChild).get();
    382        break;
    383      default:
    384        break;
    385    }
    386    return aStream << ")";
    387  }
    388 
    389 protected:
    390  // nullptr if nsINode::Remove() is called when no parent.
    391  nsINode* mNode;
    392  nsIContent* mChild = nullptr;
    393  nsIContent* mReference = nullptr;
    394 };
    395 
    396 /**
    397 * Wrapper class of Element::SetAttr and Element::UnsetAttr.
    398 */
    399 class MOZ_STACK_CLASS AutoElementAttrAPIWrapper : public AutoDOMAPIWrapperBase {
    400 public:
    401  static AutoElementAttrAPIWrapper* FromBase(AutoDOMAPIWrapperBase* aBase) {
    402    switch (aBase->Type()) {
    403      case DOMAPI::Element_SetAttr:
    404      case DOMAPI::Element_UnsetAttr:
    405        return static_cast<AutoElementAttrAPIWrapper*>(aBase);
    406      default:
    407        return nullptr;
    408    }
    409  }
    410  static AutoElementAttrAPIWrapper* FromBaseOrNull(
    411      AutoDOMAPIWrapperBase* aBase) {
    412    return aBase ? FromBase(aBase) : nullptr;
    413  }
    414  static const AutoElementAttrAPIWrapper* FromBase(
    415      const AutoDOMAPIWrapperBase* aBase) {
    416    return FromBase(const_cast<AutoDOMAPIWrapperBase*>(aBase));
    417  }
    418  static const AutoElementAttrAPIWrapper* FromBaseOrNull(
    419      const AutoDOMAPIWrapperBase* aBase) {
    420    return FromBaseOrNull(const_cast<AutoDOMAPIWrapperBase*>(aBase));
    421  }
    422 
    423  MOZ_CAN_RUN_SCRIPT AutoElementAttrAPIWrapper(EditorBase& aEditorBase,
    424                                               Element& aElement)
    425      : AutoDOMAPIWrapperBase(aEditorBase), mElement(aElement) {}
    426 
    427  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetAttr(nsAtom* aAttr,
    428                                                    const nsAString& aNewValue,
    429                                                    bool aNotify) {
    430    mAttr = aAttr;
    431    mNewValuePtr = &aNewValue;
    432    AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::Element_SetAttr);
    433    nsresult rv =
    434        mElement.SetAttr(kNameSpaceID_None, aAttr, aNewValue, aNotify);
    435    // Don't keep storing the pointer, nobody can guarantee the lifetime.
    436    mNewValuePtr = nullptr;
    437    NS_WARNING_ASSERTION(
    438        NS_SUCCEEDED(rv),
    439        nsPrintfCString(
    440            "Element::SetAttr(kNameSpaceID_None, %s, %s, %s) failed",
    441            nsAutoAtomCString(mAttr).get(),
    442            NS_ConvertUTF16toUTF8(aNewValue).get(), TrueOrFalse(aNotify))
    443            .get());
    444    return rv;
    445  }
    446 
    447  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult UnsetAttr(nsAtom* aAttr,
    448                                                      bool aNotify) {
    449    mAttr = aAttr;
    450    AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::Element_UnsetAttr);
    451    nsresult rv = mElement.UnsetAttr(kNameSpaceID_None, mAttr, aNotify);
    452    NS_WARNING_ASSERTION(
    453        NS_SUCCEEDED(rv),
    454        nsPrintfCString("Element::UnsetAttr(kNameSpaceID_None, %s, %s) failed",
    455                        nsAutoAtomCString(mAttr).get(), TrueOrFalse(aNotify))
    456            .get());
    457    return rv;
    458  }
    459 
    460  [[nodiscard]] bool IsExpectedResult(const nsAString& aExpectedValue) const {
    461    switch (Type()) {
    462      case DOMAPI::Element_SetAttr: {
    463        nsAutoString value;
    464        const bool hasAttr = mElement.GetAttr(kNameSpaceID_None, mAttr, value);
    465        return hasAttr && value == aExpectedValue;
    466      }
    467      case DOMAPI::Element_UnsetAttr:
    468        return !mElement.HasAttr(kNameSpaceID_None, mAttr);
    469      default:
    470        MOZ_ASSERT_UNREACHABLE("Instantiated with wrong type?");
    471        return false;
    472    }
    473  }
    474 
    475  [[nodiscard]] virtual bool IsExpectedAttributeChanged(
    476      Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
    477      AttrModType aModType, const nsAttrValue* aOldValue) const {
    478    switch (Type()) {
    479      case DOMAPI::Element_SetAttr:
    480        return IsAdditionOrModification(aModType) && aElement == &mElement &&
    481               aNameSpaceID == kNameSpaceID_None && aAttribute == mAttr &&
    482               mNewValuePtr && IsExpectedResult(*mNewValuePtr);
    483      case DOMAPI::Element_UnsetAttr:
    484        return aModType == AttrModType::Removal && aElement == &mElement &&
    485               aNameSpaceID == kNameSpaceID_None && aAttribute == mAttr;
    486      default:
    487        return false;
    488    }
    489  }
    490 
    491  friend std::ostream& operator<<(std::ostream& aStream,
    492                                  const AutoElementAttrAPIWrapper& aWrapper) {
    493    aStream << aWrapper.Type()
    494            << "(element: " << NodeToString(&aWrapper.mElement).get()
    495            << ", attr: " << nsAutoAtomCString(aWrapper.mAttr).get();
    496    switch (aWrapper.Type()) {
    497      case DOMAPI::Element_SetAttr: {
    498        MOZ_ASSERT(aWrapper.mNewValuePtr);
    499        nsAutoString newValue(aWrapper.mNewValuePtr ? *aWrapper.mNewValuePtr
    500                                                    : EmptyString());
    501        MakeHumanFriendly(newValue);
    502        aStream << ", new value=\"" << NS_ConvertUTF16toUTF8(newValue).get()
    503                << "\"";
    504        break;
    505      }
    506      case DOMAPI::Element_UnsetAttr:
    507      default:
    508        break;
    509    }
    510    return aStream << ")";
    511  }
    512 
    513 protected:
    514  MOZ_KNOWN_LIVE Element& mElement;
    515  nsAtom* mAttr = nullptr;
    516  // For avoiding to copy the string, we store the given string pointer only
    517  // while calling the API because it's enough to check whether checking
    518  // mutations are expected ones or not.
    519  const nsAString* mNewValuePtr = nullptr;
    520 };
    521 
    522 /**
    523 * Wrapper class of CharacterData::DeleteData, CharacterData::InsertData,
    524 * CharacterData::ReplaceData and CharacterData::SetData.
    525 */
    526 class MOZ_STACK_CLASS AutoCharacterDataAPIWrapper
    527    : public AutoDOMAPIWrapperBase {
    528 public:
    529  static AutoCharacterDataAPIWrapper* FromBase(AutoDOMAPIWrapperBase* aBase) {
    530    switch (aBase->Type()) {
    531      case DOMAPI::CharacterData_DeleteData:
    532      case DOMAPI::CharacterData_InsertData:
    533      case DOMAPI::CharacterData_ReplaceData:
    534      case DOMAPI::CharacterData_SetData:
    535        return static_cast<AutoCharacterDataAPIWrapper*>(aBase);
    536      default:
    537        return nullptr;
    538    }
    539  }
    540  static AutoCharacterDataAPIWrapper* FromBaseOrNull(
    541      AutoDOMAPIWrapperBase* aBase) {
    542    return aBase ? FromBase(aBase) : nullptr;
    543  }
    544  static const AutoCharacterDataAPIWrapper* FromBase(
    545      const AutoDOMAPIWrapperBase* aBase) {
    546    return FromBase(const_cast<AutoDOMAPIWrapperBase*>(aBase));
    547  }
    548  static const AutoCharacterDataAPIWrapper* FromBaseOrNull(
    549      const AutoDOMAPIWrapperBase* aBase) {
    550    return FromBaseOrNull(const_cast<AutoDOMAPIWrapperBase*>(aBase));
    551  }
    552 
    553  MOZ_CAN_RUN_SCRIPT AutoCharacterDataAPIWrapper(EditorBase& aEditorBase,
    554                                                 CharacterData& aNode)
    555      : AutoDOMAPIWrapperBase(aEditorBase), mCharacterData(aNode) {}
    556 
    557  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult DeleteData(uint32_t aOffset,
    558                                                       uint32_t aLength) {
    559    mOffset = aOffset;
    560    mReplaceLength = aLength;
    561    AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::CharacterData_DeleteData);
    562    IgnoredErrorResult error;
    563    mCharacterData.DeleteData(mOffset, mReplaceLength, error);
    564    if (NS_WARN_IF(mEditorBase.Destroyed())) {
    565      return NS_ERROR_EDITOR_DESTROYED;
    566    }
    567    if (MOZ_UNLIKELY(error.Failed())) {
    568      NS_WARNING("CharacterData::DeleteData() failed");
    569      return error.StealNSResult();
    570    }
    571    return NS_OK;
    572  }
    573 
    574  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InsertData(uint32_t aOffset,
    575                                                       const nsAString& aData) {
    576    mOffset = aOffset;
    577    mDataPtr = &aData;
    578    AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::CharacterData_InsertData);
    579    IgnoredErrorResult error;
    580    mCharacterData.InsertData(mOffset, aData, error);
    581    // Don't keep storing the pointer, nobody can guarantee the lifetime.
    582    mDataPtr = nullptr;
    583    if (NS_WARN_IF(mEditorBase.Destroyed())) {
    584      return NS_ERROR_EDITOR_DESTROYED;
    585    }
    586    if (MOZ_UNLIKELY(error.Failed())) {
    587      NS_WARNING("CharacterData::InsertData() failed");
    588      return error.StealNSResult();
    589    }
    590    return NS_OK;
    591  }
    592 
    593  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult ReplaceData(
    594      uint32_t aOffset, uint32_t aReplaceLength, const nsAString& aData) {
    595    mOffset = aOffset;
    596    mReplaceLength = aReplaceLength;
    597    mDataPtr = &aData;
    598    AutoNotifyEditorOfAPICall notifier(*this,
    599                                       DOMAPI::CharacterData_ReplaceData);
    600    IgnoredErrorResult error;
    601    mCharacterData.ReplaceData(mOffset, mReplaceLength, aData, error);
    602    // Don't keep storing the pointer, nobody can guarantee the lifetime.
    603    mDataPtr = nullptr;
    604    if (NS_WARN_IF(mEditorBase.Destroyed())) {
    605      return NS_ERROR_EDITOR_DESTROYED;
    606    }
    607    if (MOZ_UNLIKELY(error.Failed())) {
    608      NS_WARNING("CharacterData::ReplaceData() failed");
    609      return error.StealNSResult();
    610    }
    611    return NS_OK;
    612  }
    613 
    614  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetData(const nsAString& aData) {
    615    mDataPtr = &aData;
    616    AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::CharacterData_SetData);
    617    IgnoredErrorResult error;
    618    mCharacterData.SetData(aData, error);
    619    // Don't keep storing the pointer, nobody can guarantee the lifetime.
    620    mDataPtr = nullptr;
    621    if (NS_WARN_IF(mEditorBase.Destroyed())) {
    622      return NS_ERROR_EDITOR_DESTROYED;
    623    }
    624    if (MOZ_UNLIKELY(error.Failed())) {
    625      NS_WARNING("CharacterData::SetData() failed");
    626      return error.StealNSResult();
    627    }
    628    return NS_OK;
    629  }
    630 
    631  /**
    632   * Be aware, this may be too slow for the normal path.  This should be used
    633   * by debugging code like assertions or logging code.
    634   *
    635   * @param aExpectedData       Specify the data which you call an above API
    636   *                            calling method.
    637   */
    638  [[nodiscard]] bool IsExpectedResult(const nsAString& aExpectedData) const {
    639    switch (Type()) {
    640      case DOMAPI::CharacterData_DeleteData:
    641        // XXX We don't check whether the final data is expected one because
    642        // we need to store the original value or the expected value, but that
    643        // may require a big buffer if the text node has long text.
    644        return mCharacterData.TextDataLength() >= mOffset;
    645      case DOMAPI::CharacterData_InsertData:
    646      case DOMAPI::CharacterData_ReplaceData: {
    647        if (MOZ_UNLIKELY(mCharacterData.TextDataLength() <
    648                         mOffset + aExpectedData.Length())) {
    649          return false;
    650        }
    651        // Let's check only the new data is expected value.
    652        nsAutoString data;
    653        mCharacterData.GetData(data);
    654        return Substring(data, mOffset, aExpectedData.Length()) ==
    655               aExpectedData;
    656      }
    657      case DOMAPI::CharacterData_SetData: {
    658        if (MOZ_UNLIKELY(mCharacterData.TextDataLength() !=
    659                         aExpectedData.Length())) {
    660          return false;
    661        }
    662        // We can check strictly only in this case.  However, getting the
    663        // value may be slow if the text node has long text.
    664        nsAutoString data;
    665        mCharacterData.GetData(data);
    666        return data == aExpectedData;
    667      }
    668      default:
    669        MOZ_ASSERT_UNREACHABLE("Instantiated with wrong type?");
    670        return false;
    671    }
    672  }
    673 
    674  [[nodiscard]] virtual bool IsExpectedCharacterDataChanged(
    675      nsIContent* aContent, const CharacterDataChangeInfo& aInfo) const {
    676    return aContent == &mCharacterData && aInfo.mChangeStart == mOffset &&
    677           aInfo.LengthOfRemovedText() == mReplaceLength && mDataPtr &&
    678           aInfo.mReplaceLength == mDataPtr->Length() && !aInfo.mDetails &&
    679           IsExpectedResult(*mDataPtr);
    680  }
    681 
    682  friend std::ostream& operator<<(std::ostream& aStream,
    683                                  const AutoCharacterDataAPIWrapper& aWrapper) {
    684    nsAutoString data;
    685    aWrapper.mCharacterData.AppendTextTo(data);
    686    MarkSelectionAndShrinkLongString shrunkenData(
    687        data, aWrapper.mOffset, aWrapper.mOffset + aWrapper.mReplaceLength);
    688    MakeHumanFriendly(shrunkenData);
    689    aStream << aWrapper.Type() << "(node: " << aWrapper.mCharacterData
    690            << ", data=\"" << NS_ConvertUTF16toUTF8(shrunkenData).get()
    691            << "\" (length=" << data.Length()
    692            << "), offset: " << aWrapper.mOffset
    693            << ", replace length: " << aWrapper.mReplaceLength;
    694    switch (aWrapper.Type()) {
    695      case DOMAPI::CharacterData_DeleteData:
    696        break;
    697      case DOMAPI::CharacterData_InsertData:
    698      case DOMAPI::CharacterData_ReplaceData:
    699      case DOMAPI::CharacterData_SetData: {
    700        MOZ_ASSERT(aWrapper.mDataPtr);
    701        nsAutoString newData(aWrapper.mDataPtr ? *aWrapper.mDataPtr
    702                                               : EmptyString());
    703        MakeHumanFriendly(newData);
    704        aStream << ", inserting data=\"" << NS_ConvertUTF16toUTF8(newData).get()
    705                << "\" (length="
    706                << (aWrapper.mDataPtr ? aWrapper.mDataPtr->Length() : 0u)
    707                << ")";
    708        break;
    709      }
    710      default:
    711        break;
    712    }
    713    return aStream << ")";
    714  }
    715 
    716 protected:
    717  MOZ_KNOWN_LIVE CharacterData& mCharacterData;
    718  uint32_t mOffset = 0;
    719  uint32_t mReplaceLength = 0;
    720  // For avoiding to copy the string, we store the given string pointer only
    721  // while calling the API because it's enough to check whether checking
    722  // mutations are expected ones or not.
    723  const nsAString* mDataPtr = nullptr;
    724 };
    725 
    726 /**
    727 * Wrapper class of nsICSSDeclaration::SetProperty and
    728 * nsICSSDeclaration::RemoveProperty which modifies `style` attribute.
    729 */
    730 class MOZ_STACK_CLASS AutoCSSDeclarationAPIWrapper
    731    : public AutoDOMAPIWrapperBase {
    732 public:
    733  static AutoCSSDeclarationAPIWrapper* FromBase(AutoDOMAPIWrapperBase* aBase) {
    734    switch (aBase->Type()) {
    735      case DOMAPI::CSSDeclaration_SetProperty:
    736      case DOMAPI::CSSDeclaration_RemoveProperty:
    737        return static_cast<AutoCSSDeclarationAPIWrapper*>(aBase);
    738      default:
    739        return nullptr;
    740    }
    741  }
    742  static AutoCSSDeclarationAPIWrapper* FromBaseOrNull(
    743      AutoDOMAPIWrapperBase* aBase) {
    744    return aBase ? FromBase(aBase) : nullptr;
    745  }
    746  static const AutoCSSDeclarationAPIWrapper* FromBase(
    747      const AutoDOMAPIWrapperBase* aBase) {
    748    return FromBase(const_cast<AutoDOMAPIWrapperBase*>(aBase));
    749  }
    750  static const AutoCSSDeclarationAPIWrapper* FromBaseOrNull(
    751      const AutoDOMAPIWrapperBase* aBase) {
    752    return FromBaseOrNull(const_cast<AutoDOMAPIWrapperBase*>(aBase));
    753  }
    754 
    755  MOZ_CAN_RUN_SCRIPT AutoCSSDeclarationAPIWrapper(
    756      EditorBase& aEditorBase, nsStyledElement& aStyledElement,
    757      nsDOMCSSDeclaration* aDeclaration = nullptr)
    758      : AutoDOMAPIWrapperBase(aEditorBase),
    759        mStyledElement(aStyledElement),
    760        mCSSDeclaration(aDeclaration ? *aDeclaration
    761                                     : *aStyledElement.Style()) {}
    762 
    763  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
    764  SetProperty(const nsACString& aPropertyName, const nsACString& aValues,
    765              const nsACString& aPriority) {
    766    mPropertyNamePtr = &aPropertyName;
    767    mValuesPtr = &aValues;
    768    mPriorityPtr = &aPriority;
    769    AutoNotifyEditorOfAPICall notifier(*this,
    770                                       DOMAPI::CSSDeclaration_SetProperty);
    771    IgnoredErrorResult error;
    772    mCSSDeclaration->SetProperty(aPropertyName, aValues, aPriority, error);
    773    // Don't keep storing the pointers, nobody can guarantee the lifetime.
    774    mPropertyNamePtr = mValuesPtr = mPriorityPtr = nullptr;
    775    if (MOZ_UNLIKELY(error.Failed())) {
    776      NS_WARNING(
    777          nsPrintfCString("nsICSSDeclaration::SetProperty(\"%s\", \"%s\", "
    778                          "\"%s\") failed (mStyledElement=%s)",
    779                          PromiseFlatCString(aPropertyName).get(),
    780                          PromiseFlatCString(aValues).get(),
    781                          PromiseFlatCString(aPriority).get(),
    782                          ToString(mStyledElement).c_str())
    783              .get());
    784      return error.StealNSResult();
    785    }
    786    return NS_OK;
    787  }
    788 
    789  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
    790  RemoveProperty(const nsACString& aPropertyName) {
    791    mPropertyNamePtr = &aPropertyName;
    792    AutoNotifyEditorOfAPICall notifier(*this,
    793                                       DOMAPI::CSSDeclaration_RemoveProperty);
    794    IgnoredErrorResult error;
    795    mCSSDeclaration->RemoveProperty(aPropertyName, mRemovedValue, error);
    796    // Don't keep storing the pointers, nobody can guarantee the lifetime.
    797    mPropertyNamePtr = mValuesPtr = mPriorityPtr = nullptr;
    798    if (MOZ_UNLIKELY(error.Failed())) {
    799      NS_WARNING(
    800          nsPrintfCString("nsICSSDeclaration::RemoveProperty(\"%s\") failed "
    801                          "(mStyledElement=%s, removed value=\"%s\")",
    802                          PromiseFlatCString(aPropertyName).get(),
    803                          ToString(mStyledElement).c_str(), mRemovedValue.get())
    804              .get());
    805      return error.StealNSResult();
    806    }
    807    return NS_OK;
    808  }
    809 
    810  [[nodiscard]] const nsAutoCString& RemovedValueRef() const {
    811    MOZ_ASSERT(Type() == DOMAPI::CSSDeclaration_RemoveProperty);
    812    return mRemovedValue;
    813  }
    814 
    815  [[nodiscard]] virtual bool IsExpectedAttributeChanged(
    816      Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
    817      AttrModType aModType, const nsAttrValue* aOldValue) const {
    818    // XXX We don't check the style value is expected one because it requires
    819    // to store the original value and compute the expected new value.
    820    return aAttribute == nsGkAtoms::style &&
    821           aNameSpaceID == kNameSpaceID_None && aElement == &mStyledElement &&
    822           IsAdditionOrRemoval(aModType);
    823  }
    824 
    825  friend std::ostream& operator<<(
    826      std::ostream& aStream, const AutoCSSDeclarationAPIWrapper& aWrapper) {
    827    MOZ_ASSERT(aWrapper.mPropertyNamePtr);
    828    aStream << aWrapper.Type()
    829            << "(element: " << NodeToString(&aWrapper.mStyledElement).get()
    830            << ", property: \""
    831            << (aWrapper.mPropertyNamePtr
    832                    ? PromiseFlatCString(*aWrapper.mPropertyNamePtr).get()
    833                    : "")
    834            << "\"";
    835    switch (aWrapper.Type()) {
    836      case DOMAPI::CSSDeclaration_SetProperty: {
    837        MOZ_ASSERT(aWrapper.mValuesPtr);
    838        nsAutoCString values(aWrapper.mValuesPtr ? *aWrapper.mValuesPtr
    839                                                 : EmptyCString());
    840        MakeHumanFriendly(values);
    841        aStream << ", values=\"" << values.get() << "\", priority=\""
    842                << (aWrapper.mPriorityPtr
    843                        ? PromiseFlatCString(*aWrapper.mPriorityPtr).get()
    844                        : "")
    845                << "\"";
    846        break;
    847      }
    848      case DOMAPI::Element_UnsetAttr:
    849      default:
    850        break;
    851    }
    852    return aStream << ")";
    853  }
    854 
    855 protected:
    856  MOZ_KNOWN_LIVE nsStyledElement& mStyledElement;
    857  MOZ_KNOWN_LIVE const OwningNonNull<nsDOMCSSDeclaration> mCSSDeclaration;
    858  nsAutoCString mRemovedValue;
    859  // For avoiding to copy the strings, we store the given string pointers only
    860  // while calling the API because it's enough to check whether checking
    861  // mutations are expected ones or not.
    862  const nsACString* mPropertyNamePtr = nullptr;
    863  const nsACString* mValuesPtr = nullptr;
    864  const nsACString* mPriorityPtr = nullptr;
    865 };
    866 
    867 inline std::ostream& operator<<(std::ostream& aStream,
    868                                const AutoDOMAPIWrapperBase& aWrapperBase) {
    869  switch (aWrapperBase.Type()) {
    870    case AutoDOMAPIWrapperBase::DOMAPI::nsINode_AppendChild:
    871    case AutoDOMAPIWrapperBase::DOMAPI::nsINode_InsertBefore:
    872    case AutoDOMAPIWrapperBase::DOMAPI::nsINode_Remove:
    873    case AutoDOMAPIWrapperBase::DOMAPI::nsINode_RemoveChild: {
    874      const auto* runner = AutoNodeAPIWrapper::FromBase(&aWrapperBase);
    875      return aStream << *runner;
    876    }
    877    case AutoDOMAPIWrapperBase::DOMAPI::Element_SetAttr:
    878    case AutoDOMAPIWrapperBase::DOMAPI::Element_UnsetAttr: {
    879      const auto* runner = AutoElementAttrAPIWrapper::FromBase(&aWrapperBase);
    880      return aStream << *runner;
    881    }
    882    case AutoDOMAPIWrapperBase::DOMAPI::CharacterData_DeleteData:
    883    case AutoDOMAPIWrapperBase::DOMAPI::CharacterData_InsertData:
    884    case AutoDOMAPIWrapperBase::DOMAPI::CharacterData_ReplaceData:
    885    case AutoDOMAPIWrapperBase::DOMAPI::CharacterData_SetData: {
    886      const auto* runner = AutoCharacterDataAPIWrapper::FromBase(&aWrapperBase);
    887      return aStream << *runner;
    888    }
    889    case AutoDOMAPIWrapperBase::DOMAPI::CSSDeclaration_SetProperty:
    890    case AutoDOMAPIWrapperBase::DOMAPI::CSSDeclaration_RemoveProperty: {
    891      const auto* runner =
    892          AutoCSSDeclarationAPIWrapper::FromBase(&aWrapperBase);
    893      return aStream << *runner;
    894    }
    895    default:
    896      MOZ_ASSERT_UNREACHABLE("Invalid DOMAPI value");
    897      return aStream << "<The wrapper has invalid DOMAPI value>";
    898  }
    899 }
    900 
    901 }  // namespace mozilla
    902 
    903 #endif  // #ifndef EditorDOMAPIWrapper_h