/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef EditorDOMAPIWrapper_h #define EditorDOMAPIWrapper_h #include "EditorBase.h" // for EditorBase #include "HTMLEditor.h" // for HTMLEditor #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/CharacterData.h" #include "mozilla/dom/Element.h" #include "nsAtom.h" #include "nsDOMCSSDeclaration.h" #include "nsIContent.h" #include "nsIMutationObserver.h" #include "nsINode.h" #include "nsPrintfCString.h" #include "nsString.h" #include "nsStyledElement.h" namespace mozilla { static void MakeHumanFriendly(nsAutoString& aStr) { aStr.ReplaceSubstring(u"\n", u"\\n"); aStr.ReplaceSubstring(u"\r", u"\\r"); aStr.ReplaceSubstring(u"\t", u"\\t"); aStr.ReplaceSubstring(u"\f", u"\\f"); aStr.ReplaceSubstring(u"\u00A0", u" "); for (char16_t ch = 0; ch <= 0x20; ch++) { aStr.ReplaceSubstring( nsDependentSubstring(&ch, 1), NS_ConvertASCIItoUTF16(nsPrintfCString("&#x%X04", ch))); } } static void MakeHumanFriendly(nsAutoCString& aStr) { aStr.ReplaceSubstring("\n", "\\n"); aStr.ReplaceSubstring("\r", "\\r"); aStr.ReplaceSubstring("\t", "\\t"); aStr.ReplaceSubstring("\f", "\\f"); aStr.ReplaceSubstring("\u00A0", " "); for (char ch = 0; ch <= 0x20; ch++) { aStr.ReplaceSubstring(nsDependentCSubstring(&ch, 1), nsPrintfCString("&#x%X04", ch)); } } class NodeToString : public nsAutoCString { public: explicit NodeToString(const nsINode* aNode) { if (!aNode) { Assign("null"); return; } if (const dom::CharacterData* const characterData = dom::CharacterData::FromNode(aNode)) { nsAutoString data; characterData->AppendTextTo(data); if (data.Length() > 10) { data.Truncate(10); data.Append(u"..."); } MakeHumanFriendly(data); Assign(nsPrintfCString("%s, data=\"%s\" (length=%zu)", ToString(*characterData).c_str(), NS_ConvertUTF16toUTF8(data).get(), data.Length())); return; } Assign(ToString(*aNode).c_str()); } }; class MarkSelectionAndShrinkLongString : public nsAutoString { public: MarkSelectionAndShrinkLongString(const nsAutoString& aString, uint32_t aStartOffset, uint32_t aEndOffset) : nsAutoString(aString) { if (aStartOffset <= aString.Length() && aEndOffset <= aString.Length() && aStartOffset <= aEndOffset) { Insert(u']', aEndOffset); Insert(u'[', aStartOffset); if (aString.Length() > 30) { if (aEndOffset + 10 <= Length()) { Replace(aEndOffset + 6, Length(), u"..."); } if (aStartOffset > 8) { Replace(0, aStartOffset - 5, u"..."); } } } else if (aString.Length() > 30) { Truncate(30); Append(u"..."); } } }; /** * The base class of wrappers of DOM API which modifies the DOM. Editor should * update DOM via the following classes unless the node has not been connected * to any document yet. */ class MOZ_STACK_CLASS AutoDOMAPIWrapperBase { protected: using CharacterData = dom::CharacterData; using Element = dom::Element; public: // This method is available only while a subclass is calling a DOM API. [[nodiscard]] virtual bool IsExpectedContentAppended( nsIContent* aFirstNewContent) const { return false; } // This method is available only while a subclass is calling a DOM API. [[nodiscard]] virtual bool IsExpectedContentInserted( nsIContent* aChild) const { return false; } // This method is available only while a subclass is calling a DOM API. [[nodiscard]] virtual bool IsExpectedContentWillBeRemoved( nsIContent* aChild) const { return false; } // This method is available only while a subclass is calling a DOM API. [[nodiscard]] virtual bool IsExpectedAttributeChanged( Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, AttrModType aModType, const nsAttrValue* aOldValue) const { return false; } // This method is available only while a subclass is calling a DOM API. [[nodiscard]] virtual bool IsExpectedCharacterDataChanged( nsIContent* aContent, const CharacterDataChangeInfo& aInfo) const { return false; } enum class DOMAPI { // AutoNodeAPIWrapper nsINode_AppendChild, nsINode_InsertBefore, nsINode_Remove, nsINode_RemoveChild, // AutoElementAttrAPIWrapper Element_SetAttr, Element_UnsetAttr, // AutoCharacterDataAPIWrapper CharacterData_DeleteData, CharacterData_InsertData, CharacterData_ReplaceData, CharacterData_SetData, // AutoCSSDeclarationAPIWrapper CSSDeclaration_SetProperty, CSSDeclaration_RemoveProperty, }; friend std::ostream& operator<<(std::ostream& aStream, DOMAPI aType) { switch (aType) { case DOMAPI::nsINode_AppendChild: return aStream << "nsINode::AppendChild"; case DOMAPI::nsINode_InsertBefore: return aStream << "nsINode::InsertBefore"; case DOMAPI::nsINode_Remove: return aStream << "nsINode::Remove"; case DOMAPI::nsINode_RemoveChild: return aStream << "nsINode::RemoveChild"; case DOMAPI::Element_SetAttr: return aStream << "Element::SetAttr"; case DOMAPI::Element_UnsetAttr: return aStream << "Element::UnsetAttr"; case DOMAPI::CharacterData_DeleteData: return aStream << "CharacterData::DeleteData"; case DOMAPI::CharacterData_InsertData: return aStream << "CharacterData::InsertData"; case DOMAPI::CharacterData_ReplaceData: return aStream << "CharacterData::ReplaceData"; case DOMAPI::CharacterData_SetData: return aStream << "CharacterData::SetData"; case DOMAPI::CSSDeclaration_SetProperty: return aStream << "nsICSSDeclaration::SetProperty"; case DOMAPI::CSSDeclaration_RemoveProperty: return aStream << "nsICSSDeclaration::DeleteProperty"; default: MOZ_ASSERT_UNREACHABLE("Invalid DOMAPI value"); return aStream << ""; } } [[nodiscard]] DOMAPI Type() const { return *mType; } #ifdef DEBUG virtual ~AutoDOMAPIWrapperBase() { MOZ_ASSERT(mType.isSome()); } #endif protected: MOZ_CAN_RUN_SCRIPT explicit AutoDOMAPIWrapperBase(EditorBase& aEditorBase) : mEditorBase(aEditorBase) {} class MOZ_STACK_CLASS AutoNotifyEditorOfAPICall final { public: MOZ_CAN_RUN_SCRIPT AutoNotifyEditorOfAPICall(AutoDOMAPIWrapperBase& aBase, DOMAPI aCallingAPI) : mBase(aBase) { aBase.mType.emplace(aCallingAPI); if (HTMLEditor* const htmlEditor = mBase.mEditorBase.GetAsHTMLEditor()) { mPrevBase = htmlEditor->OnDOMAPICallStart(mBase); } else { mPrevBase = nullptr; } } ~AutoNotifyEditorOfAPICall() { if (HTMLEditor* const htmlEditor = mBase.mEditorBase.GetAsHTMLEditor()) { htmlEditor->OnDOMAPICallEnd(mPrevBase); } } private: const AutoDOMAPIWrapperBase& mBase; const AutoDOMAPIWrapperBase* mPrevBase; }; friend std::ostream& operator<<(std::ostream& aStream, const AutoDOMAPIWrapperBase& aWrapperBase); MOZ_KNOWN_LIVE EditorBase& mEditorBase; Maybe mType; }; /** * Wrapper class of nsINode::AppendChild, nsINode::InsertBefore, nsINode::Remove * and nsINode::RemoveChild. */ class MOZ_STACK_CLASS AutoNodeAPIWrapper : public AutoDOMAPIWrapperBase { public: static AutoNodeAPIWrapper* FromBase(AutoDOMAPIWrapperBase* aBase) { switch (aBase->Type()) { case DOMAPI::nsINode_AppendChild: case DOMAPI::nsINode_InsertBefore: case DOMAPI::nsINode_Remove: case DOMAPI::nsINode_RemoveChild: return static_cast(aBase); default: return nullptr; } } static AutoNodeAPIWrapper* FromBaseOrNull(AutoDOMAPIWrapperBase* aBase) { return aBase ? FromBase(aBase) : nullptr; } static const AutoNodeAPIWrapper* FromBase( const AutoDOMAPIWrapperBase* aBase) { return FromBase(const_cast(aBase)); } static const AutoNodeAPIWrapper* FromBaseOrNull( const AutoDOMAPIWrapperBase* aBase) { return FromBaseOrNull(const_cast(aBase)); } MOZ_CAN_RUN_SCRIPT AutoNodeAPIWrapper(EditorBase& aEditorBase, nsINode& aNode) : AutoDOMAPIWrapperBase(aEditorBase), mNode(&aNode) {} [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult AppendChild(nsIContent& aChild) { mChild = &aChild; AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::nsINode_AppendChild); IgnoredErrorResult error; MOZ_KnownLive(mNode)->AppendChild(aChild, error); error.WouldReportJSException(); if (NS_WARN_IF(mEditorBase.Destroyed())) { return NS_ERROR_EDITOR_DESTROYED; } if (MOZ_UNLIKELY(error.Failed())) { NS_WARNING("nsINode::AppendChild() failed"); return error.StealNSResult(); } return NS_OK; } [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InsertBefore(nsIContent& aChild, nsIContent* aReferenceChild) { mChild = &aChild; mReference = aReferenceChild; AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::nsINode_InsertBefore); IgnoredErrorResult error; MOZ_KnownLive(mNode)->InsertBefore(aChild, aReferenceChild, error); error.WouldReportJSException(); if (NS_WARN_IF(mEditorBase.Destroyed())) { return NS_ERROR_EDITOR_DESTROYED; } if (MOZ_UNLIKELY(error.Failed())) { NS_WARNING("nsINode::InsertBefore() failed"); return error.StealNSResult(); } return NS_OK; } [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult RemoveChild(nsIContent& aChild) { mChild = &aChild; AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::nsINode_RemoveChild); IgnoredErrorResult error; MOZ_KnownLive(mNode)->RemoveChild(aChild, error); error.WouldReportJSException(); if (NS_WARN_IF(mEditorBase.Destroyed())) { return NS_ERROR_EDITOR_DESTROYED; } if (MOZ_UNLIKELY(error.Failed())) { NS_WARNING("nsINode::RemoveChild() failed"); return error.StealNSResult(); } return NS_OK; } [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult Remove() { mChild = nsIContent::FromNode(mNode); MOZ_ASSERT(mChild); mNode = mChild->GetParentNode(); AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::nsINode_Remove); MOZ_KnownLive(mChild)->Remove(); if (NS_WARN_IF(mEditorBase.Destroyed())) { return NS_ERROR_EDITOR_DESTROYED; } return NS_OK; } [[nodiscard]] bool IsExpectedResult() const { switch (Type()) { case DOMAPI::nsINode_AppendChild: case DOMAPI::nsINode_InsertBefore: return mChild->GetParentNode() == mNode && mChild->GetNextSibling() == mReference; case DOMAPI::nsINode_RemoveChild: case DOMAPI::nsINode_Remove: return !mChild->GetParentNode(); default: MOZ_ASSERT_UNREACHABLE("Instantiated with wrong type?"); return false; } } [[nodiscard]] bool IsExpectedContentAppended( nsIContent* aFirstNewContent) const override { return (Type() == DOMAPI::nsINode_AppendChild || Type() == DOMAPI::nsINode_InsertBefore) && aFirstNewContent == mChild && IsExpectedResult(); } [[nodiscard]] bool IsExpectedContentInserted( nsIContent* aChild) const override { return (Type() == DOMAPI::nsINode_AppendChild || Type() == DOMAPI::nsINode_InsertBefore) && aChild == mChild && IsExpectedResult(); } [[nodiscard]] bool IsExpectedContentWillBeRemoved( nsIContent* aChild) const override { if ((Type() == DOMAPI::nsINode_RemoveChild || Type() == DOMAPI::nsINode_Remove) && aChild == mChild && aChild->GetParentNode() == mNode) { return true; } return (Type() == DOMAPI::nsINode_AppendChild || Type() == DOMAPI::nsINode_InsertBefore) && aChild == mChild; } friend std::ostream& operator<<(std::ostream& aStream, const AutoNodeAPIWrapper& aWrapper) { aStream << aWrapper.Type() << "("; switch (aWrapper.Type()) { case DOMAPI::nsINode_AppendChild: aStream << "parent: " << NodeToString(aWrapper.mNode).get() << ", new child: " << NodeToString(aWrapper.mChild).get(); break; case DOMAPI::nsINode_InsertBefore: aStream << "parent: " << NodeToString(aWrapper.mNode).get() << ", new child: " << NodeToString(aWrapper.mChild).get() << ", reference node: " << NodeToString(aWrapper.mReference).get(); break; case DOMAPI::nsINode_Remove: case DOMAPI::nsINode_RemoveChild: aStream << "parent: " << NodeToString(aWrapper.mNode).get() << ", removing node: " << NodeToString(aWrapper.mChild).get(); break; default: break; } return aStream << ")"; } protected: // nullptr if nsINode::Remove() is called when no parent. nsINode* mNode; nsIContent* mChild = nullptr; nsIContent* mReference = nullptr; }; /** * Wrapper class of Element::SetAttr and Element::UnsetAttr. */ class MOZ_STACK_CLASS AutoElementAttrAPIWrapper : public AutoDOMAPIWrapperBase { public: static AutoElementAttrAPIWrapper* FromBase(AutoDOMAPIWrapperBase* aBase) { switch (aBase->Type()) { case DOMAPI::Element_SetAttr: case DOMAPI::Element_UnsetAttr: return static_cast(aBase); default: return nullptr; } } static AutoElementAttrAPIWrapper* FromBaseOrNull( AutoDOMAPIWrapperBase* aBase) { return aBase ? FromBase(aBase) : nullptr; } static const AutoElementAttrAPIWrapper* FromBase( const AutoDOMAPIWrapperBase* aBase) { return FromBase(const_cast(aBase)); } static const AutoElementAttrAPIWrapper* FromBaseOrNull( const AutoDOMAPIWrapperBase* aBase) { return FromBaseOrNull(const_cast(aBase)); } MOZ_CAN_RUN_SCRIPT AutoElementAttrAPIWrapper(EditorBase& aEditorBase, Element& aElement) : AutoDOMAPIWrapperBase(aEditorBase), mElement(aElement) {} [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetAttr(nsAtom* aAttr, const nsAString& aNewValue, bool aNotify) { mAttr = aAttr; mNewValuePtr = &aNewValue; AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::Element_SetAttr); nsresult rv = mElement.SetAttr(kNameSpaceID_None, aAttr, aNewValue, aNotify); // Don't keep storing the pointer, nobody can guarantee the lifetime. mNewValuePtr = nullptr; NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), nsPrintfCString( "Element::SetAttr(kNameSpaceID_None, %s, %s, %s) failed", nsAutoAtomCString(mAttr).get(), NS_ConvertUTF16toUTF8(aNewValue).get(), TrueOrFalse(aNotify)) .get()); return rv; } [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult UnsetAttr(nsAtom* aAttr, bool aNotify) { mAttr = aAttr; AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::Element_UnsetAttr); nsresult rv = mElement.UnsetAttr(kNameSpaceID_None, mAttr, aNotify); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), nsPrintfCString("Element::UnsetAttr(kNameSpaceID_None, %s, %s) failed", nsAutoAtomCString(mAttr).get(), TrueOrFalse(aNotify)) .get()); return rv; } [[nodiscard]] bool IsExpectedResult(const nsAString& aExpectedValue) const { switch (Type()) { case DOMAPI::Element_SetAttr: { nsAutoString value; const bool hasAttr = mElement.GetAttr(kNameSpaceID_None, mAttr, value); return hasAttr && value == aExpectedValue; } case DOMAPI::Element_UnsetAttr: return !mElement.HasAttr(kNameSpaceID_None, mAttr); default: MOZ_ASSERT_UNREACHABLE("Instantiated with wrong type?"); return false; } } [[nodiscard]] virtual bool IsExpectedAttributeChanged( Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, AttrModType aModType, const nsAttrValue* aOldValue) const { switch (Type()) { case DOMAPI::Element_SetAttr: return IsAdditionOrModification(aModType) && aElement == &mElement && aNameSpaceID == kNameSpaceID_None && aAttribute == mAttr && mNewValuePtr && IsExpectedResult(*mNewValuePtr); case DOMAPI::Element_UnsetAttr: return aModType == AttrModType::Removal && aElement == &mElement && aNameSpaceID == kNameSpaceID_None && aAttribute == mAttr; default: return false; } } friend std::ostream& operator<<(std::ostream& aStream, const AutoElementAttrAPIWrapper& aWrapper) { aStream << aWrapper.Type() << "(element: " << NodeToString(&aWrapper.mElement).get() << ", attr: " << nsAutoAtomCString(aWrapper.mAttr).get(); switch (aWrapper.Type()) { case DOMAPI::Element_SetAttr: { MOZ_ASSERT(aWrapper.mNewValuePtr); nsAutoString newValue(aWrapper.mNewValuePtr ? *aWrapper.mNewValuePtr : EmptyString()); MakeHumanFriendly(newValue); aStream << ", new value=\"" << NS_ConvertUTF16toUTF8(newValue).get() << "\""; break; } case DOMAPI::Element_UnsetAttr: default: break; } return aStream << ")"; } protected: MOZ_KNOWN_LIVE Element& mElement; nsAtom* mAttr = nullptr; // For avoiding to copy the string, we store the given string pointer only // while calling the API because it's enough to check whether checking // mutations are expected ones or not. const nsAString* mNewValuePtr = nullptr; }; /** * Wrapper class of CharacterData::DeleteData, CharacterData::InsertData, * CharacterData::ReplaceData and CharacterData::SetData. */ class MOZ_STACK_CLASS AutoCharacterDataAPIWrapper : public AutoDOMAPIWrapperBase { public: static AutoCharacterDataAPIWrapper* FromBase(AutoDOMAPIWrapperBase* aBase) { switch (aBase->Type()) { case DOMAPI::CharacterData_DeleteData: case DOMAPI::CharacterData_InsertData: case DOMAPI::CharacterData_ReplaceData: case DOMAPI::CharacterData_SetData: return static_cast(aBase); default: return nullptr; } } static AutoCharacterDataAPIWrapper* FromBaseOrNull( AutoDOMAPIWrapperBase* aBase) { return aBase ? FromBase(aBase) : nullptr; } static const AutoCharacterDataAPIWrapper* FromBase( const AutoDOMAPIWrapperBase* aBase) { return FromBase(const_cast(aBase)); } static const AutoCharacterDataAPIWrapper* FromBaseOrNull( const AutoDOMAPIWrapperBase* aBase) { return FromBaseOrNull(const_cast(aBase)); } MOZ_CAN_RUN_SCRIPT AutoCharacterDataAPIWrapper(EditorBase& aEditorBase, CharacterData& aNode) : AutoDOMAPIWrapperBase(aEditorBase), mCharacterData(aNode) {} [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult DeleteData(uint32_t aOffset, uint32_t aLength) { mOffset = aOffset; mReplaceLength = aLength; AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::CharacterData_DeleteData); IgnoredErrorResult error; mCharacterData.DeleteData(mOffset, mReplaceLength, error); if (NS_WARN_IF(mEditorBase.Destroyed())) { return NS_ERROR_EDITOR_DESTROYED; } if (MOZ_UNLIKELY(error.Failed())) { NS_WARNING("CharacterData::DeleteData() failed"); return error.StealNSResult(); } return NS_OK; } [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InsertData(uint32_t aOffset, const nsAString& aData) { mOffset = aOffset; mDataPtr = &aData; AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::CharacterData_InsertData); IgnoredErrorResult error; mCharacterData.InsertData(mOffset, aData, error); // Don't keep storing the pointer, nobody can guarantee the lifetime. mDataPtr = nullptr; if (NS_WARN_IF(mEditorBase.Destroyed())) { return NS_ERROR_EDITOR_DESTROYED; } if (MOZ_UNLIKELY(error.Failed())) { NS_WARNING("CharacterData::InsertData() failed"); return error.StealNSResult(); } return NS_OK; } [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult ReplaceData( uint32_t aOffset, uint32_t aReplaceLength, const nsAString& aData) { mOffset = aOffset; mReplaceLength = aReplaceLength; mDataPtr = &aData; AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::CharacterData_ReplaceData); IgnoredErrorResult error; mCharacterData.ReplaceData(mOffset, mReplaceLength, aData, error); // Don't keep storing the pointer, nobody can guarantee the lifetime. mDataPtr = nullptr; if (NS_WARN_IF(mEditorBase.Destroyed())) { return NS_ERROR_EDITOR_DESTROYED; } if (MOZ_UNLIKELY(error.Failed())) { NS_WARNING("CharacterData::ReplaceData() failed"); return error.StealNSResult(); } return NS_OK; } [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetData(const nsAString& aData) { mDataPtr = &aData; AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::CharacterData_SetData); IgnoredErrorResult error; mCharacterData.SetData(aData, error); // Don't keep storing the pointer, nobody can guarantee the lifetime. mDataPtr = nullptr; if (NS_WARN_IF(mEditorBase.Destroyed())) { return NS_ERROR_EDITOR_DESTROYED; } if (MOZ_UNLIKELY(error.Failed())) { NS_WARNING("CharacterData::SetData() failed"); return error.StealNSResult(); } return NS_OK; } /** * Be aware, this may be too slow for the normal path. This should be used * by debugging code like assertions or logging code. * * @param aExpectedData Specify the data which you call an above API * calling method. */ [[nodiscard]] bool IsExpectedResult(const nsAString& aExpectedData) const { switch (Type()) { case DOMAPI::CharacterData_DeleteData: // XXX We don't check whether the final data is expected one because // we need to store the original value or the expected value, but that // may require a big buffer if the text node has long text. return mCharacterData.TextDataLength() >= mOffset; case DOMAPI::CharacterData_InsertData: case DOMAPI::CharacterData_ReplaceData: { if (MOZ_UNLIKELY(mCharacterData.TextDataLength() < mOffset + aExpectedData.Length())) { return false; } // Let's check only the new data is expected value. nsAutoString data; mCharacterData.GetData(data); return Substring(data, mOffset, aExpectedData.Length()) == aExpectedData; } case DOMAPI::CharacterData_SetData: { if (MOZ_UNLIKELY(mCharacterData.TextDataLength() != aExpectedData.Length())) { return false; } // We can check strictly only in this case. However, getting the // value may be slow if the text node has long text. nsAutoString data; mCharacterData.GetData(data); return data == aExpectedData; } default: MOZ_ASSERT_UNREACHABLE("Instantiated with wrong type?"); return false; } } [[nodiscard]] virtual bool IsExpectedCharacterDataChanged( nsIContent* aContent, const CharacterDataChangeInfo& aInfo) const { return aContent == &mCharacterData && aInfo.mChangeStart == mOffset && aInfo.LengthOfRemovedText() == mReplaceLength && mDataPtr && aInfo.mReplaceLength == mDataPtr->Length() && !aInfo.mDetails && IsExpectedResult(*mDataPtr); } friend std::ostream& operator<<(std::ostream& aStream, const AutoCharacterDataAPIWrapper& aWrapper) { nsAutoString data; aWrapper.mCharacterData.AppendTextTo(data); MarkSelectionAndShrinkLongString shrunkenData( data, aWrapper.mOffset, aWrapper.mOffset + aWrapper.mReplaceLength); MakeHumanFriendly(shrunkenData); aStream << aWrapper.Type() << "(node: " << aWrapper.mCharacterData << ", data=\"" << NS_ConvertUTF16toUTF8(shrunkenData).get() << "\" (length=" << data.Length() << "), offset: " << aWrapper.mOffset << ", replace length: " << aWrapper.mReplaceLength; switch (aWrapper.Type()) { case DOMAPI::CharacterData_DeleteData: break; case DOMAPI::CharacterData_InsertData: case DOMAPI::CharacterData_ReplaceData: case DOMAPI::CharacterData_SetData: { MOZ_ASSERT(aWrapper.mDataPtr); nsAutoString newData(aWrapper.mDataPtr ? *aWrapper.mDataPtr : EmptyString()); MakeHumanFriendly(newData); aStream << ", inserting data=\"" << NS_ConvertUTF16toUTF8(newData).get() << "\" (length=" << (aWrapper.mDataPtr ? aWrapper.mDataPtr->Length() : 0u) << ")"; break; } default: break; } return aStream << ")"; } protected: MOZ_KNOWN_LIVE CharacterData& mCharacterData; uint32_t mOffset = 0; uint32_t mReplaceLength = 0; // For avoiding to copy the string, we store the given string pointer only // while calling the API because it's enough to check whether checking // mutations are expected ones or not. const nsAString* mDataPtr = nullptr; }; /** * Wrapper class of nsICSSDeclaration::SetProperty and * nsICSSDeclaration::RemoveProperty which modifies `style` attribute. */ class MOZ_STACK_CLASS AutoCSSDeclarationAPIWrapper : public AutoDOMAPIWrapperBase { public: static AutoCSSDeclarationAPIWrapper* FromBase(AutoDOMAPIWrapperBase* aBase) { switch (aBase->Type()) { case DOMAPI::CSSDeclaration_SetProperty: case DOMAPI::CSSDeclaration_RemoveProperty: return static_cast(aBase); default: return nullptr; } } static AutoCSSDeclarationAPIWrapper* FromBaseOrNull( AutoDOMAPIWrapperBase* aBase) { return aBase ? FromBase(aBase) : nullptr; } static const AutoCSSDeclarationAPIWrapper* FromBase( const AutoDOMAPIWrapperBase* aBase) { return FromBase(const_cast(aBase)); } static const AutoCSSDeclarationAPIWrapper* FromBaseOrNull( const AutoDOMAPIWrapperBase* aBase) { return FromBaseOrNull(const_cast(aBase)); } MOZ_CAN_RUN_SCRIPT AutoCSSDeclarationAPIWrapper( EditorBase& aEditorBase, nsStyledElement& aStyledElement, nsDOMCSSDeclaration* aDeclaration = nullptr) : AutoDOMAPIWrapperBase(aEditorBase), mStyledElement(aStyledElement), mCSSDeclaration(aDeclaration ? *aDeclaration : *aStyledElement.Style()) {} [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetProperty(const nsACString& aPropertyName, const nsACString& aValues, const nsACString& aPriority) { mPropertyNamePtr = &aPropertyName; mValuesPtr = &aValues; mPriorityPtr = &aPriority; AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::CSSDeclaration_SetProperty); IgnoredErrorResult error; mCSSDeclaration->SetProperty(aPropertyName, aValues, aPriority, error); // Don't keep storing the pointers, nobody can guarantee the lifetime. mPropertyNamePtr = mValuesPtr = mPriorityPtr = nullptr; if (MOZ_UNLIKELY(error.Failed())) { NS_WARNING( nsPrintfCString("nsICSSDeclaration::SetProperty(\"%s\", \"%s\", " "\"%s\") failed (mStyledElement=%s)", PromiseFlatCString(aPropertyName).get(), PromiseFlatCString(aValues).get(), PromiseFlatCString(aPriority).get(), ToString(mStyledElement).c_str()) .get()); return error.StealNSResult(); } return NS_OK; } [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult RemoveProperty(const nsACString& aPropertyName) { mPropertyNamePtr = &aPropertyName; AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::CSSDeclaration_RemoveProperty); IgnoredErrorResult error; mCSSDeclaration->RemoveProperty(aPropertyName, mRemovedValue, error); // Don't keep storing the pointers, nobody can guarantee the lifetime. mPropertyNamePtr = mValuesPtr = mPriorityPtr = nullptr; if (MOZ_UNLIKELY(error.Failed())) { NS_WARNING( nsPrintfCString("nsICSSDeclaration::RemoveProperty(\"%s\") failed " "(mStyledElement=%s, removed value=\"%s\")", PromiseFlatCString(aPropertyName).get(), ToString(mStyledElement).c_str(), mRemovedValue.get()) .get()); return error.StealNSResult(); } return NS_OK; } [[nodiscard]] const nsAutoCString& RemovedValueRef() const { MOZ_ASSERT(Type() == DOMAPI::CSSDeclaration_RemoveProperty); return mRemovedValue; } [[nodiscard]] virtual bool IsExpectedAttributeChanged( Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, AttrModType aModType, const nsAttrValue* aOldValue) const { // XXX We don't check the style value is expected one because it requires // to store the original value and compute the expected new value. return aAttribute == nsGkAtoms::style && aNameSpaceID == kNameSpaceID_None && aElement == &mStyledElement && IsAdditionOrRemoval(aModType); } friend std::ostream& operator<<( std::ostream& aStream, const AutoCSSDeclarationAPIWrapper& aWrapper) { MOZ_ASSERT(aWrapper.mPropertyNamePtr); aStream << aWrapper.Type() << "(element: " << NodeToString(&aWrapper.mStyledElement).get() << ", property: \"" << (aWrapper.mPropertyNamePtr ? PromiseFlatCString(*aWrapper.mPropertyNamePtr).get() : "") << "\""; switch (aWrapper.Type()) { case DOMAPI::CSSDeclaration_SetProperty: { MOZ_ASSERT(aWrapper.mValuesPtr); nsAutoCString values(aWrapper.mValuesPtr ? *aWrapper.mValuesPtr : EmptyCString()); MakeHumanFriendly(values); aStream << ", values=\"" << values.get() << "\", priority=\"" << (aWrapper.mPriorityPtr ? PromiseFlatCString(*aWrapper.mPriorityPtr).get() : "") << "\""; break; } case DOMAPI::Element_UnsetAttr: default: break; } return aStream << ")"; } protected: MOZ_KNOWN_LIVE nsStyledElement& mStyledElement; MOZ_KNOWN_LIVE const OwningNonNull mCSSDeclaration; nsAutoCString mRemovedValue; // For avoiding to copy the strings, we store the given string pointers only // while calling the API because it's enough to check whether checking // mutations are expected ones or not. const nsACString* mPropertyNamePtr = nullptr; const nsACString* mValuesPtr = nullptr; const nsACString* mPriorityPtr = nullptr; }; inline std::ostream& operator<<(std::ostream& aStream, const AutoDOMAPIWrapperBase& aWrapperBase) { switch (aWrapperBase.Type()) { case AutoDOMAPIWrapperBase::DOMAPI::nsINode_AppendChild: case AutoDOMAPIWrapperBase::DOMAPI::nsINode_InsertBefore: case AutoDOMAPIWrapperBase::DOMAPI::nsINode_Remove: case AutoDOMAPIWrapperBase::DOMAPI::nsINode_RemoveChild: { const auto* runner = AutoNodeAPIWrapper::FromBase(&aWrapperBase); return aStream << *runner; } case AutoDOMAPIWrapperBase::DOMAPI::Element_SetAttr: case AutoDOMAPIWrapperBase::DOMAPI::Element_UnsetAttr: { const auto* runner = AutoElementAttrAPIWrapper::FromBase(&aWrapperBase); return aStream << *runner; } case AutoDOMAPIWrapperBase::DOMAPI::CharacterData_DeleteData: case AutoDOMAPIWrapperBase::DOMAPI::CharacterData_InsertData: case AutoDOMAPIWrapperBase::DOMAPI::CharacterData_ReplaceData: case AutoDOMAPIWrapperBase::DOMAPI::CharacterData_SetData: { const auto* runner = AutoCharacterDataAPIWrapper::FromBase(&aWrapperBase); return aStream << *runner; } case AutoDOMAPIWrapperBase::DOMAPI::CSSDeclaration_SetProperty: case AutoDOMAPIWrapperBase::DOMAPI::CSSDeclaration_RemoveProperty: { const auto* runner = AutoCSSDeclarationAPIWrapper::FromBase(&aWrapperBase); return aStream << *runner; } default: MOZ_ASSERT_UNREACHABLE("Invalid DOMAPI value"); return aStream << ""; } } } // namespace mozilla #endif // #ifndef EditorDOMAPIWrapper_h