/* -*- 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/. */
#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"nsIClipboard.h"// for nsIClipboard::ClipboardType #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 AutoTArray #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
/** * 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 DataTransfer = dom::DataTransfer; using Document = dom::Document; using Element = dom::Element; using InterlinePosition = dom::Selection::InterlinePosition; using Selection = dom::Selection; using Text = dom::Text;
/** * The default constructor. This should suffice. the setting of the * interfaces is done after the construction of the editor class.
*/ explicit EditorBase(EditorType aEditorType);
/** * 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>. returnfalse;
} #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. returntrue; #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();
} returnfalse;
}
/** * 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();
} returnfalse;
}
/** * This checks whether the call with aPrincipal should or should not be * treated as user input.
*/
[[nodiscard]] staticbool TreatAsUserInput(nsIPrincipal* aPrincipal);
/** * Create a DataTransfer object that can be shared between the paste event * and pasting into a DOM element.
*/
already_AddRefed<DataTransfer> CreateDataTransferForPaste(
EventMessage aEventMessage,
nsIClipboard::ClipboardType aClipboardType) const;
/** * 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.
*/ enumclass 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 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) { returntrue;
} return mTransactionManager->DisableUndoRedo();
} bool ClearUndoRedo() { if (!mTransactionManager) { returntrue;
} 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())) { returnfalse;
} return IsCopyToClipboardAllowedInternal();
}
/** * HandleDropEvent() is called from EditorEventListener::Drop that is handler * of drop event.
*/
MOZ_CAN_RUN_SCRIPT nsresult HandleDropEvent(dom::DragEvent* aDropEvent);
/** * 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);
/** * 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.
*/ virtualbool IsEmpty() const = 0;
/** * 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.
*/ virtualbool 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.
*/ virtualbool CanPaste(nsIClipboard::ClipboardType 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())) { returnfalse;
} 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);
enumclass AllowBeforeInputEventCancelable {
No,
Yes,
};
enumclass PreventSetSelection {
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 aPreventSetSelection * Whether setting selection after replacing text. * If No, selection is the tail of replaced text. * If Yes, selection isn't changed. * @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,
PreventSetSelection aPreventSetSelection = PreventSetSelection::No,
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|.
*/ virtualbool 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 Yes if this should dispatch ePaste event * before pasting. Otherwise, No. * @param aDataTransfer The object containing the data to use for the * paste operation. May be nullptr, in which case * this will just get the data from the clipboard. * @param aPrincipal Set subject principal if it may be called by * JS. If set to nullptr, will be treated as * called by system.
*/ enumclass DispatchPasteEvent { No, Yes };
MOZ_CAN_RUN_SCRIPT nsresult
PasteAsAction(nsIClipboard::ClipboardType aClipboardType,
DispatchPasteEvent aDispatchPasteEvent,
DataTransfer* aDataTransfer = nullptr,
nsIPrincipal* aPrincipal = nullptr);
/** * Paste aTransferable at Selection. * * @param aTransferable Must not be nullptr. * @param aDispatchPasteEvent Yes if this should dispatch ePaste event * before pasting. Otherwise, No. * @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 PasteTransferableAsAction(
nsITransferable* aTransferable, DispatchPasteEvent aDispatchPasteEvent,
nsIPrincipal* aPrincipal = nullptr);
/** * PasteAsQuotationAsAction() pastes content in clipboard as quotation. * If the editor is TextEditor or in plaintext mode, will paste the content * with appending ">" to start of each line. * 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 Yes if this should dispatch ePaste event * before pasting. Otherwise, No. * @param aDataTransfer The object containing the data to use for the * paste operation. May be nullptr, in which case * this will just get the data from the clipboard. * @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
PasteAsQuotationAsAction(nsIClipboard::ClipboardType aClipboardType,
DispatchPasteEvent aDispatchPasteEvent,
DataTransfer* aDataTransfer = nullptr,
nsIPrincipal* aPrincipal = nullptr);
/** * Return true if `beforeinput` or `input` event is being dispatched.
*/
[[nodiscard]] bool IsDispatchingInputEvent() const { return mEditActionData && mEditActionData->IsDispatchingInputEvent();
}
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 { friendclass 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); 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);
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;
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.
*/ enumclass 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 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"); returnfalse;
} 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();
}
/** * 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;
}
/** * 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;
}
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(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, DataTransfer* aDataTransfer,
nsIClipboard::ClipboardType aClipboardType);
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);
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::eFormatBlockForHTMLCommand: 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::eMaintainWhiteSpaceVisibility: 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;
}
staticbool 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: returnfalse; default: returntrue;
}
}
bool NeedsToDispatchClipboardEvent() const { if (mHasTriedToDispatchClipboardEvent) { returnfalse;
} switch (mEditAction) { case EditAction::ePaste: case EditAction::ePasteAsQuotation: case EditAction::eCut: case EditAction::eCopy: returntrue; default: returnfalse;
}
}
// 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 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<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; // Whether the editor is dispatching a `beforeinput` or `input` event. bool mDispatchingInputEvent = false;
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.
****************************************************************************/
/** * 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();
}
/** * 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.
*/
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 AutoSelectionRestorer.
*/
SelectionState& SavedSelectionRef() {
MOZ_ASSERT(IsEditActionDataAvailable()); return mEditActionData->SavedSelectionRef();
} const SelectionState& SavedSelectionRef() const {
MOZ_ASSERT(IsEditActionDataAvailable()); return mEditActionData->SavedSelectionRef();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.