/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 mozilla_TextEditor_h #define mozilla_TextEditor_h #include "mozilla/EditorBase.h" #include "nsCOMPtr.h" #include "nsCycleCollectionParticipant.h" #include "nsINamed.h" #include "nsISupportsImpl.h" #include "nsITimer.h" #include "nscore.h" class nsIContent; class nsIDocumentEncoder; class nsIOutputStream; class nsIPrincipal; class nsISelectionController; class nsITransferable; namespace mozilla { class DeleteNodeTransaction; class InsertNodeTransaction; enum class EditSubAction : int32_t; namespace dom { class DragEvent; class Selection; } // namespace dom /** * The text editor implementation. * Use to edit text document represented as a DOM tree. */ class TextEditor : public EditorBase, public nsITimerCallback, public nsINamed { public: /**************************************************************************** * NOTE: DO NOT MAKE YOUR NEW METHODS PUBLIC IF they are called by other * classes under libeditor except EditorEventListener and * HTMLEditorEventListener because each public method which may fire * eEditorInput event will need to instantiate new stack class for * managing input type value of eEditorInput and cache some objects * for smarter handling. In other words, when you add new root * method to edit the DOM tree, you can make your new method public. ****************************************************************************/ NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TextEditor, EditorBase) TextEditor(); NS_DECL_NSITIMERCALLBACK NS_DECL_NSINAMED // Overrides of nsIEditor NS_IMETHOD GetDocumentIsEmpty(bool* aDocumentIsEmpty) override; MOZ_CAN_RUN_SCRIPT NS_IMETHOD SetDocumentCharacterSet(const nsACString& characterSet) override; NS_IMETHOD GetTextLength(int32_t* aCount) override; /** * Do "undo" or "redo". * * @param aCount How many count of transactions should be * handled. * @param aPrincipal Set subject principal if it may be called by * JS. If set to nullptr, will be treated as * called by system. */ MOZ_CAN_RUN_SCRIPT nsresult UndoAsAction(uint32_t aCount, nsIPrincipal* aPrincipal = nullptr); MOZ_CAN_RUN_SCRIPT nsresult RedoAsAction(uint32_t aCount, nsIPrincipal* aPrincipal = nullptr); /** * Do "cut". * * @param aPrincipal If you know current context is subject * principal or system principal, set it. * When nullptr, this checks it automatically. */ MOZ_CAN_RUN_SCRIPT nsresult CutAsAction(nsIPrincipal* aPrincipal = nullptr); /** * IsCutCommandEnabled() returns whether cut command can be enabled or * disabled. This always returns true if we're in non-chrome HTML/XHTML * document. Otherwise, same as the result of `IsCopyToClipboardAllowed()`. */ bool IsCutCommandEnabled() const; NS_IMETHOD Copy() override; /** * IsCopyCommandEnabled() returns copy command can be enabled or disabled. * This always returns true if we're in non-chrome HTML/XHTML document. * Otherwise, same as the result of `IsCopyToClipboardAllowed()`. */ bool IsCopyCommandEnabled() const; /** * IsCopyToClipboardAllowed() returns true if the selected content can * be copied into the clipboard. This returns true when: * - `Selection` is not collapsed and we're not a password editor. * - `Selection` is not collapsed and we're a password editor but selection * range is in unmasked range. */ bool IsCopyToClipboardAllowed() const { AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); if (NS_WARN_IF(!editActionData.CanHandle())) { return false; } return IsCopyToClipboardAllowedInternal(); } /** * CanDeleteSelection() returns true if `Selection` is not collapsed and * it's allowed to be removed. */ bool CanDeleteSelection() const; virtual bool CanPaste(int32_t aClipboardType) const; // Shouldn't be used internally, but we need these using declarations for // avoiding warnings of clang. using EditorBase::CanCopy; using EditorBase::CanCut; using EditorBase::CanPaste; /** * Paste aTransferable at Selection. * * @param aTransferable Must not be nullptr. * @param aPrincipal Set subject principal if it may be called by * JS. If set to nullptr, will be treated as * called by system. */ MOZ_CAN_RUN_SCRIPT virtual nsresult PasteTransferableAsAction( nsITransferable* aTransferable, nsIPrincipal* aPrincipal = nullptr); NS_IMETHOD OutputToString(const nsAString& aFormatType, uint32_t aFlags, nsAString& aOutputString) override; /** Can we paste |aTransferable| or, if |aTransferable| is null, will a call * to pasteTransferable later possibly succeed if given an instance of * nsITransferable then? True if the doc is modifiable, and, if * |aTransfeable| is non-null, we have pasteable data in |aTransfeable|. */ virtual bool CanPasteTransferable(nsITransferable* aTransferable); // Overrides of EditorBase MOZ_CAN_RUN_SCRIPT virtual nsresult Init(Document& aDoc, Element* aRoot, nsISelectionController* aSelCon, uint32_t aFlags, const nsAString& aValue) override; /** * IsEmpty() checks whether the editor is empty. If editor has only padding *
element for empty editor, returns true. If editor's root element has * non-empty text nodes or other nodes like
, returns false. */ virtual bool IsEmpty() const; MOZ_CAN_RUN_SCRIPT virtual nsresult HandleKeyPressEvent( WidgetKeyboardEvent* aKeyboardEvent) override; virtual dom::EventTarget* GetDOMEventTarget() const override; /** * PasteAsAction() pastes clipboard content to Selection. This method * may dispatch ePaste event first. If its defaultPrevent() is called, * this does nothing but returns NS_OK. * * @param aClipboardType nsIClipboard::kGlobalClipboard or * nsIClipboard::kSelectionClipboard. * @param aDispatchPasteEvent true if this should dispatch ePaste event * before pasting. Otherwise, false. * @param aPrincipal Set subject principal if it may be called by * JS. If set to nullptr, will be treated as * called by system. */ MOZ_CAN_RUN_SCRIPT nsresult PasteAsAction(int32_t aClipboardType, bool aDispatchPasteEvent, nsIPrincipal* aPrincipal = nullptr); /** * PasteAsQuotationAsAction() pastes content in clipboard as quotation. * If the editor is TextEditor or in plaintext mode, will paste the content * with appending ">" to start of each line. * * @param aClipboardType nsIClipboard::kGlobalClipboard or * nsIClipboard::kSelectionClipboard. * @param aDispatchPasteEvent true if this should dispatch ePaste event * before pasting. Otherwise, false. * @param aPrincipal Set subject principal if it may be called by * JS. If set to nullptr, will be treated as * called by system. */ MOZ_CAN_RUN_SCRIPT virtual nsresult PasteAsQuotationAsAction( int32_t aClipboardType, bool aDispatchPasteEvent, nsIPrincipal* aPrincipal = nullptr); /** * The maximum number of characters allowed. * default: -1 (unlimited). */ int32_t MaxTextLength() const { return mMaxTextLength; } void SetMaxTextLength(int32_t aLength) { mMaxTextLength = aLength; } /** * Replace existed string with a string. * This is fast path to replace all string when using single line control. * * @param aString The string to be set * @param aPrincipal Set subject principal if it may be called by * JS. If set to nullptr, will be treated as * called by system. */ MOZ_CAN_RUN_SCRIPT nsresult SetTextAsAction(const nsAString& aString, nsIPrincipal* aPrincipal = nullptr); /** * Replace text in aReplaceRange or all text in this editor with aString and * treat the change as inserting the string. * * @param aString The string to set. * @param aReplaceRange The range to be replaced. * If nullptr, all contents will be replaced. * @param aPrincipal Set subject principal if it may be called by * JS. If set to nullptr, will be treated as * called by system. */ MOZ_CAN_RUN_SCRIPT nsresult ReplaceTextAsAction(const nsAString& aString, nsRange* aReplaceRange, nsIPrincipal* aPrincipal = nullptr); /** * InsertLineBreakAsAction() is called when user inputs a line break with * Enter or something. * * @param aPrincipal Set subject principal if it may be called by * JS. If set to nullptr, will be treated as * called by system. */ MOZ_CAN_RUN_SCRIPT virtual nsresult InsertLineBreakAsAction( nsIPrincipal* aPrincipal = nullptr); /** * OnCompositionStart() is called when editor receives eCompositionStart * event which should be handled in this editor. */ nsresult OnCompositionStart(WidgetCompositionEvent& aCompositionStartEvent); /** * OnCompositionChange() is called when editor receives an eCompositioChange * event which should be handled in this editor. * * @param aCompositionChangeEvent eCompositionChange event which should * be handled in this editor. */ MOZ_CAN_RUN_SCRIPT nsresult OnCompositionChange(WidgetCompositionEvent& aCompositionChangeEvent); /** * OnCompositionEnd() is called when editor receives an eCompositionChange * event and it's followed by eCompositionEnd event and after * OnCompositionChange() is called. */ MOZ_CAN_RUN_SCRIPT void OnCompositionEnd( WidgetCompositionEvent& aCompositionEndEvent); /** * OnDrop() is called from EditorEventListener::Drop that is handler of drop * event. */ MOZ_CAN_RUN_SCRIPT nsresult OnDrop(dom::DragEvent* aDropEvent); /** * ComputeTextValue() computes plaintext value of this editor. This may be * too expensive if it's in hot path. * * @param aDocumentEncoderFlags Flags of nsIDocumentEncoder. * @param aCharset Encoding of the document. */ nsresult ComputeTextValue(uint32_t aDocumentEncoderFlags, nsAString& aOutputString) const { AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_NOT_INITIALIZED; } nsresult rv = ComputeValueInternal(NS_LITERAL_STRING("text/plain"), aDocumentEncoderFlags, aOutputString); if (NS_WARN_IF(NS_FAILED(rv))) { return EditorBase::ToGenericNSResult(rv); } return NS_OK; } /** * The following methods are available only when the instance is a password * editor. They return whether there is unmasked range or not and range * start and length. */ bool IsAllMasked() const { MOZ_ASSERT(IsPasswordEditor()); return mUnmaskedStart == UINT32_MAX && mUnmaskedLength == 0; } uint32_t UnmaskedStart() const { MOZ_ASSERT(IsPasswordEditor()); return mUnmaskedStart; } uint32_t UnmaskedLength() const { MOZ_ASSERT(IsPasswordEditor()); return mUnmaskedLength; } uint32_t UnmaskedEnd() const { MOZ_ASSERT(IsPasswordEditor()); return mUnmaskedStart + mUnmaskedLength; } /** * IsMaskingPassword() returns false when the last caller of `Unmask()` * didn't want to mask again automatically. When this returns true, user * input causes masking the password even before timed-out. */ bool IsMaskingPassword() const { MOZ_ASSERT(IsPasswordEditor()); return mIsMaskingPassword; } /** * PasswordMask() returns a character which masks each character in password * fields. */ static char16_t PasswordMask(); protected: // May be called by friends. /**************************************************************************** * Some friend classes are allowed to call the following protected methods. * However, those methods won't prepare caches of some objects which are * necessary for them. So, if you call them from friend classes, you need * to make sure that AutoEditActionDataSetter is created. ****************************************************************************/ // Overrides of EditorBase MOZ_CAN_RUN_SCRIPT virtual nsresult RemoveAttributeOrEquivalent( Element* aElement, nsAtom* aAttribute, bool aSuppressTransaction) override; MOZ_CAN_RUN_SCRIPT virtual nsresult SetAttributeOrEquivalent( Element* aElement, nsAtom* aAttribute, const nsAString& aValue, bool aSuppressTransaction) override; using EditorBase::RemoveAttributeOrEquivalent; using EditorBase::SetAttributeOrEquivalent; /** * DeleteSelectionByDragAsAction() removes selection and dispatch "input" * event whose inputType is "deleteByDrag". */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult DeleteSelectionByDragAsAction(bool aDispatchInputEvent); /** * Replace existed string with aString. Caller must guarantee that there * is a placeholder transaction which will have the transaction. * * @ param aString The string to be set. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetTextAsSubAction(const nsAString& aString); /** * ReplaceSelectionAsSubAction() replaces selection with aString. * * @param aString The string to replace. */ MOZ_CAN_RUN_SCRIPT nsresult ReplaceSelectionAsSubAction(const nsAString& aString); /** * MaybeDoAutoPasswordMasking() may mask password if we're doing auto-masking. */ void MaybeDoAutoPasswordMasking() { if (IsPasswordEditor() && IsMaskingPassword()) { MaskAllCharacters(); } } /** * SetUnmaskRange() is available only when the instance is a password * editor. This just updates unmask range. I.e., caller needs to * guarantee to update the layout. * * @param aStart First index to show the character. * If aLength is 0, this value is ignored. * @param aLength Optional, Length to show characters. * If UINT32_MAX, it means unmasking all characters after * aStart. * If 0, it means that masking all characters. * @param aTimeout Optional, specify milliseconds to hide the unmasked * characters after this call. * If 0, it means this won't mask the characters * automatically. * If aLength is 0, this value is ignored. */ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult SetUnmaskRange( uint32_t aStart, uint32_t aLength = UINT32_MAX, uint32_t aTimeout = 0) { return SetUnmaskRangeInternal(aStart, aLength, aTimeout, false, false); } /** * SetUnmaskRangeAndNotify() is available only when the instance is a * password editor. This updates unmask range and notifying the text frame * to update the visible characters. * * @param aStart First index to show the character. * If UINT32_MAX, it means masking all. * @param aLength Optional, Length to show characters. * If UINT32_MAX, it means unmasking all characters after * aStart. * @param aTimeout Optional, specify milliseconds to hide the unmasked * characters after this call. * If 0, it means this won't mask the characters * automatically. * If aLength is 0, this value is ignored. */ MOZ_CAN_RUN_SCRIPT nsresult SetUnmaskRangeAndNotify( uint32_t aStart, uint32_t aLength = UINT32_MAX, uint32_t aTimeout = 0) { return SetUnmaskRangeInternal(aStart, aLength, aTimeout, true, false); } /** * MaskAllCharacters() is an alias of SetUnmaskRange() to mask all characters. * In other words, this removes existing unmask range. * After this is called, TextEditor starts masking password automatically. */ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult MaskAllCharacters() { return SetUnmaskRangeInternal(UINT32_MAX, 0, 0, false, true); } /** * MaskAllCharactersAndNotify() is an alias of SetUnmaskRangeAndNotify() to * mask all characters and notifies the text frame. In other words, this * removes existing unmask range. * After this is called, TextEditor starts masking password automatically. */ MOZ_CAN_RUN_SCRIPT nsresult MaskAllCharactersAndNotify() { return SetUnmaskRangeInternal(UINT32_MAX, 0, 0, true, true); } /** * WillDeleteText() is called before `DeleteTextTransaction` or something * removes text in a text node. Note that this won't be called if the * instance is `HTMLEditor` since supporting it makes the code complicated * due to mutation events. * * @param aCurrentLength Current text length of the node. * @param aRemoveStartOffset Start offset of the range to be removed. * @param aRemoveLength Length of the range to be removed. */ void WillDeleteText(uint32_t aCurrentLength, uint32_t aRemoveStartOffset, uint32_t aRemoveLength); /** * DidInsertText() is called after `InsertTextTransaction` or something * inserts text into a text node. Note that this won't be called if the * instance is `HTMLEditor` since supporting it makes the code complicated * due to mutatione events. * * @param aNewLength New text length after the insertion. * @param aInsertedOffset Start offset of the inserted text. * @param aInsertedLength Length of the inserted text. * @return NS_OK or NS_ERROR_EDITOR_DESTROYED. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult DidInsertText( uint32_t aNewLength, uint32_t aInsertedOffset, uint32_t aInsertedLength); protected: // edit sub-action handler /** * MaybeTruncateInsertionStringForMaxLength() truncates aInsertionString to * `maxlength` if it was not pasted in by the user. * * @param aInsertionString [in/out] New insertion string. This is * truncated to `maxlength` if it was not pasted in * by the user. * @return If aInsertionString is truncated, it returns "as * handled", else "as ignored." */ EditActionResult MaybeTruncateInsertionStringForMaxLength( nsAString& aInsertionString); /** * InsertLineFeedCharacterAtSelection() inserts a linefeed character at * selection. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult InsertLineFeedCharacterAtSelection(); /** * Handles the newline characters according to the default system prefs * (editor.singleLine.pasteNewlines). * Each value means: * nsIEditor::eNewlinesReplaceWithSpaces (2, Firefox default): * replace newlines with spaces. * nsIEditor::eNewlinesStrip (3): * remove newlines from the string. * nsIEditor::eNewlinesReplaceWithCommas (4, Thunderbird default): * replace newlines with commas. * nsIEditor::eNewlinesStripSurroundingWhitespace (5): * collapse newlines and surrounding white-space characters and * remove them from the string. * nsIEditor::eNewlinesPasteIntact (0): * only remove the leading and trailing newlines. * nsIEditor::eNewlinesPasteToFirst (1) or any other value: * remove the first newline and all characters following it. * * @param aString the string to be modified in place. */ void HandleNewLinesInStringForSingleLineEditor(nsString& aString) const; /** * HandleInsertText() handles inserting text at selection. * * @param aEditSubAction Must be EditSubAction::eInsertText or * EditSubAction::eInsertTextComingFromIME. * @param aInsertionString String to be inserted at selection. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual EditActionResult HandleInsertText( EditSubAction aEditSubAction, const nsAString& aInsertionString); /** * HandleDeleteSelectionInternal() is a helper method of * HandleDeleteSelection(). Must be called only when the instance is * TextEditor. * NOTE: This method creates SelectionBatcher. Therefore, each caller * needs to check if the editor is still available even if this returns * NS_OK. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult HandleDeleteSelectionInternal(nsIEditor::EDirection aDirectionAndAmount, nsIEditor::EStripWrappers aStripWrappers); /** * This method handles "delete selection" commands. * * @param aDirectionAndAmount Direction of the deletion. * @param aStripWrappers Must be nsIEditor::eNoStrip. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult HandleDeleteSelection(nsIEditor::EDirection aDirectionAndAmount, nsIEditor::EStripWrappers aStripWrappers) override; /** * ComputeValueFromTextNodeAndPaddingBRElement() tries to compute "value" of * this editor content only with text node and padding `
` element. * If this succeeds to compute the value, it's returned with aValue and * the result is marked as "handled". Otherwise, the caller needs to * compute it with another way. */ EditActionResult ComputeValueFromTextNodeAndPaddingBRElement( nsAString& aValue) const; /** * SetTextWithoutTransaction() is optimized method to set `.value` * and `