summaryrefslogtreecommitdiffstats
path: root/editor/libeditor/EditorBase.h
diff options
context:
space:
mode:
Diffstat (limited to 'editor/libeditor/EditorBase.h')
-rw-r--r--editor/libeditor/EditorBase.h2928
1 files changed, 2928 insertions, 0 deletions
diff --git a/editor/libeditor/EditorBase.h b/editor/libeditor/EditorBase.h
new file mode 100644
index 0000000000..fb78692071
--- /dev/null
+++ b/editor/libeditor/EditorBase.h
@@ -0,0 +1,2928 @@
+/* -*- 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_EditorBase_h
+#define mozilla_EditorBase_h
+
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
+#include "mozilla/EditAction.h" // for EditAction and EditSubAction
+#include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint
+#include "mozilla/EditorForwards.h"
+#include "mozilla/EventForwards.h" // for InputEventTargetRanges
+#include "mozilla/Likely.h" // for MOZ_UNLIKELY, MOZ_LIKELY
+#include "mozilla/Maybe.h" // for Maybe
+#include "mozilla/OwningNonNull.h" // for OwningNonNull
+#include "mozilla/PendingStyles.h" // for PendingStyle, PendingStyleCache
+#include "mozilla/RangeBoundary.h" // for RawRangeBoundary, RangeBoundary
+#include "mozilla/SelectionState.h" // for RangeUpdater, etc.
+#include "mozilla/StyleSheet.h" // for StyleSheet
+#include "mozilla/TransactionManager.h" // for TransactionManager
+#include "mozilla/WeakPtr.h" // for WeakPtr
+#include "mozilla/dom/DataTransfer.h" // for dom::DataTransfer
+#include "mozilla/dom/HTMLBRElement.h" // for dom::HTMLBRElement
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/Text.h"
+#include "nsAtom.h" // for nsAtom, nsStaticAtom
+#include "nsCOMPtr.h" // for already_AddRefed, nsCOMPtr
+#include "nsCycleCollectionParticipant.h"
+#include "nsGkAtoms.h"
+#include "nsIContentInlines.h" // for nsINode::IsEditable()
+#include "nsIEditor.h" // for nsIEditor, etc.
+#include "nsISelectionController.h" // for nsISelectionController constants
+#include "nsISelectionListener.h" // for nsISelectionListener
+#include "nsISupportsImpl.h" // for EditorBase::Release, etc.
+#include "nsIWeakReferenceUtils.h" // for nsWeakPtr
+#include "nsLiteralString.h" // for NS_LITERAL_STRING
+#include "nsPIDOMWindow.h" // for nsPIDOMWindowInner, etc.
+#include "nsString.h" // for nsCString
+#include "nsTArray.h" // for nsTArray and nsAutoTArray
+#include "nsWeakReference.h" // for nsSupportsWeakReference
+#include "nscore.h" // for nsresult, nsAString, etc.
+
+#include <tuple> // for std::tuple
+
+class mozInlineSpellChecker;
+class nsAtom;
+class nsCaret;
+class nsIContent;
+class nsIDocumentEncoder;
+class nsIDocumentStateListener;
+class nsIEditActionListener;
+class nsINode;
+class nsIPrincipal;
+class nsISupports;
+class nsITransferable;
+class nsITransaction;
+class nsIWidget;
+class nsRange;
+
+namespace mozilla {
+class AlignStateAtSelection;
+class AutoTransactionsConserveSelection;
+class AutoUpdateViewBatch;
+class ErrorResult;
+class IMEContentObserver;
+class ListElementSelectionState;
+class ListItemElementSelectionState;
+class ParagraphStateAtSelection;
+class PresShell;
+class TextComposition;
+class TextInputListener;
+class TextServicesDocument;
+namespace dom {
+class AbstractRange;
+class DataTransfer;
+class Document;
+class DragEvent;
+class Element;
+class EventTarget;
+class HTMLBRElement;
+} // namespace dom
+
+namespace widget {
+struct IMEState;
+} // namespace widget
+
+/**
+ * Implementation of an editor object. it will be the controller/focal point
+ * for the main editor services. i.e. the GUIManager, publishing, transaction
+ * manager, event interfaces. the idea for the event interfaces is to have them
+ * delegate the actual commands to the editor independent of the XPFE
+ * implementation.
+ */
+class EditorBase : public nsIEditor,
+ public nsISelectionListener,
+ public nsSupportsWeakReference {
+ 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.
+ ****************************************************************************/
+
+ using Document = dom::Document;
+ using Element = dom::Element;
+ using InterlinePosition = dom::Selection::InterlinePosition;
+ using Selection = dom::Selection;
+ using Text = dom::Text;
+
+ enum class EditorType { Text, HTML };
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(EditorBase, nsIEditor)
+
+ // nsIEditor methods
+ NS_DECL_NSIEDITOR
+
+ // nsISelectionListener method
+ NS_DECL_NSISELECTIONLISTENER
+
+ /**
+ * The default constructor. This should suffice. the setting of the
+ * interfaces is done after the construction of the editor class.
+ */
+ explicit EditorBase(EditorType aEditorType);
+
+ bool IsInitialized() const { return !!mDocument; }
+ bool Destroyed() const { return mDidPreDestroy; }
+
+ Document* GetDocument() const { return mDocument; }
+ nsPIDOMWindowOuter* GetWindow() const;
+ nsPIDOMWindowInner* GetInnerWindow() const;
+
+ /**
+ * MayHaveMutationEventListeners() returns true when the window may have
+ * mutation event listeners.
+ *
+ * @param aMutationEventType One or multiple of NS_EVENT_BITS_MUTATION_*.
+ * @return true if the editor is an HTMLEditor instance,
+ * and at least one of NS_EVENT_BITS_MUTATION_* is
+ * set to the window or in debug build.
+ */
+ bool MayHaveMutationEventListeners(
+ uint32_t aMutationEventType = 0xFFFFFFFF) const {
+ if (IsTextEditor()) {
+ // DOM mutation event listeners cannot catch the changes of
+ // <input type="text"> nor <textarea>.
+ return false;
+ }
+#ifdef DEBUG
+ // On debug build, this should always return true for testing complicated
+ // path without mutation event listeners because when mutation event
+ // listeners do not touch the DOM, editor needs to run as there is no
+ // mutation event listeners.
+ return true;
+#else // #ifdef DEBUG
+ nsPIDOMWindowInner* window = GetInnerWindow();
+ return window ? window->HasMutationListeners(aMutationEventType) : false;
+#endif // #ifdef DEBUG #else
+ }
+
+ /**
+ * MayHaveBeforeInputEventListenersForTelemetry() returns true when the
+ * window may have or have had one or more `beforeinput` event listeners.
+ * Note that this may return false even if there is a `beforeinput`.
+ * See nsPIDOMWindowInner::HasBeforeInputEventListenersForTelemetry()'s
+ * comment for the detail.
+ */
+ bool MayHaveBeforeInputEventListenersForTelemetry() const {
+ if (const nsPIDOMWindowInner* window = GetInnerWindow()) {
+ return window->HasBeforeInputEventListenersForTelemetry();
+ }
+ return false;
+ }
+
+ /**
+ * MutationObserverHasObservedNodeForTelemetry() returns true when a node in
+ * the window may have been observed by the web apps with a mutation observer
+ * (i.e., `MutationObserver.observe()` called by chrome script and addon's
+ * script does not make this returns true).
+ * Note that this may return false even if there is a node observed by
+ * a MutationObserver. See
+ * nsPIDOMWindowInner::MutationObserverHasObservedNodeForTelemetry()'s comment
+ * for the detail.
+ */
+ bool MutationObserverHasObservedNodeForTelemetry() const {
+ if (const nsPIDOMWindowInner* window = GetInnerWindow()) {
+ return window->MutationObserverHasObservedNodeForTelemetry();
+ }
+ return false;
+ }
+
+ PresShell* GetPresShell() const;
+ nsPresContext* GetPresContext() const;
+ already_AddRefed<nsCaret> GetCaret() const;
+
+ already_AddRefed<nsIWidget> GetWidget() const;
+
+ nsISelectionController* GetSelectionController() const;
+
+ nsresult GetSelection(SelectionType aSelectionType,
+ Selection** aSelection) const;
+
+ Selection* GetSelection(
+ SelectionType aSelectionType = SelectionType::eNormal) const {
+ if (aSelectionType == SelectionType::eNormal &&
+ IsEditActionDataAvailable()) {
+ return &SelectionRef();
+ }
+ nsISelectionController* sc = GetSelectionController();
+ if (!sc) {
+ return nullptr;
+ }
+ Selection* selection = sc->GetSelection(ToRawSelectionType(aSelectionType));
+ return selection;
+ }
+
+ /**
+ * Fast non-refcounting editor root element accessor
+ */
+ Element* GetRoot() const { return mRootElement; }
+
+ /**
+ * Likewise, but gets the text control element instead of the root for
+ * plaintext editors.
+ */
+ Element* GetExposedRoot() const;
+
+ /**
+ * Set or unset TextInputListener. If setting non-nullptr when the editor
+ * already has a TextInputListener, this will crash in debug build.
+ */
+ void SetTextInputListener(TextInputListener* aTextInputListener);
+
+ /**
+ * Set or unset IMEContentObserver. If setting non-nullptr when the editor
+ * already has an IMEContentObserver, this will crash in debug build.
+ */
+ void SetIMEContentObserver(IMEContentObserver* aIMEContentObserver);
+
+ /**
+ * Returns current composition.
+ */
+ TextComposition* GetComposition() const;
+
+ /**
+ * Get preferred IME status of current widget.
+ */
+ virtual nsresult GetPreferredIMEState(widget::IMEState* aState);
+
+ /**
+ * Returns true if there is composition string and not fixed.
+ */
+ bool IsIMEComposing() const;
+
+ /**
+ * Commit composition if there is.
+ * Note that when there is a composition, this requests to commit composition
+ * to native IME. Therefore, when there is composition, this can do anything.
+ * For example, the editor instance, the widget or the process itself may
+ * be destroyed.
+ */
+ nsresult CommitComposition();
+
+ /**
+ * ToggleTextDirection() toggles text-direction of the root element.
+ *
+ * @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
+ ToggleTextDirectionAsAction(nsIPrincipal* aPrincipal = nullptr);
+
+ /**
+ * SwitchTextDirectionTo() sets the text-direction of the root element to
+ * LTR or RTL.
+ */
+ enum class TextDirection {
+ eLTR,
+ eRTL,
+ };
+ MOZ_CAN_RUN_SCRIPT void SwitchTextDirectionTo(TextDirection aTextDirection);
+
+ /**
+ * Finalizes selection and caret for the editor.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult FinalizeSelection();
+
+ /**
+ * Returns true if selection is in an editable element and both the range
+ * start and the range end are editable. E.g., even if the selection range
+ * includes non-editable elements, returns true when one of common ancestors
+ * of the range start and the range end is editable. Otherwise, false.
+ */
+ bool IsSelectionEditable();
+
+ /**
+ * Returns number of undo or redo items.
+ */
+ size_t NumberOfUndoItems() const {
+ return mTransactionManager ? mTransactionManager->NumberOfUndoItems() : 0;
+ }
+ size_t NumberOfRedoItems() const {
+ return mTransactionManager ? mTransactionManager->NumberOfRedoItems() : 0;
+ }
+
+ /**
+ * Returns number of maximum undo/redo transactions.
+ */
+ int32_t NumberOfMaximumTransactions() const {
+ return mTransactionManager
+ ? mTransactionManager->NumberOfMaximumTransactions()
+ : 0;
+ }
+
+ /**
+ * Returns true if this editor can store transactions for undo/redo.
+ */
+ bool IsUndoRedoEnabled() const {
+ return mTransactionManager &&
+ mTransactionManager->NumberOfMaximumTransactions();
+ }
+
+ /**
+ * Return true if it's possible to undo/redo right now.
+ */
+ bool CanUndo() const {
+ return IsUndoRedoEnabled() && NumberOfUndoItems() > 0;
+ }
+ bool CanRedo() const {
+ return IsUndoRedoEnabled() && NumberOfRedoItems() > 0;
+ }
+
+ /**
+ * Enables or disables undo/redo feature. Returns true if it succeeded,
+ * otherwise, e.g., we're undoing or redoing, returns false.
+ */
+ bool EnableUndoRedo(int32_t aMaxTransactionCount = -1) {
+ if (!mTransactionManager) {
+ mTransactionManager = new TransactionManager();
+ }
+ return mTransactionManager->EnableUndoRedo(aMaxTransactionCount);
+ }
+ bool DisableUndoRedo() {
+ if (!mTransactionManager) {
+ return true;
+ }
+ return mTransactionManager->DisableUndoRedo();
+ }
+ bool ClearUndoRedo() {
+ if (!mTransactionManager) {
+ return true;
+ }
+ return mTransactionManager->ClearUndoRedo();
+ }
+
+ /**
+ * See Document::AreClipboardCommandsUnconditionallyEnabled.
+ */
+ bool AreClipboardCommandsUnconditionallyEnabled() const;
+
+ /**
+ * 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()`.
+ */
+ MOZ_CAN_RUN_SCRIPT bool IsCutCommandEnabled() const;
+
+ /**
+ * 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()`.
+ */
+ MOZ_CAN_RUN_SCRIPT 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();
+ }
+
+ /**
+ * HandleDropEvent() is called from EditorEventListener::Drop that is handler
+ * of drop event.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult HandleDropEvent(dom::DragEvent* aDropEvent);
+
+ MOZ_CAN_RUN_SCRIPT virtual nsresult HandleKeyPressEvent(
+ WidgetKeyboardEvent* aKeyboardEvent);
+
+ virtual dom::EventTarget* GetDOMEventTarget() const = 0;
+
+ /**
+ * 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);
+
+ /**
+ * Similar to the setter for wrapWidth, but just sets the editor
+ * internal state without actually changing the content being edited
+ * to wrap at that column. This should only be used by callers who
+ * are sure that their content is already set up correctly.
+ */
+ void SetWrapColumn(int32_t aWrapColumn) { mWrapColumn = aWrapColumn; }
+
+ /**
+ * Accessor methods to flags.
+ */
+ uint32_t Flags() const { return mFlags; }
+
+ MOZ_CAN_RUN_SCRIPT nsresult AddFlags(uint32_t aFlags) {
+ const uint32_t kOldFlags = Flags();
+ const uint32_t kNewFlags = (kOldFlags | aFlags);
+ if (kNewFlags == kOldFlags) {
+ return NS_OK;
+ }
+ return SetFlags(kNewFlags); // virtual call and may be expensive.
+ }
+ MOZ_CAN_RUN_SCRIPT nsresult RemoveFlags(uint32_t aFlags) {
+ const uint32_t kOldFlags = Flags();
+ const uint32_t kNewFlags = (kOldFlags & ~aFlags);
+ if (kNewFlags == kOldFlags) {
+ return NS_OK;
+ }
+ return SetFlags(kNewFlags); // virtual call and may be expensive.
+ }
+ MOZ_CAN_RUN_SCRIPT nsresult AddAndRemoveFlags(uint32_t aAddingFlags,
+ uint32_t aRemovingFlags) {
+ MOZ_ASSERT(!(aAddingFlags & aRemovingFlags),
+ "Same flags are specified both adding and removing");
+ const uint32_t kOldFlags = Flags();
+ const uint32_t kNewFlags = ((kOldFlags | aAddingFlags) & ~aRemovingFlags);
+ if (kNewFlags == kOldFlags) {
+ return NS_OK;
+ }
+ return SetFlags(kNewFlags); // virtual call and may be expensive.
+ }
+
+ bool IsInPlaintextMode() const {
+ const bool isPlaintextMode =
+ (mFlags & nsIEditor::eEditorPlaintextMask) != 0;
+ MOZ_ASSERT_IF(IsTextEditor(), isPlaintextMode);
+ return isPlaintextMode;
+ }
+
+ bool IsSingleLineEditor() const {
+ const bool isSingleLineEditor =
+ (mFlags & nsIEditor::eEditorSingleLineMask) != 0;
+ MOZ_ASSERT_IF(isSingleLineEditor, IsTextEditor());
+ return isSingleLineEditor;
+ }
+
+ bool IsPasswordEditor() const {
+ const bool isPasswordEditor =
+ (mFlags & nsIEditor::eEditorPasswordMask) != 0;
+ MOZ_ASSERT_IF(isPasswordEditor, IsTextEditor());
+ return isPasswordEditor;
+ }
+
+ // FYI: Both IsRightToLeft() and IsLeftToRight() may return false if
+ // the editor inherits the content node's direction.
+ bool IsRightToLeft() const {
+ return (mFlags & nsIEditor::eEditorRightToLeft) != 0;
+ }
+ bool IsLeftToRight() const {
+ return (mFlags & nsIEditor::eEditorLeftToRight) != 0;
+ }
+
+ bool IsReadonly() const {
+ return (mFlags & nsIEditor::eEditorReadonlyMask) != 0;
+ }
+
+ bool IsMailEditor() const {
+ return (mFlags & nsIEditor::eEditorMailMask) != 0;
+ }
+
+ bool IsWrapHackEnabled() const {
+ return (mFlags & nsIEditor::eEditorEnableWrapHackMask) != 0;
+ }
+
+ bool IsInteractionAllowed() const {
+ const bool isInteractionAllowed =
+ (mFlags & nsIEditor::eEditorAllowInteraction) != 0;
+ MOZ_ASSERT_IF(isInteractionAllowed, IsHTMLEditor());
+ return isInteractionAllowed;
+ }
+
+ bool ShouldSkipSpellCheck() const {
+ return (mFlags & nsIEditor::eEditorSkipSpellCheck) != 0;
+ }
+
+ bool HasIndependentSelection() const {
+ MOZ_ASSERT_IF(mSelectionController, IsTextEditor());
+ return !!mSelectionController;
+ }
+
+ bool IsModifiable() const { return !IsReadonly(); }
+
+ /**
+ * IsInEditSubAction() return true while the instance is handling an edit
+ * sub-action. Otherwise, false.
+ */
+ bool IsInEditSubAction() const { return mIsInEditSubAction; }
+
+ /**
+ * IsEmpty() checks whether the editor is empty. If editor has only padding
+ * <br> element for empty editor, returns true. If editor's root element has
+ * non-empty text nodes or other nodes like <br>, returns false.
+ */
+ virtual bool IsEmpty() const = 0;
+
+ /**
+ * SuppressDispatchingInputEvent() suppresses or unsuppresses dispatching
+ * "input" event.
+ */
+ void SuppressDispatchingInputEvent(bool aSuppress) {
+ mDispatchInputEvent = !aSuppress;
+ }
+
+ /**
+ * IsSuppressingDispatchingInputEvent() returns true if the editor stops
+ * dispatching input event. Otherwise, false.
+ */
+ bool IsSuppressingDispatchingInputEvent() const {
+ return !mDispatchInputEvent;
+ }
+
+ /**
+ * Returns true if markNodeDirty() has any effect. Returns false if
+ * markNodeDirty() is a no-op.
+ */
+ bool OutputsMozDirty() const {
+ // Return true for Composer (!IsInteractionAllowed()) or mail
+ // (IsMailEditor()), but false for webpages.
+ return !IsInteractionAllowed() || IsMailEditor();
+ }
+
+ /**
+ * Get the focused element, if we're focused. Returns null otherwise.
+ */
+ virtual Element* GetFocusedElement() const;
+
+ /**
+ * Whether the aGUIEvent should be handled by this editor or not. When this
+ * returns false, The aGUIEvent shouldn't be handled on this editor,
+ * i.e., The aGUIEvent should be handled by another inner editor or ancestor
+ * elements.
+ */
+ virtual bool IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) const;
+
+ /**
+ * FindSelectionRoot() returns a selection root of this editor when aNode
+ * gets focus. aNode must be a content node or a document node. When the
+ * target isn't a part of this editor, returns nullptr. If this is for
+ * designMode, you should set the document node to aNode except that an
+ * element in the document has focus.
+ */
+ [[nodiscard]] virtual Element* FindSelectionRoot(const nsINode& aNode) const;
+
+ /**
+ * OnFocus() is called when we get a focus event.
+ *
+ * @param aOriginalEventTargetNode The original event target node of the
+ * focus event.
+ */
+ MOZ_CAN_RUN_SCRIPT virtual nsresult OnFocus(
+ const nsINode& aOriginalEventTargetNode);
+
+ /**
+ * OnBlur() is called when we're blurred.
+ *
+ * @param aEventTarget The event target of the blur event.
+ */
+ virtual nsresult OnBlur(const dom::EventTarget* aEventTarget) = 0;
+
+ /** Resyncs spellchecking state (enabled/disabled). This should be called
+ * when anything that affects spellchecking state changes, such as the
+ * spellcheck attribute value.
+ */
+ void SyncRealTimeSpell();
+
+ /**
+ * 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);
+
+ /**
+ * CanPaste() returns true if user can paste something at current selection.
+ */
+ virtual bool CanPaste(int32_t aClipboardType) const = 0;
+
+ /**
+ * 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);
+
+ /**
+ * InsertTextAsAction() inserts aStringToInsert at selection.
+ * Although this method is implementation of nsIEditor.insertText(),
+ * this treats the input is an edit action. If you'd like to insert text
+ * as part of edit action, you probably should use InsertTextAsSubAction().
+ *
+ * @param aStringToInsert The string to insert.
+ * @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 InsertTextAsAction(
+ const nsAString& aStringToInsert, nsIPrincipal* aPrincipal = nullptr);
+
+ /**
+ * InsertLineBreakAsAction() is called when user inputs a line break with
+ * Enter or something. If the instance is `HTMLEditor`, this is called
+ * when Shift + Enter or "insertlinebreak" command.
+ *
+ * @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) = 0;
+
+ /**
+ * CanDeleteSelection() returns true if `Selection` is not collapsed and
+ * it's allowed to be removed.
+ */
+ bool CanDeleteSelection() const {
+ AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
+ if (NS_WARN_IF(!editActionData.CanHandle())) {
+ return false;
+ }
+ return IsModifiable() && !SelectionRef().IsCollapsed();
+ }
+
+ /**
+ * DeleteSelectionAsAction() removes selection content or content around
+ * caret with transactions. This should be used for handling it as an
+ * edit action. If you'd like to remove selection for preparing to insert
+ * something, you probably should use DeleteSelectionAsSubAction().
+ *
+ * @param aDirectionAndAmount How much range should be removed.
+ * @param aStripWrappers Whether the parent blocks should be removed
+ * when they become empty.
+ * @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
+ DeleteSelectionAsAction(nsIEditor::EDirection aDirectionAndAmount,
+ nsIEditor::EStripWrappers aStripWrappers,
+ nsIPrincipal* aPrincipal = nullptr);
+
+ enum class AllowBeforeInputEventCancelable {
+ No,
+ Yes,
+ };
+
+ /**
+ * 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.
+ * NOTE: Currently, nullptr is not allowed if
+ * the editor is an HTMLEditor.
+ * @param aAllowBeforeInputEventCancelable
+ * Whether `beforeinput` event which will be
+ * dispatched for this can be cancelable or not.
+ * @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,
+ AllowBeforeInputEventCancelable aAllowBeforeInputEventCancelable,
+ nsIPrincipal* aPrincipal = nullptr);
+
+ /**
+ * 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) = 0;
+
+ /**
+ * 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 virtual nsresult PasteAsAction(
+ int32_t aClipboardType, bool aDispatchPasteEvent,
+ nsIPrincipal* aPrincipal = nullptr) = 0;
+
+ /**
+ * 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) = 0;
+
+ /**
+ * 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.
+ * if the editor is HTMLEditor and is not in plaintext mode, will patste it
+ * into newly created blockquote element.
+ *
+ * @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) = 0;
+
+ protected: // May be used by friends.
+ class AutoEditActionDataSetter;
+
+ /**
+ * TopLevelEditSubActionData stores temporary data while we're handling
+ * top-level edit sub-action.
+ */
+ struct MOZ_STACK_CLASS TopLevelEditSubActionData final {
+ friend class AutoEditActionDataSetter;
+
+ // Set selected range before edit. Then, RangeUpdater keep modifying
+ // the range while we're changing the DOM tree.
+ RefPtr<RangeItem> mSelectedRange;
+
+ // Computing changed range while we're handling sub actions.
+ RefPtr<nsRange> mChangedRange;
+
+ // XXX In strict speaking, mCachedPendingStyles isn't enough to cache
+ // inline styles because inline style can be specified with "style"
+ // attribute and/or CSS in <style> elements or CSS files. So, we need
+ // to look for better implementation about this.
+ // FYI: Initialization cost of AutoPendingStyleCacheArray is expensive and
+ // it is not used by TextEditor so that we should construct it only
+ // when we're an HTMLEditor.
+ Maybe<AutoPendingStyleCacheArray> mCachedPendingStyles;
+
+ // If we tried to delete selection, set to true.
+ bool mDidDeleteSelection;
+
+ // If we have explicitly set selection inter line, set to true.
+ // `AfterEdit()` or something shouldn't overwrite it in such case.
+ bool mDidExplicitlySetInterLine;
+
+ // If we have deleted non-collapsed range set to true, there are only 2
+ // cases for now:
+ // - non-collapsed range was selected.
+ // - selection was collapsed in a text node and a Unicode character
+ // was removed.
+ bool mDidDeleteNonCollapsedRange;
+
+ // If we have deleted parent empty blocks, set to true.
+ bool mDidDeleteEmptyParentBlocks;
+
+ // If we're a contenteditable editor, we temporarily increase edit count
+ // of the document between `BeforeEdit()` and `AfterEdit()`. I.e., if
+ // we increased the count in `BeforeEdit()`, we need to decrease it in
+ // `AfterEdit()`, however, the document may be changed to designMode or
+ // non-editable. Therefore, we need to store with this whether we need
+ // to restore it.
+ bool mRestoreContentEditableCount;
+
+ // If we explicitly normalized whitespaces around the changed range,
+ // set to true.
+ bool mDidNormalizeWhitespaces;
+
+ // Set to true by default. If somebody inserts an HTML fragment
+ // intentionally, any empty elements shouldn't be cleaned up later. In the
+ // case this is set to false.
+ // TODO: We should not do this by default. If it's necessary, each edit
+ // action handler do it by itself instead. Then, we can avoid such
+ // unnecessary DOM tree scan.
+ bool mNeedsToCleanUpEmptyElements;
+
+ /**
+ * The following methods modifies some data of this struct and
+ * `EditSubActionData` struct. Currently, these are required only
+ * by `HTMLEditor`. Therefore, for cutting the runtime cost of
+ * `TextEditor`, these methods should be called only by `HTMLEditor`.
+ * But it's fine to use these methods in `TextEditor` if necessary.
+ * If so, you need to call `DidDeleteText()` and `DidInsertText()`
+ * from `SetTextNodeWithoutTransaction()`.
+ */
+ void DidCreateElement(EditorBase& aEditorBase, Element& aNewElement);
+ void DidInsertContent(EditorBase& aEditorBase, nsIContent& aNewContent);
+ void WillDeleteContent(EditorBase& aEditorBase,
+ nsIContent& aRemovingContent);
+ void DidSplitContent(EditorBase& aEditorBase, nsIContent& aSplitContent,
+ nsIContent& aNewContent,
+ SplitNodeDirection aSplitNodeDirection);
+ void DidJoinContents(EditorBase& aEditorBase,
+ const EditorRawDOMPoint& aJoinedPoint);
+ void DidInsertText(EditorBase& aEditorBase,
+ const EditorRawDOMPoint& aInsertionBegin,
+ const EditorRawDOMPoint& aInsertionEnd);
+ void DidDeleteText(EditorBase& aEditorBase,
+ const EditorRawDOMPoint& aStartInTextNode);
+ void WillDeleteRange(EditorBase& aEditorBase,
+ const EditorRawDOMPoint& aStart,
+ const EditorRawDOMPoint& aEnd);
+
+ private:
+ void Clear() {
+ mDidExplicitlySetInterLine = false;
+ // We don't need to clear other members which are referred only when the
+ // editor is an HTML editor anymore. Note that if `mSelectedRange` is
+ // non-nullptr, that means that we're in `HTMLEditor`.
+ if (!mSelectedRange) {
+ return;
+ }
+ mSelectedRange->Clear();
+ mChangedRange->Reset();
+ if (mCachedPendingStyles.isSome()) {
+ mCachedPendingStyles->Clear();
+ }
+ mDidDeleteSelection = false;
+ mDidDeleteNonCollapsedRange = false;
+ mDidDeleteEmptyParentBlocks = false;
+ mRestoreContentEditableCount = false;
+ mDidNormalizeWhitespaces = false;
+ mNeedsToCleanUpEmptyElements = true;
+ }
+
+ /**
+ * Extend mChangedRange to include `aNode`.
+ */
+ nsresult AddNodeToChangedRange(const HTMLEditor& aHTMLEditor,
+ nsINode& aNode);
+
+ /**
+ * Extend mChangedRange to include `aPoint`.
+ */
+ nsresult AddPointToChangedRange(const HTMLEditor& aHTMLEditor,
+ const EditorRawDOMPoint& aPoint);
+
+ /**
+ * Extend mChangedRange to include `aStart` and `aEnd`.
+ */
+ nsresult AddRangeToChangedRange(const HTMLEditor& aHTMLEditor,
+ const EditorRawDOMPoint& aStart,
+ const EditorRawDOMPoint& aEnd);
+
+ TopLevelEditSubActionData() = default;
+ TopLevelEditSubActionData(const TopLevelEditSubActionData& aOther) = delete;
+ };
+
+ struct MOZ_STACK_CLASS EditSubActionData final {
+ // While this is set to false, TopLevelEditSubActionData::mChangedRange
+ // shouldn't be modified since in some cases, modifying it in the setter
+ // itself may be faster. Note that we should affect this only for current
+ // edit sub action since mutation event listener may edit different range.
+ bool mAdjustChangedRangeFromListener;
+
+ private:
+ void Clear() { mAdjustChangedRangeFromListener = true; }
+
+ friend EditorBase;
+ };
+
+ protected: // AutoEditActionDataSetter, this shouldn't be accessed by friends.
+ /**
+ * SettingDataTransfer enum class is used to specify whether DataTransfer
+ * should be initialized with or without format. For example, when user
+ * uses Accel + Shift + V to paste text without format, DataTransfer should
+ * have only plain/text data to make web apps treat it without format.
+ */
+ enum class SettingDataTransfer {
+ eWithFormat,
+ eWithoutFormat,
+ };
+
+ /**
+ * AutoEditActionDataSetter grabs some necessary objects for handling any
+ * edit actions and store the edit action what we're handling. When this is
+ * created, its pointer is set to the mEditActionData, and this guarantees
+ * the lifetime of grabbing objects until it's destroyed.
+ */
+ class MOZ_STACK_CLASS AutoEditActionDataSetter final {
+ public:
+ // NOTE: aPrincipal will be used when we implement "beforeinput" event.
+ // It's set only when maybe we shouldn't dispatch it because of
+ // called by JS. I.e., if this is nullptr, we can always dispatch
+ // it.
+ AutoEditActionDataSetter(const EditorBase& aEditorBase,
+ EditAction aEditAction,
+ nsIPrincipal* aPrincipal = nullptr);
+ ~AutoEditActionDataSetter();
+
+ void SetSelectionCreatedByDoubleclick(bool aSelectionCreatedByDoubleclick) {
+ mSelectionCreatedByDoubleclick = aSelectionCreatedByDoubleclick;
+ }
+
+ [[nodiscard]] bool SelectionCreatedByDoubleclick() const {
+ return mSelectionCreatedByDoubleclick;
+ }
+
+ void UpdateEditAction(EditAction aEditAction) {
+ MOZ_ASSERT(!mHasTriedToDispatchBeforeInputEvent,
+ "It's too late to update EditAction since this may have "
+ "already dispatched a beforeinput event");
+ mEditAction = aEditAction;
+ }
+
+ /**
+ * CanHandle() or CanHandleAndHandleBeforeInput() must be called
+ * immediately after creating the instance. If caller does not need to
+ * handle "beforeinput" event or caller needs to set additional information
+ * the events later, use the former. Otherwise, use the latter. If caller
+ * uses the former, it's required to call MaybeDispatchBeforeInputEvent() by
+ * itself.
+ *
+ */
+ [[nodiscard]] bool CanHandle() const {
+#ifdef DEBUG
+ mHasCanHandleChecked = true;
+#endif // #ifdefn DEBUG
+ // Don't allow to run new edit action when an edit action caused
+ // destroying the editor while it's being handled.
+ if (mEditAction != EditAction::eInitializing &&
+ mEditorWasDestroyedDuringHandlingEditAction) {
+ NS_WARNING("Editor was destroyed during an edit action being handled");
+ return false;
+ }
+ return IsDataAvailable();
+ }
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ CanHandleAndMaybeDispatchBeforeInputEvent() {
+ if (MOZ_UNLIKELY(NS_WARN_IF(!CanHandle()))) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ nsresult rv = MaybeFlushPendingNotifications();
+ if (MOZ_UNLIKELY(NS_FAILED(rv))) {
+ return rv;
+ }
+ return MaybeDispatchBeforeInputEvent();
+ }
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ CanHandleAndFlushPendingNotifications() {
+ if (MOZ_UNLIKELY(NS_WARN_IF(!CanHandle()))) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ MOZ_ASSERT(MayEditActionRequireLayout(mRawEditAction));
+ return MaybeFlushPendingNotifications();
+ }
+
+ [[nodiscard]] bool IsDataAvailable() const {
+ return mSelection && mEditorBase.IsInitialized();
+ }
+
+ /**
+ * MaybeDispatchBeforeInputEvent() considers whether this instance needs to
+ * dispatch "beforeinput" event or not. Then,
+ * mHasTriedToDispatchBeforeInputEvent is set to true.
+ *
+ * @param aDeleteDirectionAndAmount
+ * If `MayEditActionDeleteAroundCollapsedSelection(
+ * mEditAction)` returns true, this must be set.
+ * Otherwise, don't set explicitly.
+ * @return If this method actually dispatches "beforeinput" event
+ * and it's canceled, returns
+ * NS_ERROR_EDITOR_ACTION_CANCELED.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult MaybeDispatchBeforeInputEvent(
+ nsIEditor::EDirection aDeleteDirectionAndAmount = nsIEditor::eNone);
+
+ /**
+ * MarkAsBeforeInputHasBeenDispatched() should be called only when updating
+ * the DOM occurs asynchronously from user input (e.g., inserting blob
+ * object which is loaded asynchronously) and `beforeinput` has already
+ * been dispatched (always should be so).
+ */
+ void MarkAsBeforeInputHasBeenDispatched() {
+ MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent());
+ MOZ_ASSERT(mEditAction == EditAction::ePaste ||
+ mEditAction == EditAction::ePasteAsQuotation ||
+ mEditAction == EditAction::eDrop);
+ mHasTriedToDispatchBeforeInputEvent = true;
+ }
+
+ /**
+ * MarkAsHandled() is called before dispatching `input` event and notifying
+ * editor observers. After this is called, any nested edit action become
+ * non illegal case.
+ */
+ void MarkAsHandled() {
+ MOZ_ASSERT(!mHandled);
+ mHandled = true;
+ }
+
+ /**
+ * ShouldAlreadyHaveHandledBeforeInputEventDispatching() returns true if the
+ * edit action requires to handle "beforeinput" event but not yet dispatched
+ * it nor considered as not dispatched it and can dispatch it when this is
+ * called.
+ */
+ bool ShouldAlreadyHaveHandledBeforeInputEventDispatching() const {
+ return !HasTriedToDispatchBeforeInputEvent() &&
+ NeedsBeforeInputEventHandling(mEditAction) &&
+ IsBeforeInputEventEnabled() /* &&
+ // If we still need to dispatch a clipboard event, we should
+ // dispatch it first, then, we need to dispatch beforeinput
+ // event later.
+ !NeedsToDispatchClipboardEvent()*/
+ ;
+ }
+
+ /**
+ * HasTriedToDispatchBeforeInputEvent() returns true if the instance's
+ * MaybeDispatchBeforeInputEvent() has already been called.
+ */
+ bool HasTriedToDispatchBeforeInputEvent() const {
+ return mHasTriedToDispatchBeforeInputEvent;
+ }
+
+ bool IsCanceled() const { return mBeforeInputEventCanceled; }
+
+ /**
+ * Returns a `Selection` for normal selection. The lifetime is guaranteed
+ * during alive this instance in the stack.
+ */
+ MOZ_KNOWN_LIVE Selection& SelectionRef() const {
+ MOZ_ASSERT(!mSelection ||
+ (mSelection->GetType() == SelectionType::eNormal));
+ return *mSelection;
+ }
+
+ nsIPrincipal* GetPrincipal() const { return mPrincipal; }
+ EditAction GetEditAction() const { return mEditAction; }
+
+ template <typename PT, typename CT>
+ void SetSpellCheckRestartPoint(const EditorDOMPointBase<PT, CT>& aPoint) {
+ MOZ_ASSERT(aPoint.IsSet());
+ // We should store only container and offset because new content may
+ // be inserted before referring child.
+ // XXX Shouldn't we compare whether aPoint is before
+ // mSpellCheckRestartPoint if it's set.
+ mSpellCheckRestartPoint =
+ EditorDOMPoint(aPoint.GetContainer(), aPoint.Offset());
+ }
+ void ClearSpellCheckRestartPoint() { mSpellCheckRestartPoint.Clear(); }
+ const EditorDOMPoint& GetSpellCheckRestartPoint() const {
+ return mSpellCheckRestartPoint;
+ }
+
+ void SetData(const nsAString& aData) {
+ MOZ_ASSERT(!mHasTriedToDispatchBeforeInputEvent,
+ "It's too late to set data since this may have already "
+ "dispatched a beforeinput event");
+ mData = aData;
+ }
+ const nsString& GetData() const { return mData; }
+
+ void SetColorData(const nsAString& aData);
+
+ /**
+ * InitializeDataTransfer(DataTransfer*) sets mDataTransfer to
+ * aDataTransfer. In this case, aDataTransfer should not be read/write
+ * because it'll be set to InputEvent.dataTransfer and which should be
+ * read-only.
+ */
+ void InitializeDataTransfer(dom::DataTransfer* aDataTransfer);
+ /**
+ * InitializeDataTransfer(nsITransferable*) creates new DataTransfer
+ * instance, initializes it with aTransferable and sets mDataTransfer to
+ * it.
+ */
+ void InitializeDataTransfer(nsITransferable* aTransferable);
+ /**
+ * InitializeDataTransfer(const nsAString&) creates new DataTransfer
+ * instance, initializes it with aString and sets mDataTransfer to it.
+ */
+ void InitializeDataTransfer(const nsAString& aString);
+ /**
+ * InitializeDataTransferWithClipboard() creates new DataTransfer instance,
+ * initializes it with clipboard and sets mDataTransfer to it.
+ */
+ void InitializeDataTransferWithClipboard(
+ SettingDataTransfer aSettingDataTransfer, int32_t aClipboardType);
+ dom::DataTransfer* GetDataTransfer() const { return mDataTransfer; }
+
+ /**
+ * AppendTargetRange() appends aTargetRange to target ranges. This should
+ * be used only by edit action handlers which do not want to set target
+ * ranges to selection ranges.
+ */
+ void AppendTargetRange(dom::StaticRange& aTargetRange);
+
+ /**
+ * Make dispatching `beforeinput` forcibly non-cancelable.
+ */
+ void MakeBeforeInputEventNonCancelable() {
+ mMakeBeforeInputEventNonCancelable = true;
+ }
+
+ /**
+ * NotifyOfDispatchingClipboardEvent() is called after dispatching
+ * a clipboard event.
+ */
+ void NotifyOfDispatchingClipboardEvent() {
+ MOZ_ASSERT(NeedsToDispatchClipboardEvent());
+ MOZ_ASSERT(!mHasTriedToDispatchClipboardEvent);
+ mHasTriedToDispatchClipboardEvent = true;
+ }
+
+ void Abort() { mAborted = true; }
+ bool IsAborted() const { return mAborted; }
+
+ void OnEditorDestroy() {
+ if (!mHandled && mHasTriedToDispatchBeforeInputEvent) {
+ // Remember the editor was destroyed only when this edit action is being
+ // handled because they are caused by mutation event listeners or
+ // something other unexpected event listeners. In the cases, new child
+ // edit action shouldn't been aborted.
+ mEditorWasDestroyedDuringHandlingEditAction = true;
+ }
+ if (mParentData) {
+ mParentData->OnEditorDestroy();
+ }
+ }
+ bool HasEditorDestroyedDuringHandlingEditAction() const {
+ return mEditorWasDestroyedDuringHandlingEditAction;
+ }
+
+ void SetTopLevelEditSubAction(EditSubAction aEditSubAction,
+ EDirection aDirection = eNone) {
+ mTopLevelEditSubAction = aEditSubAction;
+ TopLevelEditSubActionDataRef().Clear();
+ switch (mTopLevelEditSubAction) {
+ case EditSubAction::eInsertNode:
+ case EditSubAction::eMoveNode:
+ case EditSubAction::eCreateNode:
+ case EditSubAction::eSplitNode:
+ case EditSubAction::eInsertText:
+ case EditSubAction::eInsertTextComingFromIME:
+ case EditSubAction::eSetTextProperty:
+ case EditSubAction::eRemoveTextProperty:
+ case EditSubAction::eRemoveAllTextProperties:
+ case EditSubAction::eSetText:
+ case EditSubAction::eInsertLineBreak:
+ case EditSubAction::eInsertParagraphSeparator:
+ case EditSubAction::eCreateOrChangeList:
+ case EditSubAction::eIndent:
+ case EditSubAction::eOutdent:
+ case EditSubAction::eSetOrClearAlignment:
+ case EditSubAction::eCreateOrRemoveBlock:
+ case EditSubAction::eMergeBlockContents:
+ case EditSubAction::eRemoveList:
+ case EditSubAction::eCreateOrChangeDefinitionListItem:
+ case EditSubAction::eInsertElement:
+ case EditSubAction::eInsertQuotation:
+ case EditSubAction::eInsertQuotedText:
+ case EditSubAction::ePasteHTMLContent:
+ case EditSubAction::eInsertHTMLSource:
+ case EditSubAction::eSetPositionToAbsolute:
+ case EditSubAction::eSetPositionToStatic:
+ case EditSubAction::eDecreaseZIndex:
+ case EditSubAction::eIncreaseZIndex:
+ MOZ_ASSERT(aDirection == eNext);
+ mDirectionOfTopLevelEditSubAction = eNext;
+ break;
+ case EditSubAction::eJoinNodes:
+ case EditSubAction::eDeleteText:
+ MOZ_ASSERT(aDirection == ePrevious);
+ mDirectionOfTopLevelEditSubAction = ePrevious;
+ break;
+ case EditSubAction::eUndo:
+ case EditSubAction::eRedo:
+ case EditSubAction::eComputeTextToOutput:
+ case EditSubAction::eCreatePaddingBRElementForEmptyEditor:
+ case EditSubAction::eNone:
+ case EditSubAction::eReplaceHeadWithHTMLSource:
+ MOZ_ASSERT(aDirection == eNone);
+ mDirectionOfTopLevelEditSubAction = eNone;
+ break;
+ case EditSubAction::eDeleteNode:
+ case EditSubAction::eDeleteSelectedContent:
+ // Unfortunately, eDeleteNode and eDeleteSelectedContent is used with
+ // any direction. We might have specific sub-action for each
+ // direction, but there are some points referencing
+ // eDeleteSelectedContent so that we should keep storing direction
+ // as-is for now.
+ mDirectionOfTopLevelEditSubAction = aDirection;
+ break;
+ }
+ }
+ EditSubAction GetTopLevelEditSubAction() const {
+ MOZ_ASSERT(IsDataAvailable());
+ return mTopLevelEditSubAction;
+ }
+ EDirection GetDirectionOfTopLevelEditSubAction() const {
+ return mDirectionOfTopLevelEditSubAction;
+ }
+
+ const TopLevelEditSubActionData& TopLevelEditSubActionDataRef() const {
+ return mParentData ? mParentData->TopLevelEditSubActionDataRef()
+ : mTopLevelEditSubActionData;
+ }
+ TopLevelEditSubActionData& TopLevelEditSubActionDataRef() {
+ return mParentData ? mParentData->TopLevelEditSubActionDataRef()
+ : mTopLevelEditSubActionData;
+ }
+
+ const EditSubActionData& EditSubActionDataRef() const {
+ return mEditSubActionData;
+ }
+ EditSubActionData& EditSubActionDataRef() { return mEditSubActionData; }
+
+ SelectionState& SavedSelectionRef() {
+ return mParentData ? mParentData->SavedSelectionRef() : mSavedSelection;
+ }
+ const SelectionState& SavedSelectionRef() const {
+ return mParentData ? mParentData->SavedSelectionRef() : mSavedSelection;
+ }
+
+ RangeUpdater& RangeUpdaterRef() {
+ return mParentData ? mParentData->RangeUpdaterRef() : mRangeUpdater;
+ }
+ const RangeUpdater& RangeUpdaterRef() const {
+ return mParentData ? mParentData->RangeUpdaterRef() : mRangeUpdater;
+ }
+
+ void UpdateSelectionCache(Selection& aSelection);
+
+ private:
+ bool IsBeforeInputEventEnabled() const;
+
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ MaybeFlushPendingNotifications() const;
+
+ static bool NeedsBeforeInputEventHandling(EditAction aEditAction) {
+ MOZ_ASSERT(aEditAction != EditAction::eNone);
+ switch (aEditAction) {
+ case EditAction::eNone:
+ // If we're not handling edit action, we don't need to handle
+ // "beforeinput" event.
+ case EditAction::eNotEditing:
+ // If we're being initialized, we may need to create a padding <br>
+ // element, but it shouldn't cause `beforeinput` event.
+ case EditAction::eInitializing:
+ // If we're just selecting or getting table cells, we shouldn't
+ // dispatch `beforeinput` event.
+ case NS_EDIT_ACTION_CASES_ACCESSING_TABLE_DATA_WITHOUT_EDITING:
+ // If raw level transaction API is used, the API user needs to handle
+ // both "beforeinput" event and "input" event if it's necessary.
+ case EditAction::eUnknown:
+ // Hiding/showing password affects only layout so that we don't need
+ // to handle beforeinput event for it.
+ case EditAction::eHidePassword:
+ // We don't need to dispatch "beforeinput" event before
+ // "compositionstart".
+ case EditAction::eStartComposition:
+ // We don't need to let web apps know the mode change.
+ case EditAction::eEnableOrDisableCSS:
+ case EditAction::eEnableOrDisableAbsolutePositionEditor:
+ case EditAction::eEnableOrDisableResizer:
+ case EditAction::eEnableOrDisableInlineTableEditingUI:
+ // We don't need to let contents in chrome's editor to know the size
+ // change.
+ case EditAction::eSetWrapWidth:
+ // While resizing or moving element, we update only shadow, i.e.,
+ // don't touch to the DOM in content. Therefore, we don't need to
+ // dispatch "beforeinput" event.
+ case EditAction::eResizingElement:
+ case EditAction::eMovingElement:
+ // Perhaps, we don't need to dispatch "beforeinput" event for
+ // padding `<br>` element for empty editor because it's internal
+ // handling and it should be occurred by another change.
+ case EditAction::eCreatePaddingBRElementForEmptyEditor:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ bool NeedsToDispatchClipboardEvent() const {
+ if (mHasTriedToDispatchClipboardEvent) {
+ return false;
+ }
+ switch (mEditAction) {
+ case EditAction::ePaste:
+ case EditAction::ePasteAsQuotation:
+ case EditAction::eCut:
+ case EditAction::eCopy:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ EditorBase& mEditorBase;
+ RefPtr<Selection> mSelection;
+ nsTArray<OwningNonNull<Selection>> mRetiredSelections;
+
+ // True if the selection was created by doubleclicking a word.
+ bool mSelectionCreatedByDoubleclick{false};
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ // EditAction may be nested, for example, a command may be executed
+ // from mutation event listener which is run while editor changes
+ // the DOM tree. In such case, we need to handle edit action separately.
+ AutoEditActionDataSetter* mParentData;
+
+ // Cached selection for HTMLEditor::AutoSelectionRestorer.
+ SelectionState mSavedSelection;
+
+ // Utility class object for maintaining preserved ranges.
+ RangeUpdater mRangeUpdater;
+
+ // The data should be set to InputEvent.data.
+ nsString mData;
+
+ // The dataTransfer should be set to InputEvent.dataTransfer.
+ RefPtr<dom::DataTransfer> mDataTransfer;
+
+ // They are used for result of InputEvent.getTargetRanges() of beforeinput.
+ OwningNonNullStaticRangeArray mTargetRanges;
+
+ // Start point where spell checker should check from. This is used only
+ // by TextEditor.
+ EditorDOMPoint mSpellCheckRestartPoint;
+
+ // Different from mTopLevelEditSubAction, its data should be stored only
+ // in the most ancestor AutoEditActionDataSetter instance since we don't
+ // want to pay the copying cost and sync cost.
+ TopLevelEditSubActionData mTopLevelEditSubActionData;
+
+ // Different from mTopLevelEditSubActionData, this stores temporaly data
+ // for current edit sub action.
+ EditSubActionData mEditSubActionData;
+
+ // mEditAction and mRawEditActions stores edit action. The difference of
+ // them is, if and only if edit actions are nested and parent edit action
+ // is one of trying to edit something, but nested one is not so, it's
+ // overwritten by the parent edit action.
+ EditAction mEditAction;
+ EditAction mRawEditAction;
+
+ // Different from its data, you can refer "current" AutoEditActionDataSetter
+ // instance's mTopLevelEditSubAction member since it's copied from the
+ // parent instance at construction and it's always cleared before this
+ // won't be overwritten and cleared before destruction.
+ EditSubAction mTopLevelEditSubAction;
+
+ EDirection mDirectionOfTopLevelEditSubAction;
+
+ bool mAborted;
+
+ // Set to true when this handles "beforeinput" event dispatching. Note
+ // that even if "beforeinput" event shouldn't be dispatched for this,
+ // instance, this is set to true when it's considered.
+ bool mHasTriedToDispatchBeforeInputEvent;
+ // Set to true if "beforeinput" event was dispatched and it's canceled.
+ bool mBeforeInputEventCanceled;
+ // Set to true if `beforeinput` event must not be cancelable even if
+ // its inputType is defined as cancelable by the standards.
+ bool mMakeBeforeInputEventNonCancelable;
+ // Set to true when the edit action handler tries to dispatch a clipboard
+ // event.
+ bool mHasTriedToDispatchClipboardEvent;
+ // The editor instance may be destroyed once temporarily if `document.write`
+ // etc runs. In such case, we should mark this flag of being handled
+ // edit action.
+ bool mEditorWasDestroyedDuringHandlingEditAction;
+ // This is set before dispatching `input` event and notifying editor
+ // observers.
+ bool mHandled;
+
+#ifdef DEBUG
+ mutable bool mHasCanHandleChecked = false;
+#endif // #ifdef DEBUG
+
+ AutoEditActionDataSetter() = delete;
+ AutoEditActionDataSetter(const AutoEditActionDataSetter& aOther) = delete;
+ };
+
+ void UpdateEditActionData(const nsAString& aData) {
+ mEditActionData->SetData(aData);
+ }
+
+ void NotifyOfDispatchingClipboardEvent() {
+ MOZ_ASSERT(mEditActionData);
+ mEditActionData->NotifyOfDispatchingClipboardEvent();
+ }
+
+ 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.
+ ****************************************************************************/
+
+ bool IsEditActionCanceled() const {
+ MOZ_ASSERT(mEditActionData);
+ return mEditActionData->IsCanceled();
+ }
+
+ bool ShouldAlreadyHaveHandledBeforeInputEventDispatching() const {
+ MOZ_ASSERT(mEditActionData);
+ return mEditActionData
+ ->ShouldAlreadyHaveHandledBeforeInputEventDispatching();
+ }
+
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult MaybeDispatchBeforeInputEvent() {
+ MOZ_ASSERT(mEditActionData);
+ return mEditActionData->MaybeDispatchBeforeInputEvent();
+ }
+
+ void MarkAsBeforeInputHasBeenDispatched() {
+ MOZ_ASSERT(mEditActionData);
+ return mEditActionData->MarkAsBeforeInputHasBeenDispatched();
+ }
+
+ bool HasTriedToDispatchBeforeInputEvent() const {
+ return mEditActionData &&
+ mEditActionData->HasTriedToDispatchBeforeInputEvent();
+ }
+
+ bool IsEditActionDataAvailable() const {
+ return mEditActionData && mEditActionData->IsDataAvailable();
+ }
+
+ bool IsTopLevelEditSubActionDataAvailable() const {
+ return mEditActionData && !!GetTopLevelEditSubAction();
+ }
+
+ bool IsEditActionAborted() const {
+ MOZ_ASSERT(mEditActionData);
+ return mEditActionData->IsAborted();
+ }
+
+ /**
+ * SelectionRef() returns cached normal Selection. This is pretty faster than
+ * EditorBase::GetSelection() if available.
+ * Note that this never crash unless public methods ignore the result of
+ * AutoEditActionDataSetter::CanHandle() and keep handling edit action but any
+ * methods should stop handling edit action if it returns false.
+ */
+ MOZ_KNOWN_LIVE Selection& SelectionRef() const {
+ MOZ_ASSERT(mEditActionData);
+ MOZ_ASSERT(mEditActionData->SelectionRef().GetType() ==
+ SelectionType::eNormal);
+ return mEditActionData->SelectionRef();
+ }
+
+ nsIPrincipal* GetEditActionPrincipal() const {
+ MOZ_ASSERT(mEditActionData);
+ return mEditActionData->GetPrincipal();
+ }
+
+ /**
+ * GetEditAction() returns EditAction which is being handled. If some
+ * edit actions are nested, this returns the innermost edit action.
+ */
+ EditAction GetEditAction() const {
+ return mEditActionData ? mEditActionData->GetEditAction()
+ : EditAction::eNone;
+ }
+
+ /**
+ * GetInputEventData() returns inserting or inserted text value with
+ * current edit action. The result is proper for InputEvent.data value.
+ */
+ const nsString& GetInputEventData() const {
+ return mEditActionData ? mEditActionData->GetData() : VoidString();
+ }
+
+ /**
+ * GetInputEventDataTransfer() returns inserting or inserted transferable
+ * content with current edit action. The result is proper for
+ * InputEvent.dataTransfer value.
+ */
+ dom::DataTransfer* GetInputEventDataTransfer() const {
+ return mEditActionData ? mEditActionData->GetDataTransfer() : nullptr;
+ }
+
+ /**
+ * GetTopLevelEditSubAction() returns the top level edit sub-action.
+ * For example, if selected content is being replaced with inserted text,
+ * while removing selected content, the top level edit sub-action may be
+ * EditSubAction::eDeleteSelectedContent. However, while inserting new
+ * text, the top level edit sub-action may be EditSubAction::eInsertText.
+ * So, this result means what we are doing right now unless you're looking
+ * for a case which the method is called via mutation event listener or
+ * selectionchange event listener which are fired while handling the edit
+ * sub-action.
+ */
+ EditSubAction GetTopLevelEditSubAction() const {
+ return mEditActionData ? mEditActionData->GetTopLevelEditSubAction()
+ : EditSubAction::eNone;
+ }
+
+ /**
+ * GetDirectionOfTopLevelEditSubAction() returns direction which user
+ * intended for doing the edit sub-action.
+ */
+ EDirection GetDirectionOfTopLevelEditSubAction() const {
+ return mEditActionData
+ ? mEditActionData->GetDirectionOfTopLevelEditSubAction()
+ : eNone;
+ }
+
+ /**
+ * SavedSelection() returns reference to saved selection which are
+ * stored by HTMLEditor::AutoSelectionRestorer.
+ */
+ SelectionState& SavedSelectionRef() {
+ MOZ_ASSERT(IsHTMLEditor());
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ return mEditActionData->SavedSelectionRef();
+ }
+ const SelectionState& SavedSelectionRef() const {
+ MOZ_ASSERT(IsHTMLEditor());
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ return mEditActionData->SavedSelectionRef();
+ }
+
+ RangeUpdater& RangeUpdaterRef() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ return mEditActionData->RangeUpdaterRef();
+ }
+ const RangeUpdater& RangeUpdaterRef() const {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ return mEditActionData->RangeUpdaterRef();
+ }
+
+ template <typename PT, typename CT>
+ void SetSpellCheckRestartPoint(const EditorDOMPointBase<PT, CT>& aPoint) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ return mEditActionData->SetSpellCheckRestartPoint(aPoint);
+ }
+
+ void ClearSpellCheckRestartPoint() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ return mEditActionData->ClearSpellCheckRestartPoint();
+ }
+
+ const EditorDOMPoint& GetSpellCheckRestartPoint() const {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ return mEditActionData->GetSpellCheckRestartPoint();
+ }
+
+ const TopLevelEditSubActionData& TopLevelEditSubActionDataRef() const {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ return mEditActionData->TopLevelEditSubActionDataRef();
+ }
+ TopLevelEditSubActionData& TopLevelEditSubActionDataRef() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ return mEditActionData->TopLevelEditSubActionDataRef();
+ }
+
+ const EditSubActionData& EditSubActionDataRef() const {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ return mEditActionData->EditSubActionDataRef();
+ }
+ EditSubActionData& EditSubActionDataRef() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ return mEditActionData->EditSubActionDataRef();
+ }
+
+ /**
+ * GetFirstIMESelectionStartPoint() and GetLastIMESelectionEndPoint() returns
+ * start of first IME selection range or end of last IME selection range if
+ * there is. Otherwise, returns non-set DOM point.
+ */
+ template <typename EditorDOMPointType>
+ EditorDOMPointType GetFirstIMESelectionStartPoint() const;
+ template <typename EditorDOMPointType>
+ EditorDOMPointType GetLastIMESelectionEndPoint() const;
+
+ /**
+ * IsSelectionRangeContainerNotContent() returns true if one of container
+ * of selection ranges is not a content node, i.e., a Document node.
+ */
+ bool IsSelectionRangeContainerNotContent() const;
+
+ /**
+ * OnInputText() is called when user inputs text with keyboard or something.
+ *
+ * @param aStringToInsert The string to insert.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ OnInputText(const nsAString& aStringToInsert);
+
+ /**
+ * InsertTextAsSubAction() inserts aStringToInsert at selection. This
+ * should be used for handling it as an edit sub-action.
+ *
+ * @param aStringToInsert The string to insert.
+ * @param aSelectionHandling Specify whether selected content should be
+ * deleted or ignored.
+ */
+ enum class SelectionHandling { Ignore, Delete };
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InsertTextAsSubAction(
+ const nsAString& aStringToInsert, SelectionHandling aSelectionHandling);
+
+ /**
+ * InsertTextWithTransaction() inserts aStringToInsert to aPointToInsert or
+ * better insertion point around it. If aPointToInsert isn't in a text node,
+ * this method looks for the nearest point in a text node with
+ * FindBetterInsertionPoint(). If there is no text node, this creates
+ * new text node and put aStringToInsert to it.
+ *
+ * @param aDocument The document of this editor.
+ * @param aStringToInsert The string to insert.
+ * @param aPointToInsert The point to insert aStringToInsert.
+ * Must be valid DOM point.
+ * @return If succeeded, returns the point after inserted
+ * aStringToInsert. So, when this method actually
+ * inserts string, returns a point in the text node.
+ * Otherwise, returns aPointToInsert.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual Result<EditorDOMPoint, nsresult>
+ InsertTextWithTransaction(Document& aDocument,
+ const nsAString& aStringToInsert,
+ const EditorDOMPoint& aPointToInsert);
+
+ /**
+ * InsertTextIntoTextNodeWithTransaction() inserts aStringToInsert into
+ * aOffset of aTextNode with transaction.
+ *
+ * @param aStringToInsert String to be inserted.
+ * @param aPointToInsert The insertion point.
+ * @param aSuppressIME true if it's not a part of IME composition.
+ * E.g., adjusting white-spaces during composition.
+ * false, otherwise.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult InsertTextIntoTextNodeWithTransaction(
+ const nsAString& aStringToInsert,
+ const EditorDOMPointInText& aPointToInsert, bool aSuppressIME = false);
+
+ /**
+ * SetTextNodeWithoutTransaction() is optimized path to set new value to
+ * the text node directly and without transaction. This is used when
+ * setting `<input>.value` and `<textarea>.value`.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ SetTextNodeWithoutTransaction(const nsAString& aString, Text& aTextNode);
+
+ /**
+ * DeleteNodeWithTransaction() removes aContent from the DOM tree.
+ *
+ * @param aContent The node which will be removed form the DOM tree.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ DeleteNodeWithTransaction(nsIContent& aContent);
+
+ /**
+ * InsertNodeWithTransaction() inserts aContentToInsert before the child
+ * specified by aPointToInsert.
+ *
+ * @param aContentToInsert The node to be inserted.
+ * @param aPointToInsert The insertion point of aContentToInsert.
+ * If this refers end of the container, the
+ * transaction will append the node to the
+ * container. Otherwise, will insert the node
+ * before child node referred by this.
+ * @return If succeeded, returns the new content node and
+ * point to put caret.
+ */
+ template <typename ContentNodeType>
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT
+ Result<CreateNodeResultBase<ContentNodeType>, nsresult>
+ InsertNodeWithTransaction(ContentNodeType& aContentToInsert,
+ const EditorDOMPoint& aPointToInsert);
+
+ /**
+ * InsertPaddingBRElementForEmptyLastLineWithTransaction() creates a padding
+ * <br> element with setting flags to NS_PADDING_FOR_EMPTY_LAST_LINE and
+ * inserts it around aPointToInsert.
+ *
+ * @param aPointToInsert The DOM point where should be <br> node inserted
+ * before.
+ * @return If succeeded, returns the new <br> element and
+ * point to put caret around it.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult>
+ InsertPaddingBRElementForEmptyLastLineWithTransaction(
+ const EditorDOMPoint& aPointToInsert);
+
+ /**
+ * CloneAttributesWithTransaction() clones all attributes from
+ * aSourceElement to aDestElement after removing all attributes in
+ * aDestElement.
+ */
+ MOZ_CAN_RUN_SCRIPT void CloneAttributesWithTransaction(
+ Element& aDestElement, Element& aSourceElement);
+
+ /**
+ * CloneAttributeWithTransaction() copies aAttribute of aSourceElement to
+ * aDestElement. If aSourceElement doesn't have aAttribute, this removes
+ * aAttribute from aDestElement.
+ *
+ * @param aAttribute Attribute name to be cloned.
+ * @param aDestElement Element node which will be set aAttribute or
+ * whose aAttribute will be removed.
+ * @param aSourceElement Element node which provides the value of
+ * aAttribute in aDestElement.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult CloneAttributeWithTransaction(
+ nsAtom& aAttribute, Element& aDestElement, Element& aSourceElement);
+
+ /**
+ * RemoveAttributeWithTransaction() removes aAttribute from aElement.
+ *
+ * @param aElement Element node which will lose aAttribute.
+ * @param aAttribute Attribute name to be removed from aElement.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ RemoveAttributeWithTransaction(Element& aElement, nsAtom& aAttribute);
+
+ MOZ_CAN_RUN_SCRIPT virtual nsresult RemoveAttributeOrEquivalent(
+ Element* aElement, nsAtom* aAttribute, bool aSuppressTransaction) = 0;
+
+ /**
+ * SetAttributeWithTransaction() sets aAttribute of aElement to aValue.
+ *
+ * @param aElement Element node which will have aAttribute.
+ * @param aAttribute Attribute name to be set.
+ * @param aValue Attribute value be set to aAttribute.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult SetAttributeWithTransaction(
+ Element& aElement, nsAtom& aAttribute, const nsAString& aValue);
+
+ MOZ_CAN_RUN_SCRIPT virtual nsresult SetAttributeOrEquivalent(
+ Element* aElement, nsAtom* aAttribute, const nsAString& aValue,
+ bool aSuppressTransaction) = 0;
+
+ /**
+ * Method to replace certain CreateElementNS() calls.
+ *
+ * @param aTag Tag you want.
+ */
+ already_AddRefed<Element> CreateHTMLContent(const nsAtom* aTag) const;
+
+ /**
+ * Creates text node which is marked as "maybe modified frequently" and
+ * "maybe masked" if this is a password editor.
+ */
+ already_AddRefed<nsTextNode> CreateTextNode(const nsAString& aData) const;
+
+ /**
+ * DoInsertText(), DoDeleteText(), DoReplaceText() and DoSetText() are
+ * wrapper of `CharacterData::InsertData()`, `CharacterData::DeleteData()`,
+ * `CharacterData::ReplaceData()` and `CharacterData::SetData()`.
+ */
+ MOZ_CAN_RUN_SCRIPT void DoInsertText(dom::Text& aText, uint32_t aOffset,
+ const nsAString& aStringToInsert,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void DoDeleteText(dom::Text& aText, uint32_t aOffset,
+ uint32_t aCount, ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void DoReplaceText(dom::Text& aText, uint32_t aOffset,
+ uint32_t aCount,
+ const nsAString& aStringToInsert,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void DoSetText(dom::Text& aText,
+ const nsAString& aStringToSet,
+ ErrorResult& aRv);
+
+ /**
+ * DeleteTextWithTransaction() removes text in the range from aTextNode.
+ *
+ * @param aTextNode The text node which should be modified.
+ * @param aOffset Start offset of removing text in aTextNode.
+ * @param aLength Length of removing text.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult DeleteTextWithTransaction(dom::Text& aTextNode,
+ uint32_t aOffset,
+ uint32_t aLength);
+
+ /**
+ * MarkElementDirty() sets a special dirty attribute on the element.
+ * Usually this will be called immediately after creating a new node.
+ *
+ * @param aElement The element for which to insert formatting.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ MarkElementDirty(Element& aElement) const;
+
+ MOZ_CAN_RUN_SCRIPT nsresult
+ DoTransactionInternal(nsITransaction* aTransaction);
+
+ /**
+ * Returns true if aNode is our root node. The root is:
+ * If TextEditor, the anonymous <div> element.
+ * If HTMLEditor, a <body> element or the document element which may not be
+ * editable if it's not in the design mode.
+ */
+ bool IsRoot(const nsINode* inNode) const;
+
+ /**
+ * Returns true if aNode is a descendant of our root node.
+ * See the comment for IsRoot() for what the root node means.
+ */
+ bool IsDescendantOfRoot(const nsINode* inNode) const;
+
+ /**
+ * Returns true when inserting text should be a part of current composition.
+ */
+ bool ShouldHandleIMEComposition() const;
+
+ template <typename EditorDOMPointType>
+ EditorDOMPointType GetFirstSelectionStartPoint() const;
+ template <typename EditorDOMPointType>
+ EditorDOMPointType GetFirstSelectionEndPoint() const;
+
+ static nsresult GetEndChildNode(const Selection& aSelection,
+ nsIContent** aEndNode);
+
+ template <typename PT, typename CT>
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ CollapseSelectionTo(const EditorDOMPointBase<PT, CT>& aPoint) const {
+ // We don't need to throw exception directly for a failure of updating
+ // selection. Therefore, let's use IgnoredErrorResult for the performance.
+ IgnoredErrorResult error;
+ CollapseSelectionTo(aPoint, error);
+ return error.StealNSResult();
+ }
+
+ template <typename PT, typename CT>
+ MOZ_CAN_RUN_SCRIPT void CollapseSelectionTo(
+ const EditorDOMPointBase<PT, CT>& aPoint, ErrorResult& aRv) const {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(!aRv.Failed());
+
+ if (aPoint.GetInterlinePosition() != InterlinePosition::Undefined) {
+ if (MOZ_UNLIKELY(NS_FAILED(SelectionRef().SetInterlinePosition(
+ aPoint.GetInterlinePosition())))) {
+ NS_WARNING("Selection::SetInterlinePosition() failed");
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ }
+
+ SelectionRef().CollapseInLimiter(aPoint, aRv);
+ if (MOZ_UNLIKELY(Destroyed())) {
+ NS_WARNING("Selection::CollapseInLimiter() caused destroying the editor");
+ aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
+ return;
+ }
+ NS_WARNING_ASSERTION(!aRv.Failed(),
+ "Selection::CollapseInLimiter() failed");
+ }
+
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ CollapseSelectionToStartOf(nsINode& aNode) const {
+ return CollapseSelectionTo(EditorRawDOMPoint(&aNode, 0u));
+ }
+
+ MOZ_CAN_RUN_SCRIPT void CollapseSelectionToStartOf(nsINode& aNode,
+ ErrorResult& aRv) const {
+ CollapseSelectionTo(EditorRawDOMPoint(&aNode, 0u), aRv);
+ }
+
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ CollapseSelectionToEndOf(nsINode& aNode) const {
+ return CollapseSelectionTo(EditorRawDOMPoint::AtEndOf(aNode));
+ }
+
+ MOZ_CAN_RUN_SCRIPT void CollapseSelectionToEndOf(nsINode& aNode,
+ ErrorResult& aRv) const {
+ CollapseSelectionTo(EditorRawDOMPoint::AtEndOf(aNode), aRv);
+ }
+
+ /**
+ * AllowsTransactionsToChangeSelection() returns true if editor allows any
+ * transactions to change Selection. Otherwise, transactions shouldn't
+ * change Selection.
+ */
+ inline bool AllowsTransactionsToChangeSelection() const {
+ return mAllowsTransactionsToChangeSelection;
+ }
+
+ /**
+ * MakeThisAllowTransactionsToChangeSelection() with true makes this editor
+ * allow transactions to change Selection. Otherwise, i.e., with false,
+ * makes this editor not allow transactions to change Selection.
+ */
+ inline void MakeThisAllowTransactionsToChangeSelection(bool aAllow) {
+ mAllowsTransactionsToChangeSelection = aAllow;
+ }
+
+ nsresult HandleInlineSpellCheck(
+ const EditorDOMPoint& aPreviouslySelectedStart,
+ const dom::AbstractRange* aRange = nullptr);
+
+ /**
+ * Whether the editor is active on the DOM window. Note that when this
+ * returns true but GetFocusedElement() returns null, it means that this
+ * editor was focused when the DOM window was active.
+ */
+ virtual bool IsActiveInDOMWindow() const;
+
+ /**
+ * FindBetterInsertionPoint() tries to look for better insertion point which
+ * is typically the nearest text node and offset in it.
+ *
+ * @param aPoint Insertion point which the callers found.
+ * @return Better insertion point if there is. If not returns
+ * same point as aPoint.
+ */
+ template <typename EditorDOMPointType>
+ EditorDOMPointType FindBetterInsertionPoint(
+ const EditorDOMPointType& aPoint) const;
+
+ /**
+ * HideCaret() hides caret with nsCaret::AddForceHide() or may show carent
+ * with nsCaret::RemoveForceHide(). This does NOT set visibility of
+ * nsCaret. Therefore, this is stateless.
+ */
+ void HideCaret(bool aHide);
+
+ protected: // Edit sub-action handler
+ /**
+ * AutoCaretBidiLevelManager() computes bidi level of caret, deleting
+ * character(s) from aPointAtCaret at construction. Then, if you'll
+ * need to extend the selection, you should calls `UpdateCaretBidiLevel()`,
+ * then, this class may update caret bidi level for you if it's required.
+ */
+ class MOZ_RAII AutoCaretBidiLevelManager final {
+ public:
+ /**
+ * @param aEditorBase The editor.
+ * @param aPointAtCaret Collapsed `Selection` point.
+ * @param aDirectionAndAmount The direction and amount to delete.
+ */
+ template <typename PT, typename CT>
+ AutoCaretBidiLevelManager(const EditorBase& aEditorBase,
+ nsIEditor::EDirection aDirectionAndAmount,
+ const EditorDOMPointBase<PT, CT>& aPointAtCaret);
+
+ /**
+ * Failed() returns true if the constructor failed to handle the bidi
+ * information.
+ */
+ bool Failed() const { return mFailed; }
+
+ /**
+ * Canceled() returns true if when the caller should stop deleting
+ * characters since caret position is not visually adjacent the deleting
+ * characters and user does not wand to delete them in that case.
+ */
+ bool Canceled() const { return mCanceled; }
+
+ /**
+ * MaybeUpdateCaretBidiLevel() may update caret bidi level and schedule to
+ * paint it if they are necessary.
+ */
+ void MaybeUpdateCaretBidiLevel(const EditorBase& aEditorBase) const;
+
+ private:
+ Maybe<mozilla::intl::BidiEmbeddingLevel> mNewCaretBidiLevel;
+ bool mFailed = false;
+ bool mCanceled = false;
+ };
+
+ /**
+ * UndefineCaretBidiLevel() resets bidi level of the caret.
+ */
+ void UndefineCaretBidiLevel() const;
+
+ /**
+ * Flushing pending notifications if nsFrameSelection requires the latest
+ * layout information to compute deletion range. This may destroy the
+ * editor instance itself. When this returns false, don't keep doing
+ * anything.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT bool
+ FlushPendingNotificationsIfToHandleDeletionWithFrameSelection(
+ nsIEditor::EDirection aDirectionAndAmount) const;
+
+ /**
+ * DeleteSelectionAsSubAction() removes selection content or content around
+ * caret with transactions. This should be used for handling it as an
+ * edit sub-action.
+ *
+ * @param aDirectionAndAmount How much range should be removed.
+ * @param aStripWrappers Whether the parent blocks should be removed
+ * when they become empty. If this instance is
+ * a TextEditor, Must be nsIEditor::eNoStrip.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ DeleteSelectionAsSubAction(nsIEditor::EDirection aDirectionAndAmount,
+ nsIEditor::EStripWrappers aStripWrappers);
+
+ /**
+ * This method handles "delete selection" commands.
+ * NOTE: Don't call this method recursively from the helper methods since
+ * when nobody handled it without canceling and returing an error,
+ * this falls it back to `DeleteSelectionWithTransaction()`.
+ *
+ * @param aDirectionAndAmount Direction of the deletion.
+ * @param aStripWrappers Must be nsIEditor::eNoStrip if this is a
+ * TextEditor instance. Otherwise,
+ * nsIEditor::eStrip is also valid.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual Result<EditActionResult, nsresult>
+ HandleDeleteSelection(nsIEditor::EDirection aDirectionAndAmount,
+ nsIEditor::EStripWrappers aStripWrappers) = 0;
+
+ /**
+ * ReplaceSelectionAsSubAction() replaces selection with aString.
+ *
+ * @param aString The string to replace.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult
+ ReplaceSelectionAsSubAction(const nsAString& aString);
+
+ /**
+ * HandleInsertText() handles inserting text at selection.
+ *
+ * @param aEditSubAction Must be EditSubAction::eInsertText or
+ * EditSubAction::eInsertTextComingFromIME.
+ * @param aInsertionString String to be inserted at selection.
+ * @param aSelectionHandling Specify whether selected content should be
+ * deleted or ignored.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual Result<EditActionResult, nsresult>
+ HandleInsertText(EditSubAction aEditSubAction,
+ const nsAString& aInsertionString,
+ SelectionHandling aSelectionHandling) = 0;
+
+ /**
+ * InsertWithQuotationsAsSubAction() inserts aQuotedText with appending ">"
+ * to start of every line.
+ *
+ * @param aQuotedText String to insert. This will be quoted by ">"
+ * automatically.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual nsresult
+ InsertWithQuotationsAsSubAction(const nsAString& aQuotedText) = 0;
+
+ /**
+ * PrepareInsertContent() is a helper method of InsertTextAt(),
+ * HTMLEditor::HTMLWithContextInserter::Run(). They insert content coming
+ * from clipboard or drag and drop. Before that, they may need to remove
+ * selected contents and adjust selection. This does them instead.
+ *
+ * @param aPointToInsert Point to insert. Must be set. Callers
+ * shouldn't use this instance after calling this
+ * method because this method may cause changing
+ * the DOM tree and Selection.
+ */
+ enum class DeleteSelectedContent : bool {
+ No, // Don't delete selection
+ Yes, // Delete selected content
+ };
+ MOZ_CAN_RUN_SCRIPT nsresult
+ PrepareToInsertContent(const EditorDOMPoint& aPointToInsert,
+ DeleteSelectedContent aDeleteSelectedContent);
+
+ /**
+ * InsertTextAt() inserts aStringToInsert at aPointToInsert.
+ *
+ * @param aStringToInsert The string which you want to insert.
+ * @param aPointToInsert The insertion point.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult InsertTextAt(
+ const nsAString& aStringToInsert, const EditorDOMPoint& aPointToInsert,
+ DeleteSelectedContent aDeleteSelectedContent);
+
+ /**
+ * Return whether the data is safe to insert as the source and destination
+ * principals match, or we are in a editor context where this doesn't matter.
+ * Otherwise, the data must be sanitized first.
+ */
+ enum class SafeToInsertData : bool { No, Yes };
+ SafeToInsertData IsSafeToInsertData(nsIPrincipal* aSourcePrincipal) const;
+
+ protected: // Called by helper classes.
+ /**
+ * OnStartToHandleTopLevelEditSubAction() is called when
+ * GetTopLevelEditSubAction() is EditSubAction::eNone and somebody starts to
+ * handle aEditSubAction.
+ *
+ * @param aTopLevelEditSubAction Top level edit sub action which
+ * will be handled soon.
+ * @param aDirectionOfTopLevelEditSubAction Direction of aEditSubAction.
+ */
+ MOZ_CAN_RUN_SCRIPT virtual void OnStartToHandleTopLevelEditSubAction(
+ EditSubAction aTopLevelEditSubAction,
+ nsIEditor::EDirection aDirectionOfTopLevelEditSubAction,
+ ErrorResult& aRv);
+
+ /**
+ * OnEndHandlingTopLevelEditSubAction() is called after
+ * SetTopLevelEditSubAction() is handled.
+ */
+ MOZ_CAN_RUN_SCRIPT virtual nsresult OnEndHandlingTopLevelEditSubAction();
+
+ /**
+ * OnStartToHandleEditSubAction() and OnEndHandlingEditSubAction() are called
+ * when starting to handle an edit sub action and ending handling an edit
+ * sub action.
+ */
+ void OnStartToHandleEditSubAction() { EditSubActionDataRef().Clear(); }
+ void OnEndHandlingEditSubAction() { EditSubActionDataRef().Clear(); }
+
+ /**
+ * (Begin|End)PlaceholderTransaction() are called by AutoPlaceholderBatch.
+ * This set of methods are similar to the (Begin|End)Transaction(), but do
+ * not use the transaction managers batching feature. Instead we use a
+ * placeholder transaction to wrap up any further transaction while the
+ * batch is open. The advantage of this is that placeholder transactions
+ * can later merge, if needed. Merging is unavailable between transaction
+ * manager batches.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void BeginPlaceholderTransaction(
+ nsStaticAtom& aTransactionName, const char* aRequesterFuncName);
+ enum class ScrollSelectionIntoView { No, Yes };
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void EndPlaceholderTransaction(
+ ScrollSelectionIntoView aScrollSelectionIntoView,
+ const char* aRequesterFuncName);
+
+ void BeginUpdateViewBatch(const char* aRequesterFuncName);
+ MOZ_CAN_RUN_SCRIPT void EndUpdateViewBatch(const char* aRequesterFuncName);
+
+ /**
+ * Used by HTMLEditor::AutoTransactionBatch, nsIEditor::BeginTransaction
+ * and nsIEditor::EndTransation. After calling BeginTransactionInternal(),
+ * all transactions will be treated as an atomic transaction. I.e., two or
+ * more transactions are undid once.
+ * XXX What's the difference with PlaceholderTransaction? Should we always
+ * use it instead?
+ */
+ MOZ_CAN_RUN_SCRIPT void BeginTransactionInternal(
+ const char* aRequesterFuncName);
+ MOZ_CAN_RUN_SCRIPT void EndTransactionInternal(
+ const char* aRequesterFuncName);
+
+ protected: // Shouldn't be used by friend classes
+ /**
+ * The default destructor. This should suffice. Should this be pure virtual
+ * for someone to derive from the EditorBase later? I don't believe so.
+ */
+ virtual ~EditorBase();
+
+ /**
+ * @param aDocument The dom document interface being observed
+ * @param aRootElement
+ * This is the root of the editable section of this
+ * document. If it is null then we get root from document
+ * body.
+ * @param aSelectionController
+ * The selection controller of selections which will be
+ * used in this editor.
+ * @param aFlags Some of nsIEditor::eEditor*Mask flags.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult
+ InitInternal(Document& aDocument, Element* aRootElement,
+ nsISelectionController& aSelectionController, uint32_t aFlags);
+
+ /**
+ * PostCreateInternal() should be called after InitInternal(), and is the time
+ * that the editor tells its documentStateObservers that the document has been
+ * created.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult PostCreateInternal();
+
+ /**
+ * PreDestroyInternal() is called before the editor goes away, and gives the
+ * editor a chance to tell its documentStateObservers that the document is
+ * going away.
+ */
+ MOZ_CAN_RUN_SCRIPT virtual void PreDestroyInternal();
+
+ MOZ_ALWAYS_INLINE EditorType GetEditorType() const {
+ return mIsHTMLEditorClass ? EditorType::HTML : EditorType::Text;
+ }
+
+ /**
+ * Check whether the caller can keep handling focus event.
+ *
+ * @param aOriginalEventTargetNode The original event target of the focus
+ * event.
+ */
+ [[nodiscard]] bool CanKeepHandlingFocusEvent(
+ const nsINode& aOriginalEventTargetNode) const;
+
+ /**
+ * If this editor has skipped spell checking and not yet flushed, this runs
+ * the spell checker.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult FlushPendingSpellCheck();
+
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult EnsureEmptyTextFirstChild();
+
+ int32_t WrapWidth() const { return mWrapColumn; }
+
+ /**
+ * ToGenericNSResult() computes proper nsresult value for the editor users.
+ * This should be used only when public methods return result of internal
+ * methods.
+ */
+ static inline nsresult ToGenericNSResult(nsresult aRv) {
+ switch (aRv) {
+ // If the editor is destroyed while handling an edit action, editor needs
+ // to stop handling it. However, editor throw exception in this case
+ // because Chrome does not throw exception even in this case.
+ case NS_ERROR_EDITOR_DESTROYED:
+ return NS_OK;
+ // If editor meets unexpected DOM tree due to modified by mutation event
+ // listener, editor needs to stop handling it. However, editor shouldn't
+ // return error for the users because Chrome does not throw exception in
+ // this case.
+ case NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE:
+ return NS_OK;
+ // If the editing action is canceled by event listeners, editor needs
+ // to stop handling it. However, editor shouldn't return error for
+ // the callers but they should be able to distinguish whether it's
+ // canceled or not. Although it's DOM specific code, let's return
+ // DOM_SUCCESS_DOM_NO_OPERATION here.
+ case NS_ERROR_EDITOR_ACTION_CANCELED:
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ // If there is no selection range or editable selection ranges, editor
+ // needs to stop handling it. However, editor shouldn't return error for
+ // the callers to avoid throwing exception. However, they may want to
+ // check whether it works or not. Therefore, we should return
+ // NS_SUCCESS_DOM_NO_OPERATION instead.
+ case NS_ERROR_EDITOR_NO_EDITABLE_RANGE:
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ // If CreateNodeResultBase::SuggestCaretPointTo etc is called with
+ // SuggestCaret::AndIgnoreTrivialErrors and CollapseSelectionTo returns
+ // non-critical error e.g., not NS_ERROR_EDITOR_DESTROYED, it returns
+ // this success code instead of actual error code for making the caller
+ // handle the case easier. Therefore, this should be mapped to NS_OK
+ // for the users of editor.
+ case NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR:
+ return NS_OK;
+ default:
+ return aRv;
+ }
+ }
+
+ /**
+ * GetDocumentCharsetInternal() returns charset of the document.
+ */
+ nsresult GetDocumentCharsetInternal(nsACString& aCharset) const;
+
+ /**
+ * ComputeValueInternal() computes string value of this editor for given
+ * format. This may be too expensive if it's in hot path.
+ *
+ * @param aFormatType MIME type like "text/plain".
+ * @param aDocumentEncoderFlags Flags of nsIDocumentEncoder.
+ * @param aCharset Encoding of the document.
+ */
+ nsresult ComputeValueInternal(const nsAString& aFormatType,
+ uint32_t aDocumentEncoderFlags,
+ nsAString& aOutputString) const;
+
+ /**
+ * GetAndInitDocEncoder() returns a document encoder instance for aFormatType
+ * after initializing it. The result may be cached for saving recreation
+ * cost.
+ *
+ * @param aFormatType MIME type like "text/plain".
+ * @param aDocumentEncoderFlags Flags of nsIDocumentEncoder.
+ * @param aCharset Encoding of the document.
+ */
+ already_AddRefed<nsIDocumentEncoder> GetAndInitDocEncoder(
+ const nsAString& aFormatType, uint32_t aDocumentEncoderFlags,
+ const nsACString& aCharset) const;
+
+ /**
+ * EnsurePaddingBRElementInMultilineEditor() creates a padding `<br>` element
+ * at end of multiline text editor.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ EnsurePaddingBRElementInMultilineEditor();
+
+ /**
+ * SelectAllInternal() should be used instead of SelectAll() in editor
+ * because SelectAll() creates AutoEditActionSetter but we should avoid
+ * to create it as far as possible.
+ */
+ MOZ_CAN_RUN_SCRIPT virtual nsresult SelectAllInternal();
+
+ nsresult DetermineCurrentDirection();
+
+ /**
+ * DispatchInputEvent() dispatches an "input" event synchronously or
+ * asynchronously if it's not safe to dispatch.
+ */
+ MOZ_CAN_RUN_SCRIPT void DispatchInputEvent();
+
+ /**
+ * Called after a transaction is done successfully.
+ */
+ MOZ_CAN_RUN_SCRIPT void DoAfterDoTransaction(nsITransaction* aTransaction);
+
+ /**
+ * Called after a transaction is undone successfully.
+ */
+
+ MOZ_CAN_RUN_SCRIPT void DoAfterUndoTransaction();
+
+ /**
+ * Called after a transaction is redone successfully.
+ */
+ MOZ_CAN_RUN_SCRIPT void DoAfterRedoTransaction();
+
+ /**
+ * Tell the doc state listeners that the doc state has changed.
+ */
+ enum TDocumentListenerNotification {
+ eDocumentCreated,
+ eDocumentToBeDestroyed,
+ eDocumentStateChanged
+ };
+ MOZ_CAN_RUN_SCRIPT nsresult
+ NotifyDocumentListeners(TDocumentListenerNotification aNotificationType);
+
+ /**
+ * Make the given selection span the entire document.
+ */
+ MOZ_CAN_RUN_SCRIPT virtual nsresult SelectEntireDocument() = 0;
+
+ /**
+ * Helper method for scrolling the selection into view after
+ * an edit operation.
+ *
+ * Editor methods *should* call this method instead of the versions
+ * in the various selection interfaces, since this makes sure that
+ * the editor's sync/async settings for reflowing, painting, and scrolling
+ * match.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ ScrollSelectionFocusIntoView() const;
+
+ virtual nsresult InstallEventListeners();
+ virtual void CreateEventListeners();
+ virtual void RemoveEventListeners();
+ [[nodiscard]] bool IsListeningToEvents() const;
+
+ /**
+ * Called if and only if this editor is in readonly mode.
+ */
+ void HandleKeyPressEventInReadOnlyMode(
+ WidgetKeyboardEvent& aKeyboardEvent) const;
+
+ /**
+ * Get the input event target. This might return null.
+ */
+ virtual already_AddRefed<Element> GetInputEventTargetElement() const = 0;
+
+ /**
+ * Return true if spellchecking should be enabled for this editor.
+ */
+ [[nodiscard]] bool GetDesiredSpellCheckState();
+
+ [[nodiscard]] bool CanEnableSpellCheck() const {
+ // Check for password/readonly/disabled, which are not spellchecked
+ // regardless of DOM. Also, check to see if spell check should be skipped
+ // or not.
+ return !IsPasswordEditor() && !IsReadonly() && !ShouldSkipSpellCheck();
+ }
+
+ /**
+ * InitializeSelectionAncestorLimit() is called by InitializeSelection().
+ * When this is called, each implementation has to call
+ * Selection::SetAncestorLimiter() with aAnotherLimit.
+ *
+ * @param aAncestorLimit New ancestor limit of Selection. This always
+ * has parent node. So, it's always safe to
+ * call SetAncestorLimit() with this node.
+ */
+ virtual void InitializeSelectionAncestorLimit(
+ nsIContent& aAncestorLimit) const;
+
+ /**
+ * Initializes selection and caret for the editor at getting focus. If
+ * aOriginalEventTargetNode isn't a host of the editor, i.e., the editor
+ * doesn't get focus, this does nothing.
+ *
+ * @param aOriginalEventTargetNode The original event target node of the
+ * focus event.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult
+ InitializeSelection(const nsINode& aOriginalEventTargetNode);
+
+ enum NotificationForEditorObservers {
+ eNotifyEditorObserversOfEnd,
+ eNotifyEditorObserversOfBefore,
+ eNotifyEditorObserversOfCancel
+ };
+ MOZ_CAN_RUN_SCRIPT void NotifyEditorObservers(
+ NotificationForEditorObservers aNotification);
+
+ /**
+ * HowToHandleCollapsedRange indicates how collapsed range should be treated.
+ */
+ enum class HowToHandleCollapsedRange {
+ // Ignore collapsed range.
+ Ignore,
+ // Extend collapsed range for removing previous content.
+ ExtendBackward,
+ // Extend collapsed range for removing next content.
+ ExtendForward,
+ };
+
+ static HowToHandleCollapsedRange HowToHandleCollapsedRangeFor(
+ nsIEditor::EDirection aDirectionAndAmount) {
+ switch (aDirectionAndAmount) {
+ case nsIEditor::eNone:
+ return HowToHandleCollapsedRange::Ignore;
+ case nsIEditor::ePrevious:
+ return HowToHandleCollapsedRange::ExtendBackward;
+ case nsIEditor::eNext:
+ return HowToHandleCollapsedRange::ExtendForward;
+ case nsIEditor::ePreviousWord:
+ case nsIEditor::eNextWord:
+ case nsIEditor::eToBeginningOfLine:
+ case nsIEditor::eToEndOfLine:
+ // If the amount is word or
+ // line,`AutoRangeArray::ExtendAnchorFocusRangeFor()` must have already
+ // been extended collapsed ranges before.
+ return HowToHandleCollapsedRange::Ignore;
+ }
+ MOZ_ASSERT_UNREACHABLE("Invalid nsIEditor::EDirection value");
+ return HowToHandleCollapsedRange::Ignore;
+ }
+
+ /**
+ * InsertDroppedDataTransferAsAction() inserts all data items in aDataTransfer
+ * at aDroppedAt unless the editor is destroyed.
+ *
+ * @param aEditActionData The edit action data whose edit action must be
+ * EditAction::eDrop.
+ * @param aDataTransfer The data transfer object which is dropped.
+ * @param aDroppedAt The DOM tree position whether aDataTransfer
+ * is dropped.
+ * @param aSourcePrincipal Principal of the source of the drag.
+ * May be nullptr if it comes from another app
+ * or process.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual nsresult
+ InsertDroppedDataTransferAsAction(AutoEditActionDataSetter& aEditActionData,
+ dom::DataTransfer& aDataTransfer,
+ const EditorDOMPoint& aDroppedAt,
+ nsIPrincipal* aSourcePrincipal) = 0;
+
+ /**
+ * DeleteSelectionByDragAsAction() removes selection and dispatch "input"
+ * event whose inputType is "deleteByDrag".
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ DeleteSelectionByDragAsAction(bool aDispatchInputEvent);
+
+ /**
+ * DeleteSelectionWithTransaction() removes selected content or content
+ * around caret with transactions and remove empty inclusive ancestor
+ * inline elements of collapsed selection after removing the contents.
+ *
+ * @param aDirectionAndAmount How much range should be removed.
+ * @param aStripWrappers Whether the parent blocks should be removed
+ * when they become empty.
+ * Note that this must be `nsIEditor::eNoStrip`
+ * if this is a TextEditor because anyway it'll
+ * be ignored.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ DeleteSelectionWithTransaction(nsIEditor::EDirection aDirectionAndAmount,
+ nsIEditor::EStripWrappers aStripWrappers);
+
+ /**
+ * DeleteRangesWithTransaction() removes content in aRangesToDelete or content
+ * around collapsed ranges in aRangesToDelete with transactions and remove
+ * empty inclusive ancestor inline elements of collapsed ranges after
+ * removing the contents.
+ *
+ * @param aDirectionAndAmount How much range should be removed.
+ * @param aStripWrappers Whether the parent blocks should be removed
+ * when they become empty.
+ * Note that this must be `nsIEditor::eNoStrip`
+ * if this is a TextEditor because anyway it'll
+ * be ignored.
+ * @param aRangesToDelete The ranges to delete content.
+ */
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
+ DeleteRangesWithTransaction(nsIEditor::EDirection aDirectionAndAmount,
+ nsIEditor::EStripWrappers aStripWrappers,
+ const AutoRangeArray& aRangesToDelete);
+
+ /**
+ * Create an aggregate transaction for delete the content in aRangesToDelete.
+ * The result may include DeleteNodeTransactions and/or DeleteTextTransactions
+ * as its children.
+ *
+ * @param aHowToHandleCollapsedRange
+ * How to handle collapsed ranges.
+ * @param aRangesToDelete The ranges to delete content.
+ * @return If it can remove the content in ranges, returns
+ * an aggregate transaction which has some
+ * DeleteNodeTransactions and/or
+ * DeleteTextTransactions as its children.
+ */
+ already_AddRefed<EditAggregateTransaction>
+ CreateTransactionForDeleteSelection(
+ HowToHandleCollapsedRange aHowToHandleCollapsedRange,
+ const AutoRangeArray& aRangesToDelete);
+
+ /**
+ * Create a transaction for removing the nodes and/or text around
+ * aRangeToDelete.
+ *
+ * @param aCollapsedRange The range to be removed. This must be
+ * collapsed.
+ * @param aHowToHandleCollapsedRange
+ * How to handle aCollapsedRange. Must
+ * be HowToHandleCollapsedRange::ExtendBackward or
+ * HowToHandleCollapsedRange::ExtendForward.
+ * @return The transaction to remove content around the
+ * range. Its type is DeleteNodeTransaction or
+ * DeleteTextTransaction.
+ */
+ already_AddRefed<EditTransactionBase> CreateTransactionForCollapsedRange(
+ const nsRange& aCollapsedRange,
+ HowToHandleCollapsedRange aHowToHandleCollapsedRange);
+
+ /**
+ * ComputeInsertedRange() returns actual range modified by inserting string
+ * in a text node. If mutation event listener changed the text data, this
+ * returns a range which covers all over the text data.
+ */
+ std::tuple<EditorDOMPointInText, EditorDOMPointInText> ComputeInsertedRange(
+ const EditorDOMPointInText& aInsertedPoint,
+ const nsAString& aInsertedString) const;
+
+ /**
+ * EnsureComposition() should be called by composition event handlers. This
+ * tries to get the composition for the event and set it to mComposition.
+ * However, this may fail because the composition may be committed before
+ * the event comes to the editor.
+ *
+ * @return true if there is a composition. Otherwise, for example,
+ * a composition event handler in web contents moved focus
+ * for committing the composition, returns false.
+ */
+ bool EnsureComposition(WidgetCompositionEvent& aCompositionEvent);
+
+ /**
+ * See comment of IsCopyToClipboardAllowed() for the detail.
+ */
+ virtual bool IsCopyToClipboardAllowedInternal() const {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ return !SelectionRef().IsCollapsed();
+ }
+
+ /**
+ * Helper for Is{Cut|Copy}CommandEnabled.
+ * Look for a listener for the given command, including up the target chain.
+ */
+ MOZ_CAN_RUN_SCRIPT bool CheckForClipboardCommandListener(
+ nsAtom* aCommand, EventMessage aEventMessage) const;
+
+ /**
+ * FireClipboardEvent() may dispatch a clipboard event.
+ *
+ * @param aEventMessage The event message which may be set to the
+ * dispatching event.
+ * @param aClipboardType Working with global clipboard or selection.
+ * @param aActionTaken [optional][out] If set to non-nullptr, will be
+ * set to true if the action for the event is
+ * handled or prevented default.
+ * @return false if dispatching event is canceled.
+ */
+ bool FireClipboardEvent(EventMessage aEventMessage, int32_t aClipboardType,
+ bool* aActionTaken = nullptr);
+
+ private:
+ nsCOMPtr<nsISelectionController> mSelectionController;
+ RefPtr<Document> mDocument;
+
+ AutoEditActionDataSetter* mEditActionData;
+
+ /**
+ * SetTextDirectionTo() sets text-direction of the root element.
+ * Should use SwitchTextDirectionTo() or ToggleTextDirection() instead.
+ * This is a helper class of them.
+ */
+ nsresult SetTextDirectionTo(TextDirection aTextDirection);
+
+ protected: // helper classes which may be used by friends
+ /**
+ * Stack based helper class for batching a collection of transactions inside
+ * a placeholder transaction. Different from AutoTransactionBatch, this
+ * notifies editor observers of before/end edit action handling, and
+ * dispatches "input" event if it's necessary.
+ */
+ class MOZ_RAII AutoPlaceholderBatch final {
+ public:
+ /**
+ * @param aRequesterFuncName function name which wants to end the batch.
+ * This won't be stored nor exposed to selection listeners etc, used only
+ * for logging. This MUST be alive when the destructor runs.
+ */
+ AutoPlaceholderBatch(EditorBase& aEditorBase,
+ ScrollSelectionIntoView aScrollSelectionIntoView,
+ const char* aRequesterFuncName)
+ : mEditorBase(aEditorBase),
+ mScrollSelectionIntoView(aScrollSelectionIntoView),
+ mRequesterFuncName(aRequesterFuncName) {
+ mEditorBase->BeginPlaceholderTransaction(*nsGkAtoms::_empty,
+ mRequesterFuncName);
+ }
+
+ AutoPlaceholderBatch(EditorBase& aEditorBase,
+ nsStaticAtom& aTransactionName,
+ ScrollSelectionIntoView aScrollSelectionIntoView,
+ const char* aRequesterFuncName)
+ : mEditorBase(aEditorBase),
+ mScrollSelectionIntoView(aScrollSelectionIntoView),
+ mRequesterFuncName(aRequesterFuncName) {
+ mEditorBase->BeginPlaceholderTransaction(aTransactionName,
+ mRequesterFuncName);
+ }
+
+ ~AutoPlaceholderBatch() {
+ mEditorBase->EndPlaceholderTransaction(mScrollSelectionIntoView,
+ mRequesterFuncName);
+ }
+
+ protected:
+ const OwningNonNull<EditorBase> mEditorBase;
+ const ScrollSelectionIntoView mScrollSelectionIntoView;
+ const char* const mRequesterFuncName;
+ };
+
+ /**
+ * AutoEditSubActionNotifier notifies editor of start to handle
+ * top level edit sub-action and end handling top level edit sub-action.
+ */
+ class MOZ_RAII AutoEditSubActionNotifier final {
+ public:
+ MOZ_CAN_RUN_SCRIPT AutoEditSubActionNotifier(
+ EditorBase& aEditorBase, EditSubAction aEditSubAction,
+ nsIEditor::EDirection aDirection, ErrorResult& aRv)
+ : mEditorBase(aEditorBase), mIsTopLevel(true) {
+ // The top level edit sub action has already be set if this is nested call
+ // XXX Looks like that this is not aware of unexpected nested edit action
+ // handling via selectionchange event listener or mutation event
+ // listener.
+ if (!mEditorBase.GetTopLevelEditSubAction()) {
+ MOZ_KnownLive(mEditorBase)
+ .OnStartToHandleTopLevelEditSubAction(aEditSubAction, aDirection,
+ aRv);
+ } else {
+ mIsTopLevel = false;
+ }
+ mEditorBase.OnStartToHandleEditSubAction();
+ }
+
+ MOZ_CAN_RUN_SCRIPT ~AutoEditSubActionNotifier() {
+ mEditorBase.OnEndHandlingEditSubAction();
+ if (mIsTopLevel) {
+ MOZ_KnownLive(mEditorBase).OnEndHandlingTopLevelEditSubAction();
+ }
+ }
+
+ protected:
+ EditorBase& mEditorBase;
+ bool mIsTopLevel;
+ };
+
+ /**
+ * Stack based helper class for turning off active selection adjustment
+ * by low level transactions
+ */
+ class MOZ_RAII AutoTransactionsConserveSelection final {
+ public:
+ explicit AutoTransactionsConserveSelection(EditorBase& aEditorBase)
+ : mEditorBase(aEditorBase),
+ mAllowedTransactionsToChangeSelection(
+ aEditorBase.AllowsTransactionsToChangeSelection()) {
+ mEditorBase.MakeThisAllowTransactionsToChangeSelection(false);
+ }
+
+ ~AutoTransactionsConserveSelection() {
+ mEditorBase.MakeThisAllowTransactionsToChangeSelection(
+ mAllowedTransactionsToChangeSelection);
+ }
+
+ protected:
+ EditorBase& mEditorBase;
+ bool mAllowedTransactionsToChangeSelection;
+ };
+
+ /***************************************************************************
+ * stack based helper class for batching reflow and paint requests.
+ */
+ class MOZ_RAII AutoUpdateViewBatch final {
+ public:
+ /**
+ * @param aRequesterFuncName function name which wants to end the batch.
+ * This won't be stored nor exposed to selection listeners etc, used only
+ * for logging. This MUST be alive when the destructor runs.
+ */
+ MOZ_CAN_RUN_SCRIPT explicit AutoUpdateViewBatch(
+ EditorBase& aEditorBase, const char* aRequesterFuncName)
+ : mEditorBase(aEditorBase), mRequesterFuncName(aRequesterFuncName) {
+ mEditorBase.BeginUpdateViewBatch(mRequesterFuncName);
+ }
+
+ MOZ_CAN_RUN_SCRIPT ~AutoUpdateViewBatch() {
+ MOZ_KnownLive(mEditorBase).EndUpdateViewBatch(mRequesterFuncName);
+ }
+
+ protected:
+ EditorBase& mEditorBase;
+ const char* const mRequesterFuncName;
+ };
+
+ protected:
+ enum Tristate { eTriUnset, eTriFalse, eTriTrue };
+
+ // MIME type of the doc we are editing.
+ nsString mContentMIMEType;
+
+ RefPtr<mozInlineSpellChecker> mInlineSpellChecker;
+ // Reference to text services document for mInlineSpellChecker.
+ RefPtr<TextServicesDocument> mTextServicesDocument;
+
+ RefPtr<TransactionManager> mTransactionManager;
+ // Cached root node.
+ RefPtr<Element> mRootElement;
+
+ // The form field as an event receiver.
+ nsCOMPtr<dom::EventTarget> mEventTarget;
+ RefPtr<EditorEventListener> mEventListener;
+ // Strong reference to placeholder for begin/end batch purposes.
+ RefPtr<PlaceholderTransaction> mPlaceholderTransaction;
+ // Name of placeholder transaction.
+ nsStaticAtom* mPlaceholderName;
+ // Saved selection state for placeholder transaction batching.
+ mozilla::Maybe<SelectionState> mSelState;
+ // IME composition this is not null between compositionstart and
+ // compositionend.
+ RefPtr<TextComposition> mComposition;
+
+ RefPtr<TextInputListener> mTextInputListener;
+
+ RefPtr<IMEContentObserver> mIMEContentObserver;
+
+ // These members cache last encoder and its type for the performance in
+ // TextEditor::ComputeTextValue() which is the implementation of
+ // `<input>.value` and `<textarea>.value`. See `GetAndInitDocEncoder()`.
+ mutable nsCOMPtr<nsIDocumentEncoder> mCachedDocumentEncoder;
+ mutable nsString mCachedDocumentEncoderType;
+
+ // Listens to all low level actions on the doc.
+ // Edit action listener is currently used by highlighter of the findbar and
+ // the spellchecker. So, we should reserve only 2 items.
+ typedef AutoTArray<OwningNonNull<nsIEditActionListener>, 2>
+ AutoActionListenerArray;
+ AutoActionListenerArray mActionListeners;
+ // Listen to overall doc state (dirty or not, just created, etc.).
+ // Document state listener is currently used by FinderHighlighter and
+ // BlueGriffon so that reserving only one is enough.
+ typedef AutoTArray<OwningNonNull<nsIDocumentStateListener>, 1>
+ AutoDocumentStateListenerArray;
+ AutoDocumentStateListenerArray mDocStateListeners;
+
+ // Number of modifications (for undo/redo stack).
+ uint32_t mModCount;
+ // Behavior flags. See nsIEditor.idl for the flags we use.
+ uint32_t mFlags;
+
+ int32_t mUpdateCount;
+
+ // Nesting count for batching.
+ int32_t mPlaceholderBatch;
+
+ int32_t mWrapColumn;
+ int32_t mNewlineHandling;
+ int32_t mCaretStyle;
+
+ // -1 = not initialized
+ int8_t mDocDirtyState;
+ // A Tristate value.
+ uint8_t mSpellcheckCheckboxState;
+
+ // If true, initialization was succeeded.
+ bool mInitSucceeded;
+ // If false, transactions should not change Selection even after modifying
+ // the DOM tree.
+ bool mAllowsTransactionsToChangeSelection;
+ // Whether PreDestroy has been called.
+ bool mDidPreDestroy;
+ // Whether PostCreate has been called.
+ bool mDidPostCreate;
+ bool mDispatchInputEvent;
+ // True while the instance is handling an edit sub-action.
+ bool mIsInEditSubAction;
+ // Whether caret is hidden forcibly.
+ bool mHidingCaret;
+ // Whether spellchecker dictionary is initialized after focused.
+ bool mSpellCheckerDictionaryUpdated;
+ // Whether we are an HTML editor class.
+ bool mIsHTMLEditorClass;
+
+ friend class AlignStateAtSelection; // AutoEditActionDataSetter,
+ // ToGenericNSResult
+ friend class AutoRangeArray; // IsSEditActionDataAvailable, SelectionRef
+ friend class CaretPoint; // AllowsTransactionsToChangeSelection,
+ // CollapseSelectionTo
+ friend class CompositionTransaction; // CollapseSelectionTo, DoDeleteText,
+ // DoInsertText, DoReplaceText,
+ // HideCaret, RangeupdaterRef
+ friend class DeleteNodeTransaction; // RangeUpdaterRef
+ friend class DeleteRangeTransaction; // AllowsTransactionsToChangeSelection,
+ // CollapseSelectionTo
+ friend class DeleteTextTransaction; // AllowsTransactionsToChangeSelection,
+ // DoDeleteText, DoInsertText,
+ // RangeUpdaterRef
+ friend class InsertNodeTransaction; // AllowsTransactionsToChangeSelection,
+ // CollapseSelectionTo,
+ // MarkElementDirty, ToGenericNSResult
+ friend class InsertTextTransaction; // AllowsTransactionsToChangeSelection,
+ // CollapseSelectionTo, DoDeleteText,
+ // DoInsertText, RangeUpdaterRef
+ friend class ListElementSelectionState; // AutoEditActionDataSetter,
+ // ToGenericNSResult
+ friend class ListItemElementSelectionState; // AutoEditActionDataSetter,
+ // ToGenericNSResult
+ friend class MoveNodeTransaction; // ToGenericNSResult
+ friend class ParagraphStateAtSelection; // AutoEditActionDataSetter,
+ // ToGenericNSResult
+ friend class PendingStyles; // GetEditAction,
+ // GetFirstSelectionStartPoint,
+ // SelectionRef
+ friend class ReplaceTextTransaction; // AllowsTransactionsToChangeSelection,
+ // CollapseSelectionTo, DoReplaceText,
+ // RangeUpdaterRef
+ friend class SplitNodeTransaction; // ToGenericNSResult
+ friend class WhiteSpaceVisibilityKeeper; // AutoTransactionsConserveSelection
+ friend class nsIEditor; // mIsHTMLEditorClass
+};
+
+} // namespace mozilla
+
+bool nsIEditor::IsTextEditor() const {
+ return !AsEditorBase()->mIsHTMLEditorClass;
+}
+
+bool nsIEditor::IsHTMLEditor() const {
+ return AsEditorBase()->mIsHTMLEditorClass;
+}
+
+mozilla::EditorBase* nsIEditor::AsEditorBase() {
+ return static_cast<mozilla::EditorBase*>(this);
+}
+
+const mozilla::EditorBase* nsIEditor::AsEditorBase() const {
+ return static_cast<const mozilla::EditorBase*>(this);
+}
+
+#endif // #ifndef mozilla_EditorBase_h