diff options
Diffstat (limited to 'editor/libeditor/HTMLEditor.h')
-rw-r--r-- | editor/libeditor/HTMLEditor.h | 4945 |
1 files changed, 4945 insertions, 0 deletions
diff --git a/editor/libeditor/HTMLEditor.h b/editor/libeditor/HTMLEditor.h new file mode 100644 index 0000000000..ed6351b32b --- /dev/null +++ b/editor/libeditor/HTMLEditor.h @@ -0,0 +1,4945 @@ +/* -*- 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_HTMLEditor_h +#define mozilla_HTMLEditor_h + +#include "mozilla/Attributes.h" +#include "mozilla/ComposerCommandsUpdater.h" +#include "mozilla/CSSEditUtils.h" +#include "mozilla/EditorUtils.h" +#include "mozilla/ManualNAC.h" +#include "mozilla/Result.h" +#include "mozilla/TextEditor.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/File.h" + +#include "nsAttrName.h" +#include "nsCOMPtr.h" +#include "nsIDocumentObserver.h" +#include "nsIDOMEventListener.h" +#include "nsIEditorMailSupport.h" +#include "nsIHTMLAbsPosEditor.h" +#include "nsIHTMLEditor.h" +#include "nsIHTMLInlineTableEditor.h" +#include "nsIHTMLObjectResizer.h" +#include "nsITableEditor.h" +#include "nsPoint.h" +#include "nsStubMutationObserver.h" +#include "nsTArray.h" + +class nsDocumentFragment; +class nsFrameSelection; +class nsHTMLDocument; +class nsITransferable; +class nsIClipboard; +class nsRange; +class nsStaticAtom; +class nsStyledElement; +class nsTableWrapperFrame; + +namespace mozilla { +class AlignStateAtSelection; +class AutoSelectionSetterAfterTableEdit; +class AutoSetTemporaryAncestorLimiter; +class EditActionResult; +class EditResult; +class EmptyEditableFunctor; +class JoinNodeTransaction; +class ListElementSelectionState; +class ListItemElementSelectionState; +class MoveNodeResult; +class ParagraphStateAtSelection; +class ResizerSelectionListener; +class Runnable; +class SplitNodeTransaction; +class SplitRangeOffFromNodeResult; +class SplitRangeOffResult; +class WhiteSpaceVisibilityKeeper; +class WSRunScanner; +class WSScanResult; +enum class EditSubAction : int32_t; +struct PropItem; +template <class T> +class OwningNonNull; +namespace dom { +class AbstractRange; +class Blob; +class DocumentFragment; +class Event; +class MouseEvent; +class StaticRange; +} // namespace dom +namespace widget { +struct IMEState; +} // namespace widget + +enum class ParagraphSeparator { div, p, br }; + +/** + * The HTML editor implementation.<br> + * Use to edit HTML document represented as a DOM tree. + */ +class HTMLEditor final : public TextEditor, + public nsIHTMLEditor, + public nsIHTMLObjectResizer, + public nsIHTMLAbsPosEditor, + public nsITableEditor, + public nsIHTMLInlineTableEditor, + public nsStubMutationObserver, + public nsIEditorMailSupport { + public: + /**************************************************************************** + * NOTE: DO NOT MAKE YOUR NEW METHODS PUBLIC IF they are called by other + * classes under libeditor except EditorEventListener and + * HTMLEditorEventListener because each public method which may fire + * eEditorInput event will need to instantiate new stack class for + * managing input type value of eEditorInput and cache some objects + * for smarter handling. In other words, when you add new root + * method to edit the DOM tree, you can make your new method public. + ****************************************************************************/ + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLEditor, TextEditor) + + // nsStubMutationObserver overrides + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + + // nsIHTMLEditor methods + NS_DECL_NSIHTMLEDITOR + + // nsIHTMLObjectResizer methods (implemented in HTMLObjectResizer.cpp) + NS_DECL_NSIHTMLOBJECTRESIZER + + // nsIHTMLAbsPosEditor methods (implemented in HTMLAbsPositionEditor.cpp) + NS_DECL_NSIHTMLABSPOSEDITOR + + // nsIHTMLInlineTableEditor methods (implemented in HTMLInlineTableEditor.cpp) + NS_DECL_NSIHTMLINLINETABLEEDITOR + + // nsIEditorMailSupport methods + NS_DECL_NSIEDITORMAILSUPPORT + + // nsITableEditor methods + NS_DECL_NSITABLEEDITOR + + // nsISelectionListener overrides + NS_DECL_NSISELECTIONLISTENER + + HTMLEditor(); + + static HTMLEditor* GetFrom(EditorBase* aEditorBase) { + return aEditorBase ? aEditorBase->AsHTMLEditor() : nullptr; + } + + MOZ_CAN_RUN_SCRIPT virtual void PreDestroy(bool aDestroyingFrames) override; + + bool GetReturnInParagraphCreatesNewParagraph(); + + // TextEditor overrides + MOZ_CAN_RUN_SCRIPT virtual nsresult Init(Document& aDoc, Element* aRoot, + nsISelectionController* aSelCon, + uint32_t aFlags, + const nsAString& aValue) override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD BeginningOfDocument() override; + MOZ_CAN_RUN_SCRIPT NS_IMETHOD SetFlags(uint32_t aFlags) override; + + /** + * 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 even if there + * are only empty blocks. + */ + virtual bool IsEmpty() const override; + + virtual bool CanPaste(int32_t aClipboardType) const override; + using EditorBase::CanPaste; + + MOZ_CAN_RUN_SCRIPT virtual nsresult PasteTransferableAsAction( + nsITransferable* aTransferable, + nsIPrincipal* aPrincipal = nullptr) override; + + MOZ_CAN_RUN_SCRIPT NS_IMETHOD DeleteNode(nsINode* aNode) override; + + MOZ_CAN_RUN_SCRIPT NS_IMETHOD InsertLineBreak() override; + + /** + * PreHandleMouseDown() and PreHandleMouseUp() are called before + * HTMLEditorEventListener handles them. The coming event may be + * non-acceptable event. + */ + void PreHandleMouseDown(const dom::MouseEvent& aMouseDownEvent); + void PreHandleMouseUp(const dom::MouseEvent& aMouseUpEvent); + + /** + * PreHandleSelectionChangeCommand() and PostHandleSelectionChangeCommand() + * are called before or after handling a command which may change selection + * and/or scroll position. + */ + void PreHandleSelectionChangeCommand(Command aCommand); + void PostHandleSelectionChangeCommand(Command aCommand); + + MOZ_CAN_RUN_SCRIPT virtual nsresult HandleKeyPressEvent( + WidgetKeyboardEvent* aKeyboardEvent) override; + virtual nsIContent* GetFocusedContent() const override; + virtual nsIContent* GetFocusedContentForIME() const override; + virtual bool IsActiveInDOMWindow() const override; + virtual dom::EventTarget* GetDOMEventTarget() const override; + virtual Element* FindSelectionRoot(nsINode* aNode) const override; + virtual bool IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) const override; + virtual nsresult GetPreferredIMEState(widget::IMEState* aState) override; + + /** + * GetBackgroundColorState() returns what the background color of the + * selection. + * + * @param aMixed true if there is more than one font color + * @param aOutColor Color string. "" is returned for none. + */ + MOZ_CAN_RUN_SCRIPT nsresult GetBackgroundColorState(bool* aMixed, + nsAString& aOutColor); + + /** + * PasteNoFormatting() pastes content in clipboard without any style + * information. + * + * @param aSelectionType nsIClipboard::kGlobalClipboard or + * nsIClipboard::kSelectionClipboard. + * @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 PasteNoFormattingAsAction( + int32_t aSelectionType, nsIPrincipal* aPrincipal = nullptr); + + /** + * PasteAsQuotationAsAction() pastes content in clipboard with newly created + * blockquote element. If the editor is in plaintext mode, will paste the + * content with appending ">" to start of each line. + * + * @param aClipboardType nsIClipboard::kGlobalClipboard or + * nsIClipboard::kSelectionClipboard. + * @param aDispatchPasteEvent true if this should dispatch ePaste event + * before pasting. Otherwise, false. + * @param aPrincipal Set subject principal if it may be called by + * JS. If set to nullptr, will be treated as + * called by system. + */ + MOZ_CAN_RUN_SCRIPT virtual nsresult PasteAsQuotationAsAction( + int32_t aClipboardType, bool aDispatchPasteEvent, + nsIPrincipal* aPrincipal = nullptr) override; + + /** + * Can we paste |aTransferable| or, if |aTransferable| is null, will a call + * to pasteTransferable later possibly succeed if given an instance of + * nsITransferable then? True if the doc is modifiable, and, if + * |aTransfeable| is non-null, we have pasteable data in |aTransfeable|. + */ + virtual bool CanPasteTransferable(nsITransferable* aTransferable) override; + + /** + * InsertLineBreakAsAction() is called when user inputs a line break with + * Shift + Enter or something. + * + * @param aPrincipal Set subject principal if it may be called by + * JS. If set to nullptr, will be treated as + * called by system. + */ + MOZ_CAN_RUN_SCRIPT virtual nsresult InsertLineBreakAsAction( + nsIPrincipal* aPrincipal = nullptr) override; + + /** + * InsertParagraphSeparatorAsAction() is called when user tries to separate + * current paragraph with Enter key press in HTMLEditor or something. + * + * @param aPrincipal Set subject principal if it may be called by + * JS. If set to nullptr, will be treated as + * called by system. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + InsertParagraphSeparatorAsAction(nsIPrincipal* aPrincipal = nullptr); + + MOZ_CAN_RUN_SCRIPT nsresult + InsertElementAtSelectionAsAction(Element* aElement, bool aDeleteSelection, + nsIPrincipal* aPrincipal = nullptr); + + MOZ_CAN_RUN_SCRIPT nsresult InsertLinkAroundSelectionAsAction( + Element* aAnchorElement, nsIPrincipal* aPrincipal = nullptr); + + /** + * CreateElementWithDefaults() creates new element whose name is + * aTagName with some default attributes are set. Note that this is a + * public utility method. I.e., just creates element, not insert it + * into the DOM tree. + * NOTE: This is available for internal use too since this does not change + * the DOM tree nor undo transactions, and does not refer Selection, + * etc. + * + * @param aTagName The new element's tag name. If the name is + * one of "href", "anchor" or "namedanchor", + * this creates an <a> element. + * @return Newly created element. + */ + MOZ_CAN_RUN_SCRIPT already_AddRefed<Element> CreateElementWithDefaults( + const nsAtom& aTagName); + + /** + * Indent or outdent content around Selection. + * + * @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 + IndentAsAction(nsIPrincipal* aPrincipal = nullptr); + MOZ_CAN_RUN_SCRIPT nsresult + OutdentAsAction(nsIPrincipal* aPrincipal = nullptr); + + MOZ_CAN_RUN_SCRIPT nsresult SetParagraphFormatAsAction( + const nsAString& aParagraphFormat, nsIPrincipal* aPrincipal = nullptr); + + MOZ_CAN_RUN_SCRIPT nsresult AlignAsAction(const nsAString& aAlignType, + nsIPrincipal* aPrincipal = nullptr); + + MOZ_CAN_RUN_SCRIPT nsresult RemoveListAsAction( + const nsAString& aListType, nsIPrincipal* aPrincipal = nullptr); + + /** + * MakeOrChangeListAsAction() makes selected hard lines list element(s). + * + * @param aListElementTagName The new list element tag name. Must be + * nsGkAtoms::ul, nsGkAtoms::ol or + * nsGkAtoms::dl. + * @param aBulletType If this is not empty string, it's set + * to `type` attribute of new list item + * elements. Otherwise, existing `type` + * attributes will be removed. + * @param aSelectAllOfCurrentList Yes if this should treat all of + * ancestor list element at selection. + */ + enum class SelectAllOfCurrentList { Yes, No }; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult MakeOrChangeListAsAction( + nsAtom& aListElementTagName, const nsAString& aBulletType, + SelectAllOfCurrentList aSelectAllOfCurrentList, + nsIPrincipal* aPrincipal = nullptr); + + /** + * If aTargetElement is a resizer, start to drag the resizer. Otherwise, if + * aTargetElement is the grabber, start to handle drag gester on it. + * + * @param aMouseDownEvent A `mousedown` event fired on aTargetElement. + * @param aEventTargetElement The target element being pressed. This must + * be same as explicit original event target of + * aMouseDownEvent. + */ + MOZ_CAN_RUN_SCRIPT nsresult StartToDragResizerOrHandleDragGestureOnGrabber( + dom::MouseEvent& aMouseDownEvent, Element& aEventTargetElement); + + /** + * If the editor is handling dragging a resizer, handling drag gesture on + * the grabber or dragging the grabber, this finalize it. Otherwise, + * does nothing. + * + * @param aClientPoint The final point of the drag. + */ + MOZ_CAN_RUN_SCRIPT nsresult + StopDraggingResizerOrGrabberAt(const CSSIntPoint& aClientPoint); + + /** + * If the editor is handling dragging a resizer, handling drag gesture to + * start dragging the grabber or dragging the grabber, this method updates + * it's position. + * + * @param aClientPoint The new point of the drag. + */ + MOZ_CAN_RUN_SCRIPT nsresult + UpdateResizerOrGrabberPositionTo(const CSSIntPoint& aClientPoint); + + /** + * IsCSSEnabled() returns true if this editor treats styles with style + * attribute of HTML elements. Otherwise, if this editor treats all styles + * with "font style elements" like <b>, <i>, etc, and <blockquote> to indent, + * align attribute to align contents, returns false. + */ + bool IsCSSEnabled() const { + // TODO: removal of mCSSAware and use only the presence of mCSSEditUtils + return mCSSAware && mCSSEditUtils && mCSSEditUtils->IsCSSPrefChecked(); + } + + /** + * Enable/disable object resizers for <img> elements, <table> elements, + * absolute positioned elements (required absolute position editor enabled). + */ + MOZ_CAN_RUN_SCRIPT void EnableObjectResizer(bool aEnable) { + if (mIsObjectResizingEnabled == aEnable) { + return; + } + + AutoEditActionDataSetter editActionData( + *this, EditAction::eEnableOrDisableResizer); + if (NS_WARN_IF(!editActionData.CanHandle())) { + return; + } + + mIsObjectResizingEnabled = aEnable; + RefreshEditingUI(); + } + bool IsObjectResizerEnabled() const { return mIsObjectResizingEnabled; } + + Element* GetResizerTarget() const { return mResizedObject; } + + /** + * Enable/disable inline table editor, e.g., adding new row or column, + * removing existing row or column. + */ + MOZ_CAN_RUN_SCRIPT void EnableInlineTableEditor(bool aEnable) { + if (mIsInlineTableEditingEnabled == aEnable) { + return; + } + + AutoEditActionDataSetter editActionData( + *this, EditAction::eEnableOrDisableInlineTableEditingUI); + if (NS_WARN_IF(!editActionData.CanHandle())) { + return; + } + + mIsInlineTableEditingEnabled = aEnable; + RefreshEditingUI(); + } + bool IsInlineTableEditorEnabled() const { + return mIsInlineTableEditingEnabled; + } + + /** + * Enable/disable absolute position editor, resizing absolute positioned + * elements (required object resizers enabled) or positioning them with + * dragging grabber. + */ + MOZ_CAN_RUN_SCRIPT void EnableAbsolutePositionEditor(bool aEnable) { + if (mIsAbsolutelyPositioningEnabled == aEnable) { + return; + } + + AutoEditActionDataSetter editActionData( + *this, EditAction::eEnableOrDisableAbsolutePositionEditor); + if (NS_WARN_IF(!editActionData.CanHandle())) { + return; + } + + mIsAbsolutelyPositioningEnabled = aEnable; + RefreshEditingUI(); + } + bool IsAbsolutePositionEditorEnabled() const { + return mIsAbsolutelyPositioningEnabled; + } + + // non-virtual methods of interface methods + + /** + * returns the deepest absolutely positioned container of the selection + * if it exists or null. + */ + MOZ_CAN_RUN_SCRIPT already_AddRefed<Element> + GetAbsolutelyPositionedSelectionContainer() const; + + Element* GetPositionedElement() const { return mAbsolutelyPositionedObject; } + + /** + * extracts the selection from the normal flow of the document and + * positions it. + * + * @param aEnabled [IN] true to absolutely position the selection, + * false to put it back in the normal flow + * @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 SetSelectionToAbsoluteOrStaticAsAction( + bool aEnabled, nsIPrincipal* aPrincipal = nullptr); + + /** + * returns the absolute z-index of a positioned element. Never returns 'auto' + * @return the z-index of the element + * @param aElement [IN] the element. + */ + MOZ_CAN_RUN_SCRIPT int32_t GetZIndex(Element& aElement); + + /** + * adds aChange to the z-index of the currently positioned element. + * + * @param aChange [IN] relative change to apply to current z-index + * @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 + AddZIndexAsAction(int32_t aChange, nsIPrincipal* aPrincipal = nullptr); + + MOZ_CAN_RUN_SCRIPT nsresult SetBackgroundColorAsAction( + const nsAString& aColor, nsIPrincipal* aPrincipal = nullptr); + + /** + * SetInlinePropertyAsAction() sets a property which changes inline style of + * text. E.g., bold, italic, super and sub. + * This automatically removes exclusive style, however, treats all changes + * as a transaction. + * + * @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 SetInlinePropertyAsAction( + nsAtom& aProperty, nsAtom* aAttribute, const nsAString& aValue, + nsIPrincipal* aPrincipal = nullptr); + + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult GetInlineProperty( + nsAtom* aHTMLProperty, nsAtom* aAttribute, const nsAString& aValue, + bool* aFirst, bool* aAny, bool* aAll) const; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult GetInlinePropertyWithAttrValue( + nsAtom* aHTMLProperty, nsAtom* aAttribute, const nsAString& aValue, + bool* aFirst, bool* aAny, bool* aAll, nsAString& outValue); + + /** + * RemoveInlinePropertyAsAction() removes a property which changes inline + * style of text. E.g., bold, italic, super and sub. + * + * @param aHTMLProperty Tag name whcih represents the inline style you want + * to remove. E.g., nsGkAtoms::strong, nsGkAtoms::b, + * etc. If nsGkAtoms::href, <a> element which has + * href attribute will be removed. + * If nsGkAtoms::name, <a> element which has non-empty + * name attribute will be removed. + * @param aAttribute If aHTMLProperty is nsGkAtoms::font, aAttribute should + * be nsGkAtoms::fase, nsGkAtoms::size, nsGkAtoms::color + * or nsGkAtoms::bgcolor. Otherwise, set nullptr. + * Must not use nsGkAtoms::_empty here. + * @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 RemoveInlinePropertyAsAction( + nsStaticAtom& aHTMLProperty, nsStaticAtom* aAttribute, + nsIPrincipal* aPrincipal = nullptr); + + MOZ_CAN_RUN_SCRIPT nsresult + RemoveAllInlinePropertiesAsAction(nsIPrincipal* aPrincipal = nullptr); + + MOZ_CAN_RUN_SCRIPT nsresult + IncreaseFontSizeAsAction(nsIPrincipal* aPrincipal = nullptr); + + MOZ_CAN_RUN_SCRIPT nsresult + DecreaseFontSizeAsAction(nsIPrincipal* aPrincipal = nullptr); + + /** + * GetFontColorState() returns foreground color information in first + * range of Selection. + * If first range of Selection is collapsed and there is a cache of style for + * new text, aIsMixed is set to false and aColor is set to the cached color. + * If first range of Selection is collapsed and there is no cached color, + * this returns the color of the node, aIsMixed is set to false and aColor is + * set to the color. + * If first range of Selection is not collapsed, this collects colors of + * each node in the range. If there are two or more colors, aIsMixed is set + * to true and aColor is truncated. If only one color is set to all of the + * range, aIsMixed is set to false and aColor is set to the color. + * If there is no Selection ranges, aIsMixed is set to false and aColor is + * truncated. + * + * @param aIsMixed Must not be nullptr. This is set to true + * if there is two or more colors in first + * range of Selection. + * @param aColor Returns the color if only one color is set to + * all of first range in Selection. Otherwise, + * returns empty string. + * @return Returns error only when illegal cases, e.g., + * Selection instance has gone, first range + * Selection is broken. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + GetFontColorState(bool* aIsMixed, nsAString& aColor); + + /** + * SetComposerCommandsUpdater() sets or unsets mComposerCommandsUpdater. + * This will crash in debug build if the editor already has an instance + * but called with another instance. + */ + void SetComposerCommandsUpdater( + ComposerCommandsUpdater* aComposerCommandsUpdater) { + MOZ_ASSERT(!aComposerCommandsUpdater || !mComposerCommandsUpdater || + aComposerCommandsUpdater == mComposerCommandsUpdater); + mComposerCommandsUpdater = aComposerCommandsUpdater; + } + + nsStaticAtom& DefaultParagraphSeparatorTagName() const { + return HTMLEditor::ToParagraphSeparatorTagName(mDefaultParagraphSeparator); + } + ParagraphSeparator GetDefaultParagraphSeparator() const { + return mDefaultParagraphSeparator; + } + void SetDefaultParagraphSeparator(ParagraphSeparator aSep) { + mDefaultParagraphSeparator = aSep; + } + static nsStaticAtom& ToParagraphSeparatorTagName( + ParagraphSeparator aSeparator) { + switch (aSeparator) { + case ParagraphSeparator::div: + return *nsGkAtoms::div; + case ParagraphSeparator::p: + return *nsGkAtoms::p; + case ParagraphSeparator::br: + return *nsGkAtoms::br; + default: + MOZ_ASSERT_UNREACHABLE("New paragraph separator isn't handled here"); + return *nsGkAtoms::div; + } + } + + /** + * Modifies the table containing the selection according to the + * activation of an inline table editing UI element + * @param aUIAnonymousElement [IN] the inline table editing UI element + */ + MOZ_CAN_RUN_SCRIPT nsresult + DoInlineTableEditingAction(const Element& aUIAnonymousElement); + + /** + * GetInclusiveAncestorByTagName() looks for an element node whose name + * matches aTagName from aNode or anchor node of Selection to <body> element. + * + * @param aTagName The tag name which you want to look for. + * Must not be nsGkAtoms::_empty. + * If nsGkAtoms::list, the result may be <ul>, <ol> or + * <dl> element. + * If nsGkAtoms::td, the result may be <td> or <th>. + * If nsGkAtoms::href, the result may be <a> element + * which has "href" attribute with non-empty value. + * If nsGkAtoms::anchor, the result may be <a> which + * has "name" attribute with non-empty value. + * @param aContent Start node to look for the result. + * @return If an element which matches aTagName, returns + * an Element. Otherwise, nullptr. + */ + Element* GetInclusiveAncestorByTagName(const nsStaticAtom& aTagName, + nsIContent& aContent) const; + + /** + * Get an active editor's editing host in DOM window. If this editor isn't + * active in the DOM window, this returns NULL. + */ + Element* GetActiveEditingHost() const; + + /** + * Retruns true if we're in designMode. + */ + bool IsInDesignMode() const { + Document* document = GetDocument(); + return document && document->HasFlag(NODE_IS_EDITABLE); + } + + /** + * NotifyEditingHostMaybeChanged() is called when new element becomes + * contenteditable when the document already had contenteditable elements. + */ + void NotifyEditingHostMaybeChanged(); + + /** Insert a string as quoted text + * (whose representation is dependant on the editor type), + * replacing the selected text (if any). + * + * @param aQuotedText The actual text to be quoted + * @parem aNodeInserted Return the node which was inserted. + */ + MOZ_CAN_RUN_SCRIPT // USED_BY_COMM_CENTRAL + nsresult + InsertAsQuotation(const nsAString& aQuotedText, nsINode** aNodeInserted); + + /** + * Inserts a plaintext string at the current location, + * with special processing for lines beginning with ">", + * which will be treated as mail quotes and inserted + * as plaintext quoted blocks. + * If the selection is not collapsed, the selection is deleted + * and the insertion takes place at the resulting collapsed selection. + * + * @param aString the string to be inserted + */ + MOZ_CAN_RUN_SCRIPT nsresult + InsertTextWithQuotations(const nsAString& aStringToInsert); + + MOZ_CAN_RUN_SCRIPT nsresult InsertHTMLAsAction( + const nsAString& aInString, nsIPrincipal* aPrincipal = nullptr); + + 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. + ****************************************************************************/ + + /** + * InsertBRElementWithTransaction() creates a <br> element and inserts it + * before aPointToInsert. Then, tries to collapse selection at or after the + * new <br> node if aSelect is not eNone. + * + * @param aPointToInsert The DOM point where should be <br> node inserted + * before. + * @param aSelect If eNone, this won't change selection. + * If eNext, selection will be collapsed after + * the <br> element. + * If ePrevious, selection will be collapsed at + * the <br> element. + * @return The new <br> node. If failed to create new + * <br> node, returns nullptr. + */ + MOZ_CAN_RUN_SCRIPT already_AddRefed<Element> InsertBRElementWithTransaction( + const EditorDOMPoint& aPointToInsert, EDirection aSelect = eNone); + + /** + * DeleteNodeWithTransaction() removes aContent from the DOM tree if it's + * modifiable. Note that this is not an override of same method of + * EditorBase. + * + * @param aContent The node to be removed from the DOM tree. + */ + MOZ_CAN_RUN_SCRIPT nsresult DeleteNodeWithTransaction(nsIContent& aContent); + + /** + * DeleteTextWithTransaction() removes text in the range from aTextNode if + * it's modifiable. Note that this not an override of same method of + * EditorBase. + * + * @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); + + /** + * ReplaceTextWithTransaction() replaces text in the range with + * aStringToInsert. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult ReplaceTextWithTransaction( + dom::Text& aTextNode, uint32_t aOffset, uint32_t aLength, + const nsAString& aStringToInsert); + + /** + * InsertTextWithTransaction() inserts aStringToInsert at aPointToInsert. + */ + MOZ_CAN_RUN_SCRIPT virtual nsresult InsertTextWithTransaction( + Document& aDocument, const nsAString& aStringToInsert, + const EditorRawDOMPoint& aPointToInsert, + EditorRawDOMPoint* aPointAfterInsertedString = nullptr) override; + + /** + * CopyLastEditableChildStyles() clones inline container elements into + * aPreviousBlock to aNewBlock to keep using same style in it. + * + * @param aPreviousBlock The previous block element. All inline + * elements which are last sibling of each level + * are cloned to aNewBlock. + * @param aNewBlock New block container element. + * @param aNewBRElement If this method creates a new <br> element for + * placeholder, this is set to the new <br> + * element. + */ + MOZ_CAN_RUN_SCRIPT nsresult CopyLastEditableChildStylesWithTransaction( + Element& aPreviousBlock, Element& aNewBlock, + RefPtr<Element>* aNewBRElement); + + /** + * RemoveBlockContainerWithTransaction() removes aElement from the DOM tree + * but moves its all children to its parent node and if its parent needs <br> + * element to have at least one line-height, this inserts <br> element + * automatically. + * + * @param aElement Block element to be removed. + */ + MOZ_CAN_RUN_SCRIPT nsresult + RemoveBlockContainerWithTransaction(Element& aElement); + + virtual Element* GetEditorRoot() const override; + MOZ_CAN_RUN_SCRIPT virtual nsresult RemoveAttributeOrEquivalent( + Element* aElement, nsAtom* aAttribute, + bool aSuppressTransaction) override; + MOZ_CAN_RUN_SCRIPT virtual nsresult SetAttributeOrEquivalent( + Element* aElement, nsAtom* aAttribute, const nsAString& aValue, + bool aSuppressTransaction) override; + using EditorBase::RemoveAttributeOrEquivalent; + using EditorBase::SetAttributeOrEquivalent; + + /** + * Returns container element of ranges in Selection. If Selection is + * collapsed, returns focus container node (or its parent element). + * If Selection selects only one element node, returns the element node. + * If Selection is only one range, returns common ancestor of the range. + * XXX If there are two or more Selection ranges, this returns parent node + * of start container of a range which starts with different node from + * start container of the first range. + */ + Element* GetSelectionContainerElement() const; + + /** + * DeleteTableCellContentsWithTransaction() removes any contents in cell + * elements. If two or more cell elements are selected, this removes + * all selected cells' contents. Otherwise, this removes contents of + * a cell which contains first selection range. This does not return + * error even if selection is not in cell element, just does nothing. + */ + MOZ_CAN_RUN_SCRIPT nsresult DeleteTableCellContentsWithTransaction(); + + static void IsNextCharInNodeWhiteSpace(nsIContent* aContent, int32_t aOffset, + bool* outIsSpace, bool* outIsNBSP, + nsIContent** outNode = nullptr, + int32_t* outOffset = 0); + static void IsPrevCharInNodeWhiteSpace(nsIContent* aContent, int32_t aOffset, + bool* outIsSpace, bool* outIsNBSP, + nsIContent** outNode = nullptr, + int32_t* outOffset = 0); + + /** + * extracts an element from the normal flow of the document and + * positions it, and puts it back in the normal flow. + * @param aElement [IN] the element + * @param aEnabled [IN] true to absolutely position the element, + * false to put it back in the normal flow + */ + MOZ_CAN_RUN_SCRIPT nsresult SetPositionToAbsoluteOrStatic(Element& aElement, + bool aEnabled); + + /** + * adds aChange to the z-index of an arbitrary element. + * @param aElement [IN] the element + * @param aChange [IN] relative change to apply to current z-index of + * the element + * @return The new z-index of the element + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<int32_t, nsresult> + AddZIndexWithTransaction(nsStyledElement& aStyledElement, int32_t aChange); + + /** + * Join together any adjacent editable text nodes in the range. + */ + MOZ_CAN_RUN_SCRIPT nsresult CollapseAdjacentTextNodes(nsRange& aRange); + + /** + * IsInVisibleTextFrames() returns true if all text in aText is in visible + * text frames. Callers have to guarantee that there is no pending reflow. + */ + bool IsInVisibleTextFrames(dom::Text& aText) const; + + /** + * IsVisibleTextNode() returns true if aText has visible text. If it has + * only white-spaces and they are collapsed, returns false. + */ + bool IsVisibleTextNode(Text& aText) const; + + /** + * IsEmptyNode() figures out if aNode is an empty node. A block can have + * children and still be considered empty, if the children are empty or + * non-editable. + */ + bool IsEmptyNode(nsINode& aNode, bool aSingleBRDoesntCount = false, + bool aListOrCellNotEmpty = false, + bool aSafeToAskFrames = false) const { + bool seenBR = false; + return IsEmptyNodeImpl(aNode, aSingleBRDoesntCount, aListOrCellNotEmpty, + aSafeToAskFrames, &seenBR); + } + + bool IsEmptyNodeImpl(nsINode& aNode, bool aSingleBRDoesntCount, + bool aListOrCellNotEmpty, bool aSafeToAskFrames, + bool* aSeenBR) const; + + static bool HasAttributes(Element* aElement) { + MOZ_ASSERT(aElement); + uint32_t attrCount = aElement->GetAttrCount(); + return attrCount > 1 || + (1 == attrCount && + !aElement->GetAttrNameAt(0)->Equals(nsGkAtoms::mozdirty)); + } + + /** + * Content-based query returns true if <aProperty aAttribute=aValue> effects + * aNode. If <aProperty aAttribute=aValue> contains aNode, but + * <aProperty aAttribute=SomeOtherValue> also contains aNode and the second is + * more deeply nested than the first, then the first does not effect aNode. + * + * @param aNode The target of the query + * @param aProperty The property that we are querying for + * @param aAttribute The attribute of aProperty, example: color in + * <FONT color="blue"> May be null. + * @param aValue The value of aAttribute, example: blue in + * <FONT color="blue"> May be null. Ignored if aAttribute + * is null. + * @param outValue [OUT] the value of the attribute, if aIsSet is true + * @return true if <aProperty aAttribute=aValue> effects + * aNode. + * + * The nsIContent variant returns aIsSet instead of using an out parameter. + */ + static bool IsTextPropertySetByContent(nsINode* aNode, nsAtom* aProperty, + nsAtom* aAttribute, + const nsAString* aValue, + nsAString* outValue = nullptr); + + static dom::Element* GetLinkElement(nsINode* aNode); + + /** + * Small utility routine to test if a break node is visible to user. + */ + bool IsVisibleBRElement(const nsINode* aNode) const; + + /** + * Helper routines for font size changing. + */ + enum class FontSize { incr, decr }; + MOZ_CAN_RUN_SCRIPT nsresult RelativeFontChangeOnTextNode(FontSize aDir, + Text& aTextNode, + int32_t aStartOffset, + int32_t aEndOffset); + + MOZ_CAN_RUN_SCRIPT nsresult SetInlinePropertyOnNode(nsIContent& aNode, + nsAtom& aProperty, + nsAtom* aAttribute, + const nsAString& aValue); + + /** + * SplitAncestorStyledInlineElementsAtRangeEdges() splits all ancestor inline + * elements in the block at both aStartPoint and aEndPoint if given style + * matches with some of them. + * + * @param aStartPoint Start of range to split ancestor inline elements. + * @param aEndPoint End of range to split ancestor inline elements. + * @param aProperty The style tag name which you want to split. Set + * nullptr if you want to split any styled elements. + * @param aAttribute Attribute name if aProperty has some styles like + * nsGkAtoms::font. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT SplitRangeOffResult + SplitAncestorStyledInlineElementsAtRangeEdges( + const EditorDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint, + nsAtom* aProperty, nsAtom* aAttribute); + + /** + * SplitAncestorStyledInlineElementsAt() splits ancestor inline elements at + * aPointToSplit if specified style matches with them. + * + * @param aPointToSplit The point to split style at. + * @param aProperty The style tag name which you want to split. + * Set nullptr if you want to split any styled + * elements. + * @param aAttribute Attribute name if aProperty has some styles + * like nsGkAtoms::font. + * @return The result of SplitNodeDeepWithTransaction() + * with topmost split element. If this didn't + * find inline elements to be split, Handled() + * returns false. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT SplitNodeResult + SplitAncestorStyledInlineElementsAt(const EditorDOMPoint& aPointToSplit, + nsAtom* aProperty, nsAtom* aAttribute); + + /** + * GetPriorHTMLSibling() returns the previous editable sibling, if there is + * one within the parent, optionally skipping text nodes that are only + * white-space. + */ + enum class SkipWhiteSpace { Yes, No }; + nsIContent* GetPriorHTMLSibling(nsINode* aNode, + SkipWhiteSpace = SkipWhiteSpace::No) const; + + /** + * GetNextHTMLSibling() returns the next editable sibling, if there is + * one within the parent, optionally skipping text nodes that are only + * white-space. + */ + nsIContent* GetNextHTMLSibling(nsINode* aNode, + SkipWhiteSpace = SkipWhiteSpace::No) const; + + // Helper for GetPriorHTMLSibling/GetNextHTMLSibling. + static bool SkippableWhiteSpace(nsINode* aNode, SkipWhiteSpace aSkipWS) { + return aSkipWS == SkipWhiteSpace::Yes && aNode->IsText() && + aNode->AsText()->TextIsOnlyWhitespace(); + } + + /** + * GetPreviousHTMLElementOrText*() methods are similar to + * EditorBase::GetPreviousElementOrText*() but this won't return nodes + * outside active editing host. + */ + nsIContent* GetPreviousHTMLElementOrText(const nsINode& aNode) const { + return GetPreviousHTMLElementOrTextInternal(aNode, false); + } + nsIContent* GetPreviousHTMLElementOrTextInBlock(const nsINode& aNode) const { + return GetPreviousHTMLElementOrTextInternal(aNode, true); + } + template <typename PT, typename CT> + nsIContent* GetPreviousHTMLElementOrText( + const EditorDOMPointBase<PT, CT>& aPoint) const { + return GetPreviousHTMLElementOrTextInternal(aPoint, false); + } + template <typename PT, typename CT> + nsIContent* GetPreviousHTMLElementOrTextInBlock( + const EditorDOMPointBase<PT, CT>& aPoint) const { + return GetPreviousHTMLElementOrTextInternal(aPoint, true); + } + + /** + * GetPreviousHTMLElementOrTextInternal() methods are common implementation + * of above methods. Please don't use this method directly. + */ + nsIContent* GetPreviousHTMLElementOrTextInternal(const nsINode& aNode, + bool aNoBlockCrossing) const; + template <typename PT, typename CT> + nsIContent* GetPreviousHTMLElementOrTextInternal( + const EditorDOMPointBase<PT, CT>& aPoint, bool aNoBlockCrossing) const; + + /** + * GetPreviousEditableHTMLNode*() methods are similar to + * EditorBase::GetPreviousEditableNode() but this won't return nodes outside + * active editing host. + */ + nsIContent* GetPreviousEditableHTMLNode(nsINode& aNode) const { + return GetPreviousEditableHTMLNodeInternal(aNode, false); + } + nsIContent* GetPreviousEditableHTMLNodeInBlock(nsINode& aNode) const { + return GetPreviousEditableHTMLNodeInternal(aNode, true); + } + template <typename PT, typename CT> + nsIContent* GetPreviousEditableHTMLNode( + const EditorDOMPointBase<PT, CT>& aPoint) const { + return GetPreviousEditableHTMLNodeInternal(aPoint, false); + } + template <typename PT, typename CT> + nsIContent* GetPreviousEditableHTMLNodeInBlock( + const EditorDOMPointBase<PT, CT>& aPoint) const { + return GetPreviousEditableHTMLNodeInternal(aPoint, true); + } + + /** + * GetPreviousEditableHTMLNodeInternal() methods are common implementation + * of above methods. Please don't use this method directly. + */ + nsIContent* GetPreviousEditableHTMLNodeInternal(nsINode& aNode, + bool aNoBlockCrossing) const; + template <typename PT, typename CT> + nsIContent* GetPreviousEditableHTMLNodeInternal( + const EditorDOMPointBase<PT, CT>& aPoint, bool aNoBlockCrossing) const; + + /** + * GetNextHTMLElementOrText*() methods are similar to + * EditorBase::GetNextElementOrText*() but this won't return nodes outside + * active editing host. + * + * Note that same as EditorBase::GetTextEditableNode(), methods which take + * |const EditorRawDOMPoint&| start to search from the node pointed by it. + * On the other hand, methods which take |nsINode&| start to search from + * next node of aNode. + */ + nsIContent* GetNextHTMLElementOrText(const nsINode& aNode) const { + return GetNextHTMLElementOrTextInternal(aNode, false); + } + nsIContent* GetNextHTMLElementOrTextInBlock(const nsINode& aNode) const { + return GetNextHTMLElementOrTextInternal(aNode, true); + } + template <typename PT, typename CT> + nsIContent* GetNextHTMLElementOrText( + const EditorDOMPointBase<PT, CT>& aPoint) const { + return GetNextHTMLElementOrTextInternal(aPoint, false); + } + template <typename PT, typename CT> + nsIContent* GetNextHTMLElementOrTextInBlock( + const EditorDOMPointBase<PT, CT>& aPoint) const { + return GetNextHTMLElementOrTextInternal(aPoint, true); + } + + /** + * GetNextHTMLNodeInternal() methods are common implementation + * of above methods. Please don't use this method directly. + */ + nsIContent* GetNextHTMLElementOrTextInternal(const nsINode& aNode, + bool aNoBlockCrossing) const; + template <typename PT, typename CT> + nsIContent* GetNextHTMLElementOrTextInternal( + const EditorDOMPointBase<PT, CT>& aPoint, bool aNoBlockCrossing) const; + + /** + * GetNextEditableHTMLNode*() methods are similar to + * EditorBase::GetNextEditableNode() but this won't return nodes outside + * active editing host. + * + * Note that same as EditorBase::GetTextEditableNode(), methods which take + * |const EditorRawDOMPoint&| start to search from the node pointed by it. + * On the other hand, methods which take |nsINode&| start to search from + * next node of aNode. + */ + nsIContent* GetNextEditableHTMLNode(nsINode& aNode) const { + return GetNextEditableHTMLNodeInternal(aNode, false); + } + nsIContent* GetNextEditableHTMLNodeInBlock(nsINode& aNode) const { + return GetNextEditableHTMLNodeInternal(aNode, true); + } + template <typename PT, typename CT> + nsIContent* GetNextEditableHTMLNode( + const EditorDOMPointBase<PT, CT>& aPoint) const { + return GetNextEditableHTMLNodeInternal(aPoint, false); + } + template <typename PT, typename CT> + nsIContent* GetNextEditableHTMLNodeInBlock( + const EditorDOMPointBase<PT, CT>& aPoint) const { + return GetNextEditableHTMLNodeInternal(aPoint, true); + } + + /** + * GetNextEditableHTMLNodeInternal() methods are common implementation + * of above methods. Please don't use this method directly. + */ + nsIContent* GetNextEditableHTMLNodeInternal(nsINode& aNode, + bool aNoBlockCrossing) const; + template <typename PT, typename CT> + nsIContent* GetNextEditableHTMLNodeInternal( + const EditorDOMPointBase<PT, CT>& aPoint, bool aNoBlockCrossing) const; + + bool IsFirstEditableChild(nsINode* aNode) const; + bool IsLastEditableChild(nsINode* aNode) const; + nsIContent* GetFirstEditableChild(nsINode& aNode) const; + nsIContent* GetLastEditableChild(nsINode& aNode) const; + + nsIContent* GetFirstEditableLeaf(nsINode& aNode) const; + nsIContent* GetLastEditableLeaf(nsINode& aNode) const; + + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult GetInlinePropertyBase( + nsAtom& aHTMLProperty, nsAtom* aAttribute, const nsAString* aValue, + bool* aFirst, bool* aAny, bool* aAll, nsAString* outValue) const; + + /** + * ClearStyleAt() splits parent elements to remove the specified style. + * If this splits some parent elements at near their start or end, such + * empty elements will be removed. Then, remove the specified style + * from the point and returns DOM point to put caret. + * + * @param aPoint The point to clear style at. + * @param aProperty An HTML tag name which represents a style. + * Set nullptr if you want to clear all styles. + * @param aAttribute Attribute name if aProperty has some styles like + * nsGkAtoms::font. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditResult ClearStyleAt( + const EditorDOMPoint& aPoint, nsAtom* aProperty, nsAtom* aAttribute); + + MOZ_CAN_RUN_SCRIPT nsresult SetPositionToAbsolute(Element& aElement); + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + SetPositionToStatic(Element& aElement); + + /** + * OnModifyDocument() is called when the editor is changed. This should + * be called only by runnable in HTMLEditor::OnDocumentModified() to call + * HTMLEditor::OnModifyDocument() with AutoEditActionDataSetter instance. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult OnModifyDocument(); + + /** + * DoSplitNode() creates a new node (left node) identical to an existing + * node (right node), and split the contents between the same point in both + * nodes. + * + * @param aStartOfRightNode The point to split. Its container will be + * the right node, i.e., become the new node's + * next sibling. And the point will be start + * of the right node. + * @param aNewLeftNode The new node called as left node, so, this + * becomes the container of aPointToSplit's + * previous sibling. + * @param aError Must have not already failed. + * If succeed to insert aLeftNode before the + * right node and remove unnecessary contents + * (and collapse selection at end of the left + * node if necessary), returns no error. + * Otherwise, an error. + */ + MOZ_CAN_RUN_SCRIPT void DoSplitNode(const EditorDOMPoint& aStartOfRightNode, + nsIContent& aNewLeftNode, + ErrorResult& aError); + + /** + * DoJoinNodes() merges contents in aContentToJoin to aContentToKeep and + * remove aContentToJoin from the DOM tree. aContentToJoin and aContentToKeep + * must have same parent, aParent. Additionally, if one of aContentToJoin or + * aContentToKeep is a text node, the other must be a text node. + * + * @param aContentToKeep The node that will remain after the join. + * @param aContentToJoin The node that will be joined with aContentToKeep. + * There is no requirement that the two nodes be of the + * same type. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + DoJoinNodes(nsIContent& aContentToKeep, nsIContent& aContentToJoin); + + protected: // edit sub-action handler + /** + * CanHandleHTMLEditSubAction() checks whether there is at least one + * selection range or not, and whether the first range is editable. + * If it's not editable, `Canceled()` of the result returns true. + * If `Selection` is in odd situation, returns an error. + * + * XXX I think that `IsSelectionEditable()` is better name, but it's already + * in `EditorBase`... + */ + EditActionResult CanHandleHTMLEditSubAction() const; + + /** + * EnsureCaretNotAfterPaddingBRElement() makes sure that caret is NOT after + * padding `<br>` element for preventing insertion after padding `<br>` + * element at empty last line. + * NOTE: This method should be called only when `Selection` is collapsed + * because `Selection` is a pain to work with when not collapsed. + * (no good way to extend start or end of selection), so we need to + * ignore those types of selections. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + EnsureCaretNotAfterPaddingBRElement(); + + /** + * PrepareInlineStylesForCaret() consider inline styles from top level edit + * sub-action and setting it to `mTypeInState` and clear inline style cache + * if necessary. + * NOTE: This method should be called only when `Selection` is collapsed. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult PrepareInlineStylesForCaret(); + + /** + * HandleInsertText() handles inserting text at selection. + * + * @param aEditSubAction Must be EditSubAction::eInsertText or + * EditSubAction::eInsertTextComingFromIME. + * @param aInsertionString String to be inserted at selection. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual EditActionResult HandleInsertText( + EditSubAction aEditSubAction, const nsAString& aInsertionString) final; + + /** + * GetInlineStyles() retrieves the style of aNode and modifies each item of + * aStyleCacheArray. This might cause flushing layout at retrieving computed + * values of CSS properties. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + GetInlineStyles(nsIContent& aContent, AutoStyleCacheArray& aStyleCacheArray); + + /** + * CacheInlineStyles() caches style of aContent into mCachedInlineStyles of + * TopLevelEditSubAction. This may cause flushing layout at retrieving + * computed value of CSS properties. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + CacheInlineStyles(nsIContent& aContent); + + /** + * ReapplyCachedStyles() restores some styles which are disappeared during + * handling edit action and it should be restored. This may cause flushing + * layout at retrieving computed value of CSS properties. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult ReapplyCachedStyles(); + + /** + * CreateStyleForInsertText() sets CSS properties which are stored in + * TypeInState to proper element node. + * XXX This modifies Selection, but should return insertion point instead. + * + * @param aAbstractRange Set current selection range where new text + * should be inserted. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + CreateStyleForInsertText(const dom::AbstractRange& aAbstractRange); + + /** + * GetMostAncestorMailCiteElement() returns most-ancestor mail cite element. + * "mail cite element" is <pre> element when it's in plaintext editor mode + * or an element with which calling HTMLEditUtils::IsMailCite() returns true. + * + * @param aNode The start node to look for parent mail cite elements. + */ + Element* GetMostAncestorMailCiteElement(nsINode& aNode) const; + + /** + * SplitMailCiteElements() splits mail-cite elements at start of Selection if + * Selection starts from inside a mail-cite element. Of course, if it's + * necessary, this inserts <br> node to new left nodes or existing right + * nodes. + * XXX This modifies Selection, but should return SplitNodeResult() instead. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult + SplitMailCiteElements(const EditorDOMPoint& aPointToSplit); + + /** + * InsertBRElement() inserts a <br> element into aInsertToBreak. + * This may split container elements at the point and/or may move following + * <br> element to immediately after the new <br> element if necessary. + * XXX This method name is too generic and unclear whether such complicated + * things will be done automatically or not. + * XXX This modifies Selection, but should return CreateElementResult instead. + * + * @param aInsertToBreak The point where new <br> element will be + * inserted before. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + InsertBRElement(const EditorDOMPoint& aInsertToBreak); + + /** + * GetMostAncestorInlineElement() returns the most ancestor inline element + * between aNode and the editing host. Even if the editing host is an inline + * element, this method never returns the editing host as the result. + */ + nsIContent* GetMostAncestorInlineElement(nsINode& aNode) const; + + /** + * SplitParentInlineElementsAtRangeEdges() splits parent inline nodes at both + * start and end of aRangeItem. If this splits at every point, this modifies + * aRangeItem to point each split point (typically, right node). + * + * @param aRangeItem [in/out] One or two DOM points where should be + * split. Will be modified to split point if + * they're split. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + SplitParentInlineElementsAtRangeEdges(RangeItem& aRangeItem); + + /** + * SplitParentInlineElementsAtRangeEdges(nsTArray<RefPtr<nsRange>>&) calls + * SplitParentInlineElementsAtRangeEdges(RangeItem&) for each range. Then, + * updates given range to keep edit target ranges as expected. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + SplitParentInlineElementsAtRangeEdges( + nsTArray<RefPtr<nsRange>>& aArrayOfRanges); + + /** + * SplitElementsAtEveryBRElement() splits before all <br> elements in + * aMostAncestorToBeSplit. All <br> nodes will be moved before right node + * at splitting its parent. Finally, this returns left node, first <br> + * element, next left node, second <br> element... and right-most node. + * + * @param aMostAncestorToBeSplit Most-ancestor element which should + * be split. + * @param aOutArrayOfNodes First left node, first <br> element, + * Second left node, second <br> element, + * ...right-most node. So, all nodes + * in this list should be siblings (may be + * broken the relation by mutation event + * listener though). If first <br> element + * is first leaf node of + * aMostAncestorToBeSplit, starting from + * the first <br> element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SplitElementsAtEveryBRElement( + nsIContent& aMostAncestorToBeSplit, + nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents); + + /** + * MaybeSplitElementsAtEveryBRElement() calls SplitElementsAtEveryBRElement() + * for each given node when this needs to do that for aEditSubAction. + * If split a node, it in aArrayOfContents is replaced with split nodes and + * <br> elements. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult MaybeSplitElementsAtEveryBRElement( + nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, + EditSubAction aEditSubAction); + + /** + * CollectEditableChildren() collects child nodes of aNode (starting from + * first editable child, but may return non-editable children after it). + * + * @param aNode Parent node of retrieving children. + * @param aOutArrayOfContents [out] This method will inserts found children + * into this array. + * @param aIndexToInsertChildren Starting from this index, found + * children will be inserted to the array. + * @param aCollectListChildren If Yes, will collect children of list + * and list-item elements recursively. + * @param aCollectTableChildren If Yes, will collect children of table + * related elements recursively. + * @param aCollectNonEditableNodes If Yes, will collect found children + * even if they are not editable. + * @return Number of found children. + */ + enum class CollectListChildren { No, Yes }; + enum class CollectTableChildren { No, Yes }; + enum class CollectNonEditableNodes { No, Yes }; + size_t CollectChildren( + nsINode& aNode, nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents, + size_t aIndexToInsertChildren, CollectListChildren aCollectListChildren, + CollectTableChildren aCollectTableChildren, + CollectNonEditableNodes aCollectNonEditableNodes) const; + + /** + * SplitInlinessAndCollectEditTargetNodes() splits text nodes and inline + * elements around aArrayOfRanges. Then, collects edit target nodes to + * aOutArrayOfNodes. Finally, each edit target nodes is split at every + * <br> element in it. + * FYI: You can use SplitInlinesAndCollectEditTargetNodesInOneHardLine() + * or SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges() + * instead if you want to call this with a hard line including + * specific DOM point or extended selection ranges. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + SplitInlinesAndCollectEditTargetNodes( + nsTArray<RefPtr<nsRange>>& aArrayOfRanges, + nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents, + EditSubAction aEditSubAction, + CollectNonEditableNodes aCollectNonEditableNodes); + + /** + * SplitTextNodesAtRangeEnd() splits text nodes if each range end is in + * middle of a text node. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + SplitTextNodesAtRangeEnd(nsTArray<RefPtr<nsRange>>& aArrayOfRanges); + + /** + * CollectEditTargetNodes() collects edit target nodes in aArrayOfRanges. + * First, this collects all nodes in given ranges, then, modifies the + * result for specific edit sub-actions. + * FYI: You can use CollectEditTargetNodesInExtendedSelectionRanges() instead + * if you want to call this with extended selection ranges. + */ + nsresult CollectEditTargetNodes( + nsTArray<RefPtr<nsRange>>& aArrayOfRanges, + nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents, + EditSubAction aEditSubAction, + CollectNonEditableNodes aCollectNonEditableNodes); + + /** + * GetWhiteSpaceEndPoint() returns point at first or last ASCII white-space + * or non-breakable space starting from aPoint. I.e., this returns next or + * previous point whether the character is neither ASCII white-space nor + * non-brekable space. + */ + enum class ScanDirection { Backward, Forward }; + template <typename PT, typename RT> + static EditorDOMPoint GetWhiteSpaceEndPoint( + const RangeBoundaryBase<PT, RT>& aPoint, ScanDirection aScanDirection); + + /** + * GetCurrentHardLineStartPoint() returns start point of hard line + * including aPoint. If the line starts after a `<br>` element, returns + * next sibling of the `<br>` element. If the line is first line of a block, + * returns point of the block. + * NOTE: The result may be point of editing host. I.e., the container may + * be outside of editing host. + */ + template <typename PT, typename RT> + EditorDOMPoint GetCurrentHardLineStartPoint( + const RangeBoundaryBase<PT, RT>& aPoint, + EditSubAction aEditSubAction) const; + + /** + * GetCurrentHardLineEndPoint() returns end point of hard line including + * aPoint. If the line ends with a `<br>` element, returns the `<br>` + * element unless it's the last node of a block. If the line is last line + * of a block, returns next sibling of the block. Additionally, if the + * line ends with a linefeed in pre-formated text node, returns point of + * the linefeed. + * NOTE: This result may be point of editing host. I.e., the container + * may be outside of editing host. + */ + template <typename PT, typename RT> + EditorDOMPoint GetCurrentHardLineEndPoint( + const RangeBoundaryBase<PT, RT>& aPoint) const; + + /** + * CreateRangeIncludingAdjuscentWhiteSpaces() creates an nsRange instance + * which may be expanded from the given range to include adjuscent + * white-spaces. If this fails handling something, returns nullptr. + */ + already_AddRefed<nsRange> CreateRangeIncludingAdjuscentWhiteSpaces( + const dom::AbstractRange& aAbstractRange); + template <typename SPT, typename SRT, typename EPT, typename ERT> + already_AddRefed<nsRange> CreateRangeIncludingAdjuscentWhiteSpaces( + const RangeBoundaryBase<SPT, SRT>& aStartRef, + const RangeBoundaryBase<EPT, ERT>& aEndRef); + + /** + * GetSelectionRangesExtendedToIncludeAdjuscentWhiteSpaces() collects + * selection ranges with extending to include adjuscent white-spaces + * of each range start and end. + * + * @param aOutArrayOfRanges [out] Always appended same number of ranges + * as Selection::RangeCount(). Must be empty + * when you call this. + */ + void GetSelectionRangesExtendedToIncludeAdjuscentWhiteSpaces( + nsTArray<RefPtr<nsRange>>& aOutArrayOfRanges); + + /** + * CreateRangeExtendedToHardLineStartAndEnd() creates an nsRange instance + * which may be expanded to start/end of hard line at both edges of the given + * range. If this fails handling something, returns nullptr. + */ + already_AddRefed<nsRange> CreateRangeExtendedToHardLineStartAndEnd( + const dom::AbstractRange& aAbstractRange, + EditSubAction aEditSubAction) const; + template <typename SPT, typename SRT, typename EPT, typename ERT> + already_AddRefed<nsRange> CreateRangeExtendedToHardLineStartAndEnd( + const RangeBoundaryBase<SPT, SRT>& aStartRef, + const RangeBoundaryBase<EPT, ERT>& aEndRef, + EditSubAction aEditSubAction) const; + + /** + * GetSelectionRangesExtendedToHardLineStartAndEnd() collects selection ranges + * with extending to start/end of hard line from each range start and end. + * XXX This means that same range may be included in the result. + * + * @param aOutArrayOfRanges [out] Always appended same number of ranges + * as Selection::RangeCount(). Must be empty + * when you call this. + */ + void GetSelectionRangesExtendedToHardLineStartAndEnd( + nsTArray<RefPtr<nsRange>>& aOutArrayOfRanges, + EditSubAction aEditSubAction); + + /** + * SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges() calls + * SplitInlinesAndCollectEditTargetNodes() with result of + * GetSelectionRangesExtendedToHardLineStartAndEnd(). See comments for these + * methods for the detail. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges( + nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents, + EditSubAction aEditSubAction, + CollectNonEditableNodes aCollectNonEditableNodes) { + AutoTArray<RefPtr<nsRange>, 4> extendedSelectionRanges; + GetSelectionRangesExtendedToHardLineStartAndEnd(extendedSelectionRanges, + aEditSubAction); + nsresult rv = SplitInlinesAndCollectEditTargetNodes( + extendedSelectionRanges, aOutArrayOfContents, aEditSubAction, + aCollectNonEditableNodes); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "SplitInlinesAndCollectEditTargetNodes() failed"); + return rv; + } + + /** + * SplitInlinesAndCollectEditTargetNodesInOneHardLine() just calls + * SplitInlinesAndCollectEditTargetNodes() with result of calling + * CreateRangeExtendedToHardLineStartAndEnd() with aPointInOneHardLine. + * In other words, returns nodes in the hard line including + * `aPointInOneHardLine`. See the comments for these methods for the + * detail. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + SplitInlinesAndCollectEditTargetNodesInOneHardLine( + const EditorDOMPoint& aPointInOneHardLine, + nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents, + EditSubAction aEditSubAction, + CollectNonEditableNodes aCollectNonEditableNodes) { + if (NS_WARN_IF(!aPointInOneHardLine.IsSet())) { + return NS_ERROR_INVALID_ARG; + } + RefPtr<nsRange> oneLineRange = CreateRangeExtendedToHardLineStartAndEnd( + aPointInOneHardLine.ToRawRangeBoundary(), + aPointInOneHardLine.ToRawRangeBoundary(), aEditSubAction); + if (!oneLineRange) { + // XXX It seems odd to create collapsed range for one line range... + ErrorResult error; + oneLineRange = + nsRange::Create(aPointInOneHardLine.ToRawRangeBoundary(), + aPointInOneHardLine.ToRawRangeBoundary(), error); + if (NS_WARN_IF(error.Failed())) { + return error.StealNSResult(); + } + } + AutoTArray<RefPtr<nsRange>, 1> arrayOfLineRanges; + arrayOfLineRanges.AppendElement(oneLineRange); + nsresult rv = SplitInlinesAndCollectEditTargetNodes( + arrayOfLineRanges, aOutArrayOfContents, aEditSubAction, + aCollectNonEditableNodes); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "SplitInlinesAndCollectEditTargetNodes() failed"); + return rv; + } + + /** + * CollectEditTargetNodesInExtendedSelectionRanges() calls + * CollectEditTargetNodes() with result of + * GetSelectionRangesExtendedToHardLineStartAndEnd(). See comments for these + * methods for the detail. + */ + nsresult CollectEditTargetNodesInExtendedSelectionRanges( + nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents, + EditSubAction aEditSubAction, + CollectNonEditableNodes aCollectNonEditableNodes) { + AutoTArray<RefPtr<nsRange>, 4> extendedSelectionRanges; + GetSelectionRangesExtendedToHardLineStartAndEnd(extendedSelectionRanges, + aEditSubAction); + nsresult rv = + CollectEditTargetNodes(extendedSelectionRanges, aOutArrayOfContents, + aEditSubAction, aCollectNonEditableNodes); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "CollectEditTargetNodes() failed"); + return rv; + } + + /** + * SelectBRElementIfCollapsedInEmptyBlock() helper method for + * CreateRangeIncludingAdjuscentWhiteSpaces() and + * CreateRangeExtendedToLineStartAndEnd(). If the given range is collapsed + * in a block and the block has only one `<br>` element, this makes + * aStartRef and aEndRef select the `<br>` element. + */ + template <typename SPT, typename SRT, typename EPT, typename ERT> + void SelectBRElementIfCollapsedInEmptyBlock( + RangeBoundaryBase<SPT, SRT>& aStartRef, + RangeBoundaryBase<EPT, ERT>& aEndRef) const; + + /** + * GetChildNodesOf() returns all child nodes of aParent with an array. + */ + static void GetChildNodesOf( + nsINode& aParentNode, + nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) { + MOZ_ASSERT(aOutArrayOfContents.IsEmpty()); + aOutArrayOfContents.SetCapacity(aParentNode.GetChildCount()); + for (nsIContent* childContent = aParentNode.GetFirstChild(); childContent; + childContent = childContent->GetNextSibling()) { + aOutArrayOfContents.AppendElement(*childContent); + } + } + + /** + * GetDeepestEditableOnlyChildDivBlockquoteOrListElement() returns a `<div>`, + * `<blockquote>` or one of list elements. This method climbs down from + * aContent while there is only one editable children and the editable child + * is `<div>`, `<blockquote>` or a list element. When it reaches different + * kind of node, returns the last found element. + */ + Element* GetDeepestEditableOnlyChildDivBlockquoteOrListElement( + nsINode& aNode); + + /** + * Try to get parent list element at `Selection`. This returns first find + * parent list element of common ancestor of ranges (looking for it from + * first range to last range). + */ + Element* GetParentListElementAtSelection() const; + + /** + * MaybeExtendSelectionToHardLineEdgesForBlockEditAction() adjust Selection if + * there is only one range. If range start and/or end point is <br> node or + * something non-editable point, they should be moved to nearest text node or + * something where the other methods easier to handle edit action. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + MaybeExtendSelectionToHardLineEdgesForBlockEditAction(); + + /** + * IsEmptyInlineNode() returns true if aContent is an inline node and it does + * not have meaningful content. + */ + bool IsEmptyInlineNode(nsIContent& aContent) const; + + /** + * IsEmptyOneHardLine() returns true if aArrayOfContents does not represent + * 2 or more lines and have meaningful content. + */ + bool IsEmptyOneHardLine( + nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents) const { + if (NS_WARN_IF(aArrayOfContents.IsEmpty())) { + return true; + } + + bool brElementHasFound = false; + for (OwningNonNull<nsIContent>& content : aArrayOfContents) { + if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) { + continue; + } + if (content->IsHTMLElement(nsGkAtoms::br)) { + // If there are 2 or more `<br>` elements, it's not empty line since + // there may be only one `<br>` element in a hard line. + if (brElementHasFound) { + return false; + } + brElementHasFound = true; + continue; + } + if (!IsEmptyInlineNode(content)) { + return false; + } + } + return true; + } + + /** + * MaybeSplitAncestorsForInsertWithTransaction() does nothing if container of + * aStartOfDeepestRightNode can have an element whose tag name is aTag. + * Otherwise, looks for an ancestor node which is or is in active editing + * host and can have an element whose name is aTag. If there is such + * ancestor, its descendants are split. + * + * Note that this may create empty elements while splitting ancestors. + * + * @param aTag The name of element to be inserted + * after calling this method. + * @param aStartOfDeepestRightNode The start point of deepest right node. + * This point must be descendant of + * active editing host. + * @return When succeeded, SplitPoint() returns + * the point to insert the element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT SplitNodeResult + MaybeSplitAncestorsForInsertWithTransaction( + nsAtom& aTag, const EditorDOMPoint& aStartOfDeepestRightNode); + + /** + * SplitRangeOffFromBlock() splits aBlockElement at two points, before + * aStartOfMiddleElement and after aEndOfMiddleElement. If they are very + * start or very end of aBlcok, this won't create empty block. + * + * @param aBlockElement A block element which will be split. + * @param aStartOfMiddleElement Start node of middle block element. + * @param aEndOfMiddleElement End node of middle block element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT SplitRangeOffFromNodeResult + SplitRangeOffFromBlock(Element& aBlockElement, + nsIContent& aStartOfMiddleElement, + nsIContent& aEndOfMiddleElement); + + /** + * SplitRangeOffFromBlockAndRemoveMiddleContainer() splits the nodes + * between aStartOfRange and aEndOfRange, then, removes the middle element + * and moves its content to where the middle element was. + * + * @param aBlockElement The node which will be split. + * @param aStartOfRange The first node which will be unwrapped + * from aBlockElement. + * @param aEndOfRange The last node which will be unwrapped from + * aBlockElement. + * @return The left content is new created left + * element of aBlockElement. + * The right content is split element, + * i.e., must be aBlockElement. + * The middle content is nullptr since + * removing it is the job of this method. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT SplitRangeOffFromNodeResult + SplitRangeOffFromBlockAndRemoveMiddleContainer(Element& aBlockElement, + nsIContent& aStartOfRange, + nsIContent& aEndOfRange); + + /** + * MoveNodesIntoNewBlockquoteElement() inserts at least one <blockquote> + * element and moves nodes in aArrayOfContents into new <blockquote> + * elements. + * If aArrayOfContents includes a table related element except <table>, + * this calls itself recursively to insert <blockquote> into the cell. + * + * @param aArrayOfContents Nodes which will be moved into created + * <blockquote> elements. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult MoveNodesIntoNewBlockquoteElement( + nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents); + + /** + * RemoveBlockContainerElements() removes all format blocks, table related + * element, etc in aArrayOfContents from the DOM tree. + * If aArrayOfContents has a format node, it will be removed and its contents + * will be moved to where it was. + * If aArrayOfContents has a table related element, <li>, <blockquote> or + * <div>, it will be removed and its contents will be moved to where it was. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult RemoveBlockContainerElements( + nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents); + + /** + * CreateOrChangeBlockContainerElement() formats all nodes in aArrayOfContents + * with block elements whose name is aBlockTag. + * If aArrayOfContents has an inline element, a block element is created and + * the inline element and following inline elements are moved into the new + * block element. + * If aArrayOfContents has <br> elements, they'll be removed from the DOM + * tree and new block element will be created when there are some remaining + * inline elements. + * If aArrayOfContents has a block element, this calls itself with children + * of the block element. Then, new block element will be created when there + * are some remaining inline elements. + * + * @param aArrayOfContents Must be descendants of a node. + * @param aBlockTag The element name of new block elements. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult CreateOrChangeBlockContainerElement( + nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, nsAtom& aBlockTag); + + /** + * FormatBlockContainerWithTransaction() is implementation of "formatBlock" + * command of `Document.execCommand()`. This applies block style or removes + * it. + * NOTE: This creates AutoSelectionRestorer. Therefore, even when this + * return NS_OK, editor may have been destroyed. + * + * @param aBlockType New block tag name. + * If nsGkAtoms::normal or nsGkAtoms::_empty, + * RemoveBlockContainerElements() will be called. + * If nsGkAtoms::blockquote, + * MoveNodesIntoNewBlockquoteElement() will be + * called. Otherwise, + * CreateOrChangeBlockContainerElement() will be + * called. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + FormatBlockContainerWithTransaction(nsAtom& aBlockType); + + /** + * InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() determines if + * aPointToInsert is start of a hard line and end of the line (i.e, the + * line is empty) and the line ends with block boundary, inserts a `<br>` + * element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary( + const EditorDOMPoint& aPointToInsert); + + /** + * Insert a `<br>` element if aElement is a block element and empty. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + InsertBRElementIfEmptyBlockElement(Element& aElement); + + /** + * Insert padding `<br>` element for empty last line into aElement if + * aElement is a block element and empty. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + InsertPaddingBRElementForEmptyLastLineIfNeeded(Element& aElement); + + /** + * This method inserts a padding `<br>` element for empty last line if + * selection is collapsed and container of the range needs it. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + MaybeInsertPaddingBRElementForEmptyLastLineAtSelection(); + + /** + * IsEmptyBlockElement() returns true if aElement is a block level element + * and it doesn't have any visible content. + */ + enum class IgnoreSingleBR { Yes, No }; + bool IsEmptyBlockElement(Element& aElement, + IgnoreSingleBR aIgnoreSingleBR) const; + + /** + * SplitParagraph() splits the parent block, aParentDivOrP, at + * aStartOfRightNode. + * + * @param aParentDivOrP The parent block to be split. This must be <p> + * or <div> element. + * @param aStartOfRightNode The point to be start of right node after + * split. This must be descendant of + * aParentDivOrP. + * @param aNextBRNode Next <br> node if there is. Otherwise, nullptr. + * If this is not nullptr, the <br> node may be + * removed. + */ + template <typename PT, typename CT> + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SplitParagraph( + Element& aParentDivOrP, + const EditorDOMPointBase<PT, CT>& aStartOfRightNode, nsIContent* aBRNode); + + /** + * HandleInsertParagraphInParagraph() does the right thing for Enter key + * press or 'insertParagraph' command in aParentDivOrP. aParentDivOrP will + * be split at start of first selection range. + * + * @param aParentDivOrP The parent block. This must be <p> or <div> + * element. + * @return Returns with NS_OK if this doesn't meat any + * unexpected situation. If this method tries to + * split the paragraph, marked as handled. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult + HandleInsertParagraphInParagraph(Element& aParentDivOrP); + + /** + * HandleInsertParagraphInHeadingElement() handles insertParagraph command + * (i.e., handling Enter key press) in a heading element. This splits + * aHeader element at aOffset in aNode. Then, if right heading element is + * empty, it'll be removed and new paragraph is created (its type is decided + * with default paragraph separator). + * + * @param aHeader The heading element to be split. + * @param aNode Typically, Selection start container, + * where to be split. + * @param aOffset Typically, Selection start offset in the + * start container, where to be split. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + HandleInsertParagraphInHeadingElement(Element& aHeader, nsINode& aNode, + int32_t aOffset); + + /** + * HandleInsertParagraphInListItemElement() handles insertParagraph command + * (i.e., handling Enter key press) in a list item element. + * + * @param aListItem The list item which has the following point. + * @param aNode Typically, Selection start container, where to + * insert a break. + * @param aOffset Typically, Selection start offset in the + * start container, where to insert a break. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + HandleInsertParagraphInListItemElement(Element& aListItem, nsINode& aNode, + int32_t aOffset); + + /** + * GetNearestAncestorListItemElement() returns a list item element if + * aContent or its ancestor in editing host is one. However, this won't + * cross table related element. + */ + Element* GetNearestAncestorListItemElement(nsIContent& aContent) const; + + /** + * InsertParagraphSeparatorAsSubAction() handles insertPargraph commad + * (i.e., handling Enter key press) with the above helper methods. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult + InsertParagraphSeparatorAsSubAction(); + + /** + * Returns true if aNode1 or aNode2 or both is the descendant of some type of + * table element, but their nearest table element ancestors differ. "Table + * element" here includes not just <table> but also <td>, <tbody>, <tr>, etc. + * The nodes count as being their own descendants for this purpose, so a + * table element is its own nearest table element ancestor. + */ + static bool NodesInDifferentTableElements(nsINode& aNode1, nsINode& aNode2); + + /** + * ChangeListElementType() replaces child list items of aListElement with + * new list item element whose tag name is aNewListItemTag. + * Note that if there are other list elements as children of aListElement, + * this calls itself recursively even though it's invalid structure. + * + * @param aListElement The list element whose list items will be + * replaced. + * @param aNewListTag New list tag name. + * @param aNewListItemTag New list item tag name. + * @return New list element or an error code if it fails. + * New list element may be aListElement if its + * tag name is same as aNewListTag. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT CreateElementResult ChangeListElementType( + Element& aListElement, nsAtom& aListType, nsAtom& aItemType); + + /** + * ChangeSelectedHardLinesToList() converts selected ranges to specified + * list element. If there is different type of list elements, this method + * converts them to specified list items too. Basically, each hard line + * will be wrapped with a list item element. However, only when `<p>` + * element is selected, its child `<br>` elements won't be treated as + * hard line separators. Perhaps, this is a bug. + * NOTE: This method creates AutoSelectionRestorer. Therefore, each caller + * need to check if the editor is still available even if this returns + * NS_OK. + * + * @param aListElementTagName The new list element tag name. + * @param aListItemElementTagName The new list item element tag name. + * @param aBulletType If this is not empty string, it's set + * to `type` attribute of new list item + * elements. Otherwise, existing `type` + * attributes will be removed. + * @param aSelectAllOfCurrentList Yes if this should treat all of + * ancestor list element at selection. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult + ChangeSelectedHardLinesToList(nsAtom& aListElementTagName, + nsAtom& aListItemElementTagName, + const nsAString& aBulletType, + SelectAllOfCurrentList aSelectAllOfCurrentList); + + /** + * MakeOrChangeListAndListItemAsSubAction() handles create list commands with + * current selection. If + * + * @param aListElementOrListItemElementTagName + * The new list element tag name or + * new list item tag name. + * If the former, list item tag name will + * be computed automatically. Otherwise, + * list tag name will be computed. + * @param aBulletType If this is not empty string, it's set + * to `type` attribute of new list item + * elements. Otherwise, existing `type` + * attributes will be removed. + * @param aSelectAllOfCurrentList Yes if this should treat all of + * ancestor list element at selection. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult + MakeOrChangeListAndListItemAsSubAction( + nsAtom& aListElementOrListItemElementTagName, + const nsAString& aBulletType, + SelectAllOfCurrentList aSelectAllOfCurrentList); + + /** + * DeleteTextAndTextNodesWithTransaction() removes text or text nodes in + * the given range. + */ + enum class TreatEmptyTextNodes { + // KeepIfContainerOfRangeBoundaries: + // Will remove empty text nodes middle of the range, but keep empty + // text nodes which are containers of range boundaries. + KeepIfContainerOfRangeBoundaries, + // Remove: + // Will remove all empty text nodes. + Remove, + // RemoveAllEmptyInlineAncestors: + // Will remove all empty text nodes and its inline ancestors which + // become empty due to removing empty text nodes. + RemoveAllEmptyInlineAncestors, + }; + template <typename EditorDOMPointType> + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + DeleteTextAndTextNodesWithTransaction( + const EditorDOMPointType& aStartPoint, + const EditorDOMPointType& aEndPoint, + TreatEmptyTextNodes aTreatEmptyTextNodes); + + /** + * JoinNodesWithTransaction() joins aLeftNode and aRightNode. Content of + * aLeftNode will be merged into aRightNode. Actual implemenation of this + * method is JoinNodesImpl(). So, see its explanation for the detail. + * + * @param aLeftNode Will be removed from the DOM tree. + * @param aRightNode The node which will be new container of the content of + * aLeftNode. + */ + MOZ_CAN_RUN_SCRIPT nsresult JoinNodesWithTransaction(nsINode& aLeftNode, + nsINode& aRightNode); + + /** + * JoinNearestEditableNodesWithTransaction() joins two editable nodes which + * are themselves or the nearest editable node of aLeftNode and aRightNode. + * XXX This method's behavior is odd. For example, if user types Backspace + * key at the second editable paragraph in this case: + * <div contenteditable> + * <p>first editable paragraph</p> + * <p contenteditable="false">non-editable paragraph</p> + * <p>second editable paragraph</p> + * </div> + * The first editable paragraph's content will be moved into the second + * editable paragraph and the non-editable paragraph becomes the first + * paragraph of the editor. I don't think that it's expected behavior of + * any users... + * + * @param aLeftNode The node which will be removed. + * @param aRightNode The node which will be inserted the content of + * aLeftNode. + * @param aNewFirstChildOfRightNode + * [out] The point at the first child of aRightNode. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + JoinNearestEditableNodesWithTransaction( + nsIContent& aLeftNode, nsIContent& aRightNode, + EditorDOMPoint* aNewFirstChildOfRightNode); + + /** + * ReplaceContainerAndCloneAttributesWithTransaction() creates new element + * whose name is aTagName, copies all attributes from aOldContainer to the + * new element, moves all children in aOldContainer to the new element, then, + * removes aOldContainer from the DOM tree. + * + * @param aOldContainer The element node which should be replaced + * with new element. + * @param aTagName The name of new element node. + */ + MOZ_CAN_RUN_SCRIPT already_AddRefed<Element> + ReplaceContainerAndCloneAttributesWithTransaction(Element& aOldContainer, + nsAtom& aTagName) { + return ReplaceContainerWithTransactionInternal( + aOldContainer, aTagName, *nsGkAtoms::_empty, u""_ns, true); + } + + /** + * ReplaceContainerWithTransaction() creates new element whose name is + * aTagName, sets aAttributes of the new element to aAttributeValue, moves + * all children in aOldContainer to the new element, then, removes + * aOldContainer from the DOM tree. + * + * @param aOldContainer The element node which should be replaced + * with new element. + * @param aTagName The name of new element node. + * @param aAttribute Attribute name to be set to the new element. + * @param aAttributeValue Attribute value to be set to aAttribute. + */ + MOZ_CAN_RUN_SCRIPT already_AddRefed<Element> ReplaceContainerWithTransaction( + Element& aOldContainer, nsAtom& aTagName, nsAtom& aAttribute, + const nsAString& aAttributeValue) { + return ReplaceContainerWithTransactionInternal( + aOldContainer, aTagName, aAttribute, aAttributeValue, false); + } + + /** + * ReplaceContainerWithTransaction() creates new element whose name is + * aTagName, moves all children in aOldContainer to the new element, then, + * removes aOldContainer from the DOM tree. + * + * @param aOldContainer The element node which should be replaced + * with new element. + * @param aTagName The name of new element node. + */ + MOZ_CAN_RUN_SCRIPT already_AddRefed<Element> ReplaceContainerWithTransaction( + Element& aOldContainer, nsAtom& aTagName) { + return ReplaceContainerWithTransactionInternal( + aOldContainer, aTagName, *nsGkAtoms::_empty, u""_ns, false); + } + + /** + * RemoveContainerWithTransaction() removes aElement from the DOM tree and + * moves all its children to the parent of aElement. + * + * @param aElement The element to be removed. + */ + MOZ_CAN_RUN_SCRIPT nsresult RemoveContainerWithTransaction(Element& aElement); + + /** + * InsertContainerWithTransaction() creates new element whose name is + * aTagName, moves aContent into the new element, then, inserts the new + * element into where aContent was. + * Note that this method does not check if aContent is valid child of + * the new element. So, callers need to guarantee it. + * + * @param aContent The content which will be wrapped with new + * element. + * @param aTagName Element name of new element which will wrap + * aContent and be inserted into where aContent + * was. + * @return The new element. + */ + MOZ_CAN_RUN_SCRIPT already_AddRefed<Element> InsertContainerWithTransaction( + nsIContent& aContent, nsAtom& aTagName) { + return InsertContainerWithTransactionInternal(aContent, aTagName, + *nsGkAtoms::_empty, u""_ns); + } + + /** + * InsertContainerWithTransaction() creates new element whose name is + * aTagName, sets its aAttribute to aAttributeValue, moves aContent into the + * new element, then, inserts the new element into where aContent was. + * Note that this method does not check if aContent is valid child of + * the new element. So, callers need to guarantee it. + * + * @param aContent The content which will be wrapped with new + * element. + * @param aTagName Element name of new element which will wrap + * aContent and be inserted into where aContent + * was. + * @param aAttribute Attribute which should be set to the new + * element. + * @param aAttributeValue Value to be set to aAttribute. + * @return The new element. + */ + MOZ_CAN_RUN_SCRIPT already_AddRefed<Element> InsertContainerWithTransaction( + nsIContent& aContent, nsAtom& aTagName, nsAtom& aAttribute, + const nsAString& aAttributeValue) { + return InsertContainerWithTransactionInternal(aContent, aTagName, + aAttribute, aAttributeValue); + } + + /** + * MoveNodeWithTransaction() moves aContent to aPointToInsert. + * + * @param aContent The node to be moved. + */ + MOZ_CAN_RUN_SCRIPT nsresult MoveNodeWithTransaction( + nsIContent& aContent, const EditorDOMPoint& aPointToInsert); + + /** + * MoveNodeToEndWithTransaction() moves aContent to end of aNewContainer. + * + * @param aContent The node to be moved. + * @param aNewContainer The new container which will contain aContent as + * its last child. + */ + MOZ_CAN_RUN_SCRIPT nsresult + MoveNodeToEndWithTransaction(nsIContent& aContent, nsINode& aNewContainer) { + EditorDOMPoint pointToInsert; + pointToInsert.SetToEndOf(&aNewContainer); + return MoveNodeWithTransaction(aContent, pointToInsert); + } + + /** + * MoveNodeOrChildrenWithTransaction() moves aContent to aPointToInsert. If + * cannot insert aContent due to invalid relation, moves only its children + * recursively and removes aContent from the DOM tree. + * + * @param aContent Content which should be moved. + * @param aPointToInsert The point to be inserted aContent or its + * descendants. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT MoveNodeResult + MoveNodeOrChildrenWithTransaction(nsIContent& aNode, + const EditorDOMPoint& aPointToInsert); + + /** + * CanMoveNodeOrChildren() returns true if + * `MoveNodeOrChildrenWithTransaction()` can move or delete at least a + * descendant of aElement into aNewContainer. I.e., when this returns true, + * `MoveNodeOrChildrenWithTransaction()` must return "handled". + */ + Result<bool, nsresult> CanMoveNodeOrChildren( + const nsIContent& aContent, const nsINode& aNewContainer) const; + + /** + * MoveChildrenWithTransaction() moves the children of aElement to + * aPointToInsert. If cannot insert some children due to invalid relation, + * calls MoveNodeOrChildrenWithTransaction() to remove the children but keep + * moving its children. + * + * @param aElement Container element whose children should be + * moved. + * @param aPointToInsert The point to be inserted children of aElement + * or its descendants. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT MoveNodeResult MoveChildrenWithTransaction( + Element& aElement, const EditorDOMPoint& aPointToInsert); + + /** + * CanMoveChildren() returns true if `MoveChildrenWithTransaction()` can move + * at least a descendant of aElement into aNewContainer. I.e., when this + * returns true, `MoveChildrenWithTransaction()` return "handled". + */ + Result<bool, nsresult> CanMoveChildren(const Element& aElement, + const nsINode& aNewContainer) const; + + /** + * MoveAllChildren() moves all children of aContainer to before + * aPointToInsert.GetChild(). + * See explanation of MoveChildrenBetween() for the detail of the behavior. + * + * @param aContainer The container node whose all children should + * be moved. + * @param aPointToInsert The insertion point. The container must not + * be a data node like a text node. + * @param aError The result. If this succeeds to move children, + * returns NS_OK. Otherwise, an error. + */ + void MoveAllChildren(nsINode& aContainer, + const EditorRawDOMPoint& aPointToInsert, + ErrorResult& aError); + + /** + * MoveChildrenBetween() moves all children between aFirstChild and aLastChild + * to before aPointToInsert.GetChild(). If some children are moved to + * different container while this method moves other children, they are just + * ignored. If the child node referred by aPointToInsert is moved to different + * container while this method moves children, returns error. + * + * @param aFirstChild The first child which should be moved to + * aPointToInsert. + * @param aLastChild The last child which should be moved. This + * must be a sibling of aFirstChild and it should + * be positioned after aFirstChild in the DOM tree + * order. + * @param aPointToInsert The insertion point. The container must not + * be a data node like a text node. + * @param aError The result. If this succeeds to move children, + * returns NS_OK. Otherwise, an error. + */ + void MoveChildrenBetween(nsIContent& aFirstChild, nsIContent& aLastChild, + const EditorRawDOMPoint& aPointToInsert, + ErrorResult& aError); + + /** + * MovePreviousSiblings() moves all siblings before aChild (i.e., aChild + * won't be moved) to before aPointToInsert.GetChild(). + * See explanation of MoveChildrenBetween() for the detail of the behavior. + * + * @param aChild The node which is next sibling of the last + * node to be moved. + * @param aPointToInsert The insertion point. The container must not + * be a data node like a text node. + * @param aError The result. If this succeeds to move children, + * returns NS_OK. Otherwise, an error. + */ + void MovePreviousSiblings(nsIContent& aChild, + const EditorRawDOMPoint& aPointToInsert, + ErrorResult& aError); + + /** + * MoveOneHardLineContents() moves the content in a hard line which contains + * aPointInHardLine to aPointToInsert or end of aPointToInsert's container. + * + * @param aPointInHardLine A point in a hard line. All nodes in + * same hard line will be moved. + * @param aPointToInsert Point to insert contents of the hard + * line. + * @param aMoveToEndOfContainer If `Yes`, aPointToInsert.Offset() will + * be ignored and instead, all contents + * will be appended to the container of + * aPointToInsert. The result may be + * different from setting this to `No` + * and aPointToInsert points end of the + * container because mutation event + * listeners may modify children of the + * container while we're moving nodes. + */ + enum class MoveToEndOfContainer { Yes, No }; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT MoveNodeResult MoveOneHardLineContents( + const EditorDOMPoint& aPointInHardLine, + const EditorDOMPoint& aPointToInsert, + MoveToEndOfContainer aMoveToEndOfContainer = MoveToEndOfContainer::No); + + /** + * CanMoveOrDeleteSomethingInHardLine() returns true if there are some content + * nodes which can be moved to another place or deleted. Note that if there + * is only a padding `<br>` element in empty block element, this returns + * false even though it may be deleted. + * + * @param aPointInHardLine A point in a hard line. + */ + template <typename PT, typename CT> + Result<bool, nsresult> CanMoveOrDeleteSomethingInHardLine( + const EditorDOMPointBase<PT, CT>& aPointInHardLine) const; + + /** + * SplitNodeWithTransaction() creates a transaction to create a new node + * (left node) identical to an existing node (right node), and split the + * contents between the same point in both nodes, then, execute the + * transaction. + * + * @param aStartOfRightNode The point to split. Its container will be + * the right node, i.e., become the new node's + * next sibling. And the point will be start + * of the right node. + * @param aError If succeed, returns no error. Otherwise, an + * error. + */ + MOZ_CAN_RUN_SCRIPT already_AddRefed<nsIContent> SplitNodeWithTransaction( + const EditorDOMPoint& aStartOfRightNode, ErrorResult& aResult); + + enum class SplitAtEdges { + // SplitNodeDeepWithTransaction() won't split container element + // nodes at their edges. I.e., when split point is start or end of + // container, it won't be split. + eDoNotCreateEmptyContainer, + // SplitNodeDeepWithTransaction() always splits containers even + // if the split point is at edge of a container. E.g., if split point is + // start of an inline element, empty inline element is created as a new left + // node. + eAllowToCreateEmptyContainer, + }; + + /** + * SplitNodeDeepWithTransaction() splits aMostAncestorToSplit deeply. + * + * @param aMostAncestorToSplit The most ancestor node which should be + * split. + * @param aStartOfDeepestRightNode The start point of deepest right node. + * This point must be descendant of + * aMostAncestorToSplit. + * @param aSplitAtEdges Whether the caller allows this to + * create empty container element when + * split point is start or end of an + * element. + * @return SplitPoint() returns split point in + * aMostAncestorToSplit. The point must + * be good to insert something if the + * caller want to do it. + */ + MOZ_CAN_RUN_SCRIPT SplitNodeResult + SplitNodeDeepWithTransaction(nsIContent& aMostAncestorToSplit, + const EditorDOMPoint& aDeepestStartOfRightNode, + SplitAtEdges aSplitAtEdges); + + /** + * GetGoodCaretPointFor() returns a good point to collapse `Selection` + * after handling edit action with aDirectionAndAmount. + * + * @param aContent The content where you want to put caret + * around. + * @param aDirectionAndAmount Muse be one of eNext, eNextWord, eToEndOfLine, + * ePrevious, ePreviousWord and eToBeggingOfLine. + * Set the direction of handled edit action. + */ + EditorDOMPoint GetGoodCaretPointFor( + nsIContent& aContent, nsIEditor::EDirection aDirectionAndAmount) const; + + /** + * RemoveEmptyInclusiveAncestorInlineElements() removes empty inclusive + * ancestor inline elements in inclusive ancestor block element of aContent. + * + * @param aContent Must be an empty content. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + RemoveEmptyInclusiveAncestorInlineElements(nsIContent& aContent); + + /** + * DeleteTextAndNormalizeSurroundingWhiteSpaces() deletes text between + * aStartToDelete and immediately before aEndToDelete and return new caret + * position. If surrounding characters are white-spaces, this normalize them + * too. Finally, inserts `<br>` element if it's required. + * Note that if you wants only normalizing white-spaces, you can set same + * point to both aStartToDelete and aEndToDelete. Then, this tries to + * normalize white-space sequence containing previous character of + * aStartToDelete. + */ + enum class DeleteDirection { + Forward, + Backward, + }; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + DeleteTextAndNormalizeSurroundingWhiteSpaces( + const EditorDOMPointInText& aStartToDelete, + const EditorDOMPointInText& aEndToDelete, + TreatEmptyTextNodes aTreatEmptyTextNodes, + DeleteDirection aDeleteDirection); + + /** + * ExtendRangeToDeleteWithNormalizingWhiteSpaces() is a helper method of + * DeleteTextAndNormalizeSurroundingWhiteSpaces(). This expands + * aStartToDelete and/or aEndToDelete if there are white-spaces which need + * normalizing. + * + * @param aStartToDelete [In/Out] Start to delete. If this point + * follows white-spaces, this may be modified. + * @param aEndToDelete [In/Out] Next point of last content to be + * deleted. If this point is a white-space, + * this may be modified. + * @param aNormalizedWhiteSpacesInStartNode + * [Out] If container text node of aStartToDelete + * should be modified, this offers new string + * in the range in the text node. + * @param aNormalizedWhiteSpacesInEndNode + * [Out] If container text node of aEndToDelete + * is different from the container of + * aStartToDelete and it should be modified, this + * offers new string in the range in the text node. + */ + void ExtendRangeToDeleteWithNormalizingWhiteSpaces( + EditorDOMPointInText& aStartToDelete, EditorDOMPointInText& aEndToDelete, + nsAString& aNormalizedWhiteSpacesInStartNode, + nsAString& aNormalizedWhiteSpacesInEndNode) const; + + /** + * CharPointType let the following helper methods of + * ExtendRangeToDeleteWithNormalizingWhiteSpaces() know what type of + * character will be previous or next char point after deletion. + */ + enum class CharPointType { + TextEnd, // Start or end of the text (hardline break or replaced inline + // element) + ASCIIWhiteSpace, // One of ASCII white-spaces (collapsible white-space) + NoBreakingSpace, // NBSP + VisibleChar, // Non-white-space characters + PreformattedChar, // Any character (including white-space) in preformatted + // element + }; + + /** + * GetPreviousCharPointType() and GetCharPointType() get type of + * previous/current char point from current DOM tree. In other words, if the + * point will be deleted, you cannot use these methods. + */ + template <typename EditorDOMPointType> + static CharPointType GetPreviousCharPointType( + const EditorDOMPointType& aPoint) { + MOZ_ASSERT(aPoint.IsInTextNode()); + if (aPoint.IsStartOfContainer()) { + return CharPointType::TextEnd; + } + if (EditorUtils::IsContentPreformatted(*aPoint.ContainerAsText())) { + return CharPointType::PreformattedChar; + } + if (aPoint.IsPreviousCharASCIISpace()) { + return CharPointType::ASCIIWhiteSpace; + } + return aPoint.IsPreviousCharNBSP() ? CharPointType::NoBreakingSpace + : CharPointType::VisibleChar; + } + template <typename EditorDOMPointType> + static CharPointType GetCharPointType(const EditorDOMPointType& aPoint) { + MOZ_ASSERT(aPoint.IsInTextNode()); + if (aPoint.IsEndOfContainer()) { + return CharPointType::TextEnd; + } + if (EditorUtils::IsContentPreformatted(*aPoint.ContainerAsText())) { + return CharPointType::PreformattedChar; + } + if (aPoint.IsCharASCIISpace()) { + return CharPointType::ASCIIWhiteSpace; + } + return aPoint.IsCharNBSP() ? CharPointType::NoBreakingSpace + : CharPointType::VisibleChar; + } + + /** + * CharPointData let the following helper methods of + * ExtendRangeToDeleteWithNormalizingWhiteSpaces() know what type of + * character will be previous or next char point and the point is + * in same or different text node after deletion. + */ + class MOZ_STACK_CLASS CharPointData final { + public: + static CharPointData InDifferentTextNode(CharPointType aCharPointType) { + CharPointData result; + result.mIsInDifferentTextNode = true; + result.mType = aCharPointType; + return result; + } + static CharPointData InSameTextNode(CharPointType aCharPointType) { + CharPointData result; + // Let's mark this as in different text node if given one indicates + // that there is end of text because it means that adjacent content + // from point of text node view is another element. + result.mIsInDifferentTextNode = aCharPointType == CharPointType::TextEnd; + result.mType = aCharPointType; + return result; + } + + bool AcrossTextNodeBoundary() const { return mIsInDifferentTextNode; } + bool IsWhiteSpace() const { + return mType == CharPointType::ASCIIWhiteSpace || + mType == CharPointType::NoBreakingSpace; + } + CharPointType Type() const { return mType; } + + private: + CharPointData() = default; + + CharPointType mType; + bool mIsInDifferentTextNode; + }; + + /** + * GetPreviousCharPointDataForNormalizingWhiteSpaces() and + * GetInclusiveNextCharPointDataForNormalizingWhiteSpaces() is helper methods + * of ExtendRangeToDeleteWithNormalizingWhiteSpaces(). This retrieves + * previous or inclusive next editable char point and returns its data. + */ + CharPointData GetPreviousCharPointDataForNormalizingWhiteSpaces( + const EditorDOMPointInText& aPoint) const; + CharPointData GetInclusiveNextCharPointDataForNormalizingWhiteSpaces( + const EditorDOMPointInText& aPoint) const; + + /** + * GenerateWhiteSpaceSequence() generates white-space sequence which won't + * be collapsed. + * + * @param aResult [out] White space sequence which won't be + * collapsed, but wrapable. + * @param aLength Length of generating white-space sequence. + * Must be 1 or larger. + * @param aPreviousCharPointData + * Specify the previous char point where it'll be + * inserted. Currently, for keepin this method + * simple, does not support to generate a part + * of white-space sequence in a text node. So, + * if the type is white-space, it must indicate + * different text nodes white-space. + * @param aNextCharPointData Specify the next char point where it'll be + * inserted. Same as aPreviousCharPointData, + * this must node indidate white-space in same + * text node. + */ + static void GenerateWhiteSpaceSequence( + nsAString& aResult, uint32_t aLength, + const CharPointData& aPreviousCharPointData, + const CharPointData& aNextCharPointData); + + /** + * ComputeTargetRanges() computes actual delete ranges which will be deleted + * unless the following `beforeinput` event is canceled. + * + * @param aDirectionAndAmount The direction and amount of deletion. + * @param aRangesToDelete [In/Out] The ranges to be deleted, + * typically, initialized with the + * selection ranges. This may be modified + * if selection ranges should be extened. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + ComputeTargetRanges(nsIEditor::EDirection aDirectionAndAmount, + AutoRangeArray& aRangesToDelete); + + /** + * This method handles "delete selection" commands. + * + * @param aDirectionAndAmount Direction of the deletion. + * @param aStripWrappers Must be eStrip or eNoStrip. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual EditActionResult + HandleDeleteSelection(nsIEditor::EDirection aDirectionAndAmount, + nsIEditor::EStripWrappers aStripWrappers) final; + + class AutoDeleteRangesHandler; + + /** + * DeleteMostAncestorMailCiteElementIfEmpty() deletes most ancestor + * mail cite element (`<blockquote type="cite">` or + * `<span _moz_quote="true">`, the former can be created with middle click + * paste with `Control` or `Command` even in the web) of aContent if it + * becomes empty. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + DeleteMostAncestorMailCiteElementIfEmpty(nsIContent& aContent); + + /** + * LiftUpListItemElement() moves aListItemElement outside its parent. + * If it's in a middle of a list element, the parent list element is split + * before aListItemElement. Then, moves aListItemElement to before its + * parent list element. I.e., moves aListItemElement between the 2 list + * elements if original parent was split. Then, if new parent becomes not a + * list element, the list item element is removed and its contents are moved + * to where the list item element was. If aListItemElement becomse not a + * child of list element, its contents are unwrapped from aListItemElement. + * + * @param aListItemElement Must be a <li>, <dt> or <dd> element. + * @param aLiftUpFromAllParentListElements + * If Yes, this method calls itself recursively + * to unwrap the contents in aListItemElement + * from any ancestor list elements. + * XXX This checks only direct parent of list + * elements. Therefore, if a parent list + * element in a list item element, the + * list item element and its list element + * won't be unwrapped. + */ + enum class LiftUpFromAllParentListElements { Yes, No }; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult LiftUpListItemElement( + dom::Element& aListItemElement, + LiftUpFromAllParentListElements aLiftUpFromAllParentListElements); + + /** + * DestroyListStructureRecursively() destroys the list structure of + * aListElement recursively. + * If aListElement has <li>, <dl> or <dt> as a child, the element is removed + * but its descendants are moved to where the list item element was. + * If aListElement has another <ul>, <ol> or <dl> as a child, this method is + * called recursively. + * If aListElement has other nodes as its child, they are just removed. + * Finally, aListElement is removed. and its all children are moved to + * where the aListElement was. + * + * @param aListElement A <ul>, <ol> or <dl> element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + DestroyListStructureRecursively(Element& aListElement); + + /** + * RemoveListAtSelectionAsSubAction() removes list elements and list item + * elements at Selection. And move contents in them where the removed list + * was. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult RemoveListAtSelectionAsSubAction(); + + /** + * ChangeMarginStart() changes margin of aElement to indent or outdent. + * If it's rtl text, margin-right will be changed. Otherwise, margin-left. + * XXX This is not aware of vertical writing-mode. + */ + enum class ChangeMargin { Increase, Decrease }; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + ChangeMarginStart(Element& aElement, ChangeMargin aChangeMargin); + + /** + * HandleCSSIndentAtSelectionInternal() indents around Selection with CSS. + * This method creates AutoSelectionRestorer. Therefore, each caller + * need to check if the editor is still available even if this returns + * NS_OK. + * NOTE: Use HandleCSSIndentAtSelection() instead. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + HandleCSSIndentAtSelectionInternal(); + + /** + * HandleHTMLIndentAtSelectionInternal() indents around Selection with HTML. + * This method creates AutoSelectionRestorer. Therefore, each caller + * need to check if the editor is still available even if this returns + * NS_OK. + * NOTE: Use HandleHTMLIndentAtSelection() instead. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + HandleHTMLIndentAtSelectionInternal(); + + /** + * HandleCSSIndentAtSelection() indents around Selection with CSS. + * NOTE: This is a helper method of `HandleIndentAtSelection()`. If you + * want to call this directly, you should check whether you need + * do do something which `HandleIndentAtSelection()` does. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleCSSIndentAtSelection(); + + /** + * HandleHTMLIndentAtSelection() indents around Selection with HTML. + * NOTE: This is a helper method of `HandleIndentAtSelection()`. If you + * want to call this directly, you should check whether you need + * do do something which `HandleIndentAtSelection()` does. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleHTMLIndentAtSelection(); + + /** + * HandleIndentAtSelection() indents around Selection with HTML or CSS. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult HandleIndentAtSelection(); + + /** + * OutdentPartOfBlock() outdents the nodes between aStartOfOutdent and + * aEndOfOutdent. This splits the range off from aBlockElement first. + * Then, removes the middle element if aIsBlockIndentedWithCSS is false. + * Otherwise, decreases the margin of the middle element. + * + * @param aBlockElement A block element which includes both + * aStartOfOutdent and aEndOfOutdent. + * @param aStartOfOutdent First node which is descendant of + * aBlockElement will be outdented. + * @param aEndOfOutdent Last node which is descandant of + * aBlockElement will be outdented. + * @param aBlockIndentedWith `CSS` if aBlockElement is indented with + * CSS margin property. + * `HTML` if aBlockElement is `<blockquote>` + * or something. + * @return The left content is new created element + * splitting before aStartOfOutdent. + * The right content is existing element. + * The middle content is outdented element + * if aBlockIndentedWith is `CSS`. + * Otherwise, nullptr. + */ + enum class BlockIndentedWith { CSS, HTML }; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT SplitRangeOffFromNodeResult + OutdentPartOfBlock(Element& aBlockElement, nsIContent& aStartOfOutdent, + nsIContent& aEndOutdent, + BlockIndentedWith aBlockIndentedWith); + + /** + * HandleOutdentAtSelectionInternal() outdents contents around Selection. + * This method creates AutoSelectionRestorer. Therefore, each caller + * needs to check if the editor is still available even if this returns + * NS_OK. + * NOTE: Call `HandleOutdentAtSelection()` instead. + * + * @return The left content is left content of last + * outdented element. + * The right content is right content of last + * outdented element. + * The middle content is middle content of last + * outdented element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT SplitRangeOffFromNodeResult + HandleOutdentAtSelectionInternal(); + + /** + * HandleOutdentAtSelection() outdents contents around Selection. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult HandleOutdentAtSelection(); + + /** + * AlignBlockContentsWithDivElement() sets align attribute of <div> element + * which is only child of aBlockElement to aAlignType. If aBlockElement + * has 2 or more children or does not have a `<div>` element, inserts a + * new `<div>` element into aBlockElement and move all children of + * aBlockElement into the new `<div>` element. + * + * @param aBlockElement The element node whose contents should be + * aligned to aAlignType. This should be + * an element which can have `<div>` element + * as its child. + * @param aAlignType New value of align attribute of `<div>` + * element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult AlignBlockContentsWithDivElement( + dom::Element& aBlockElement, const nsAString& aAlignType); + + /** + * AlignContentsInAllTableCellsAndListItems() calls + * AlignBlockContentsWithDivElement() for aligning contents in every list + * item element and table cell element in aElement. + * + * @param aElement The node which is or whose descendants should + * be aligned to aAlignType. + * @param aAlignType New value of `align` attribute of `<div>`. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + AlignContentsInAllTableCellsAndListItems(dom::Element& aElement, + const nsAString& aAlignType); + + /** + * MakeTransitionList() detects all the transitions in the array, where a + * transition means that adjacent nodes in the array don't have the same + * parent. + */ + static void MakeTransitionList( + const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, + nsTArray<bool>& aTransitionArray); + + /** + * EnsureHardLineBeginsWithFirstChildOf() inserts `<br>` element before + * first child of aRemovingContainerElement if it will not be start of a + * hard line after removing aRemovingContainerElement. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + EnsureHardLineBeginsWithFirstChildOf(dom::Element& aRemovingContainerElement); + + /** + * EnsureHardLineEndsWithLastChildOf() inserts `<br>` element after last + * child of aRemovingContainerElement if it will not be end of a hard line + * after removing aRemovingContainerElement. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + EnsureHardLineEndsWithLastChildOf(dom::Element& aRemovingContainerElement); + + /** + * RemoveAlignFromDescendants() removes align attributes, text-align + * properties and <center> elements in aElement. + * + * @param aElement Alignment information of the node and/or its + * descendants will be removed. + * NOTE: aElement must not be a `<table>` element. + * @param aAlignType New align value to be set only when it's in + * CSS mode and this method meets <table> or <hr>. + * XXX This is odd and not clear when you see caller of + * this method. Do you have better idea? + * @param aEditTarget If `OnlyDescendantsExceptTable`, modifies only + * descendants of aElement. + * If `NodeAndDescendantsExceptTable`, modifies `aElement` + * and its descendants. + */ + enum class EditTarget { + OnlyDescendantsExceptTable, + NodeAndDescendantsExceptTable + }; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult RemoveAlignFromDescendants( + Element& aElement, const nsAString& aAlignType, EditTarget aEditTarget); + + /** + * SetBlockElementAlign() resets `align` attribute, `text-align` property + * of descendants of aBlockOrHRElement except `<table>` element descendants. + * Then, set `align` attribute or `text-align` property of aBlockOrHRElement. + * + * @param aBlockOrHRElement The element whose contents will be aligned. + * This must be a block element or `<hr>` element. + * If we're not in CSS mode, this element has + * to support `align` attribute (i.e., + * `HTMLEditUtils::SupportsAlignAttr()` must + * return true). + * @param aAlignType Boundary or "center" which contents should be + * aligned on. + * @param aEditTarget If `OnlyDescendantsExceptTable`, modifies only + * descendants of aBlockOrHRElement. + * If `NodeAndDescendantsExceptTable`, modifies + * aBlockOrHRElement and its descendants. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + SetBlockElementAlign(Element& aBlockOrHRElement, const nsAString& aAlignType, + EditTarget aEditTarget); + + /** + * AlignContentsAtSelectionWithEmptyDivElement() inserts new `<div>` element + * at `Selection` to align selected contents. This returns as "handled" + * if this modifies `Selection` so that callers shouldn't modify `Selection` + * in such case especially when using AutoSelectionRestorer. + * + * @param aAlignType New align attribute value where the contents + * should be aligned to. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult + AlignContentsAtSelectionWithEmptyDivElement(const nsAString& aAlignType); + + /** + * AlignNodesAndDescendants() make contents of nodes in aArrayOfContents and + * their descendants aligned to aAlignType. + * + * @param aAlignType New align attribute value where the contents + * should be aligned to. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult AlignNodesAndDescendants( + nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, + const nsAString& aAlignType); + + /** + * AlignContentsAtSelection() aligns contents around Selection to aAlignType. + * This creates AutoSelectionRestorer. Therefore, even if this returns + * NS_OK, we might have been destroyed. So, every caller needs to check if + * Destroyed() returns false before modifying the DOM tree or changing + * Selection. + * NOTE: Call AlignAsSubAction() instead. + * + * @param aAlignType New align attribute value where the contents + * should be aligned to. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + AlignContentsAtSelection(const nsAString& aAlignType); + + /** + * AlignAsSubAction() handles "align" command with `Selection`. + * + * @param aAlignType New align attribute value where the contents + * should be aligned to. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult + AlignAsSubAction(const nsAString& aAlignType); + + /** + * StartOrEndOfSelectionRangesIsIn() returns true if start or end of one + * of selection ranges is in aContent. + */ + bool StartOrEndOfSelectionRangesIsIn(nsIContent& aContent) const; + + /** + * FindNearEditableContent() tries to find an editable node near aPoint. + * + * @param aPoint The DOM point where to start to search from. + * @param aDirection If nsIEditor::ePrevious is set, this searches an + * editable node from next nodes. Otherwise, from + * previous nodes. + * @return If found, returns non-nullptr. Otherwise, nullptr. + * Note that if found node is in different table element, + * this returns nullptr. + * And also if aDirection is not nsIEditor::ePrevious, + * the result may be the node pointed by aPoint. + */ + template <typename PT, typename CT> + nsIContent* FindNearEditableContent(const EditorDOMPointBase<PT, CT>& aPoint, + nsIEditor::EDirection aDirection); + + /** + * AdjustCaretPositionAndEnsurePaddingBRElement() may adjust caret + * position to nearest editable content and if padding `<br>` element is + * necessary at caret position, this creates it. + * + * @param aDirectionAndAmount Direction of the edit action. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + AdjustCaretPositionAndEnsurePaddingBRElement( + nsIEditor::EDirection aDirectionAndAmount); + + /** + * EnsureSelectionInBodyOrDocumentElement() collapse `Selection` to the + * primary `<body>` element or document element when `Selection` crosses + * `<body>` element's boundary. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + EnsureSelectionInBodyOrDocumentElement(); + + /** + * InsertBRElementToEmptyListItemsAndTableCellsInRange() inserts + * `<br>` element into empty list item or table cell elements between + * aStartRef and aEndRef. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + InsertBRElementToEmptyListItemsAndTableCellsInRange( + const RawRangeBoundary& aStartRef, const RawRangeBoundary& aEndRef); + + /** + * RemoveEmptyNodesIn() removes all empty nodes in aRange. However, if + * mail-cite node has only a `<br>` element, the node will be removed + * but <br> element is moved to where the mail-cite node was. + * XXX This method is expensive if aRange is too wide and may remove + * unexpected empty element, e.g., it was created by JS, but we haven't + * touched it. Cannot we remove this method and make guarantee that + * empty nodes won't be created? + * + * @param aRange Must be positioned. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult RemoveEmptyNodesIn(nsRange& aRange); + + /** + * SetSelectionInterlinePosition() may set interline position if caret is + * positioned around `<br>` or block boundary. Don't call this when + * `Selection` is not collapsed. + */ + void SetSelectionInterlinePosition(); + + /** + * EnsureSelectionInBlockElement() may move caret into aElement or its + * parent block if caret is outside of them. Don't call this when + * `Selection` is not collapsed. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + EnsureCaretInBlockElement(dom::Element& aElement); + + /** + * Called by `HTMLEditor::OnEndHandlingTopLevelEditSubAction()`. This may + * adjust Selection, remove unnecessary empty nodes, create `<br>` elements + * if needed, etc. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + OnEndHandlingTopLevelEditSubActionInternal(); + + /** + * MoveSelectedContentsToDivElementToMakeItAbsolutePosition() looks for + * a `<div>` element in selection first. If not, creates new `<div>` + * element. Then, move all selected contents into the target `<div>` + * element. + * Note that this creates AutoSelectionRestorer. Therefore, callers need + * to check whether we have been destroyed even when this returns NS_OK. + * + * @param aTargetElement Returns target `<div>` element which should be + * changed to absolute positioned. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + MoveSelectedContentsToDivElementToMakeItAbsolutePosition( + RefPtr<Element>* aTargetElement); + + /** + * SetSelectionToAbsoluteAsSubAction() move selected contents to first + * selected `<div>` element or newly created `<div>` element and make + * the `<div>` element positioned absolutely. + * mNewBlockElement of TopLevelEditSubActionData will be set to the `<div>` + * element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult + SetSelectionToAbsoluteAsSubAction(); + + /** + * SetSelectionToStaticAsSubAction() sets the `position` property of a + * selection parent's block whose `position` is `absolute` to `static`. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult + SetSelectionToStaticAsSubAction(); + + /** + * AddZIndexAsSubAction() adds aChange to `z-index` of nearest parent + * absolute-positioned element from current selection. + * + * @param aChange Amount to change `z-index`. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult + AddZIndexAsSubAction(int32_t aChange); + + /** + * OnDocumentModified() is called when editor content is changed. + */ + MOZ_CAN_RUN_SCRIPT nsresult OnDocumentModified(); + + protected: // Called by helper classes. + MOZ_CAN_RUN_SCRIPT virtual void OnStartToHandleTopLevelEditSubAction( + EditSubAction aTopLevelEditSubAction, + nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, + ErrorResult& aRv) override; + MOZ_CAN_RUN_SCRIPT virtual nsresult OnEndHandlingTopLevelEditSubAction() + override; + + protected: // Shouldn't be used by friend classes + virtual ~HTMLEditor(); + + template <typename PT, typename CT> + [[nodiscard]] MOZ_CAN_RUN_SCRIPT MOZ_NEVER_INLINE_DEBUG nsresult + CollapseSelectionTo(const EditorDOMPointBase<PT, CT>& aPoint) const { + ErrorResult error; + CollapseSelectionTo(aPoint, error); + return error.StealNSResult(); + } + + template <typename PT, typename CT> + MOZ_CAN_RUN_SCRIPT MOZ_NEVER_INLINE_DEBUG void CollapseSelectionTo( + const EditorDOMPointBase<PT, CT>& aPoint, ErrorResult& aRv) const { + MOZ_ASSERT(IsEditActionDataAvailable()); + MOZ_ASSERT(!aRv.Failed()); + + MOZ_KnownLive(SelectionRefPtr())->CollapseInLimiter(aPoint, aRv); + if (NS_WARN_IF(Destroyed())) { + aRv = NS_ERROR_EDITOR_DESTROYED; + return; + } + NS_WARNING_ASSERTION(!aRv.Failed(), + "Selection::CollapseInLimiter() failed"); + } + + [[nodiscard]] MOZ_CAN_RUN_SCRIPT MOZ_NEVER_INLINE_DEBUG nsresult + CollapseSelectionToStartOf(nsINode& aNode) { + return CollapseSelectionTo(EditorRawDOMPoint(&aNode, 0)); + } + + MOZ_CAN_RUN_SCRIPT MOZ_NEVER_INLINE_DEBUG void CollapseSelectionToStartOf( + nsINode& aNode, ErrorResult& aRv) const { + CollapseSelectionTo(EditorRawDOMPoint(&aNode, 0), aRv); + } + + /** + * InitEditorContentAndSelection() may insert `<br>` elements and padding + * `<br>` elements if they are required for `<body>` or document element. + * And collapse selection at the end if there is no selection ranges. + * XXX I think that this should work with active editing host unless + * all over the document is ediable (i.e., in design mode or `<body>` + * or `<html>` has `contenteditable` attribute). + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InitEditorContentAndSelection(); + + MOZ_CAN_RUN_SCRIPT virtual nsresult SelectAllInternal() override; + + /** + * SelectContentInternal() sets Selection to aContentToSelect to + * aContentToSelect + 1 in parent of aContentToSelect. + * + * @param aContentToSelect The content which should be selected. + */ + MOZ_CAN_RUN_SCRIPT nsresult + SelectContentInternal(nsIContent& aContentToSelect); + + /** + * CollapseSelectionAfter() collapses Selection after aElement. + * If aElement is an orphan node or not in editing host, returns error. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + CollapseSelectionAfter(Element& aElement); + + /** + * GetInclusiveAncestorByTagNameAtSelection() looks for an element node whose + * name matches aTagName from anchor node of Selection to <body> element. + * + * @param aTagName The tag name which you want to look for. + * Must not be nsGkAtoms::_empty. + * If nsGkAtoms::list, the result may be <ul>, <ol> or + * <dl> element. + * If nsGkAtoms::td, the result may be <td> or <th>. + * If nsGkAtoms::href, the result may be <a> element + * which has "href" attribute with non-empty value. + * If nsGkAtoms::anchor, the result may be <a> which + * has "name" attribute with non-empty value. + * @return If an element which matches aTagName, returns + * an Element. Otherwise, nullptr. + */ + Element* GetInclusiveAncestorByTagNameAtSelection( + const nsStaticAtom& aTagName) const; + + /** + * GetInclusiveAncestorByTagNameInternal() looks for an element node whose + * name matches aTagName from aNode to <body> element. + * + * @param aTagName The tag name which you want to look for. + * Must not be nsGkAtoms::_empty. + * If nsGkAtoms::list, the result may be <ul>, <ol> or + * <dl> element. + * If nsGkAtoms::td, the result may be <td> or <th>. + * If nsGkAtoms::href, the result may be <a> element + * which has "href" attribute with non-empty value. + * If nsGkAtoms::anchor, the result may be <a> which + * has "name" attribute with non-empty value. + * @param aContent Start node to look for the element. This should + * not be an orphan node. + * @return If an element which matches aTagName, returns + * an Element. Otherwise, nullptr. + */ + Element* GetInclusiveAncestorByTagNameInternal(const nsStaticAtom& aTagName, + nsIContent& aContent) const; + + /** + * GetSelectedElement() returns a "selected" element node. "selected" means: + * - there is only one selection range + * - the range starts from an element node or in an element + * - the range ends at immediately after same element + * - and the range does not include any other element nodes. + * Additionally, only when aTagName is nsGkAtoms::href, this thinks that an + * <a> element which has non-empty "href" attribute includes the range, the + * <a> element is selected. + * + * NOTE: This method is implementation of nsIHTMLEditor.getSelectedElement() + * and comm-central depends on this behavior. Therefore, if you need to use + * this method internally but you need to change, perhaps, you should create + * another method for avoiding breakage of comm-central apps. + * + * @param aTagName The atom of tag name in lower case. Set this to + * result of EditorUtils::GetTagNameAtom() if you have a + * tag name with nsString. + * If nullptr, this returns any element node or nullptr. + * If nsGkAtoms::href, this returns an <a> element which + * has non-empty "href" attribute or nullptr. + * If nsGkAtoms::anchor, this returns an <a> element which + * has non-empty "name" attribute or nullptr. + * Otherwise, returns an element node whose name is + * same as aTagName or nullptr. + * @param aRv Returns error code. + * @return A "selected" element. + */ + already_AddRefed<Element> GetSelectedElement(const nsAtom* aTagName, + ErrorResult& aRv); + + /** + * GetFirstTableRowElement() returns the first <tr> element in the most + * nearest ancestor of aTableOrElementInTable or itself. + * + * @param aTableOrElementInTable <table> element or another element. + * If this is a <table> element, returns + * first <tr> element in it. Otherwise, + * returns first <tr> element in nearest + * ancestor <table> element. + * @param aRv Returns an error code. When + * aTableOrElementInTable is neither + * <table> nor in a <table> element, + * returns NS_ERROR_FAILURE. + * However, if <table> does not have + * <tr> element, returns NS_OK. + */ + Element* GetFirstTableRowElement(Element& aTableOrElementInTable, + ErrorResult& aRv) const; + + /** + * GetNextTableRowElement() returns next <tr> element of aTableRowElement. + * This won't cross <table> element boundary but may cross table section + * elements like <tbody>. + * + * @param aTableRowElement A <tr> element. + * @param aRv Returns error. If given element is <tr> but + * there is no next <tr> element, this returns + * nullptr but does not return error. + */ + Element* GetNextTableRowElement(Element& aTableRowElement, + ErrorResult& aRv) const; + + struct CellData; + + /** + * CellIndexes store both row index and column index of a table cell. + */ + struct MOZ_STACK_CLASS CellIndexes final { + int32_t mRow; + int32_t mColumn; + + /** + * This constructor initializes mRowIndex and mColumnIndex with indexes of + * aCellElement. + * + * @param aCellElement An <td> or <th> element. + * @param aRv Returns error if layout information is not + * available or given element is not a table cell. + */ + MOZ_CAN_RUN_SCRIPT CellIndexes(Element& aCellElement, PresShell* aPresShell, + ErrorResult& aRv) + : mRow(-1), mColumn(-1) { + MOZ_ASSERT(!aRv.Failed()); + Update(aCellElement, aPresShell, aRv); + } + + /** + * Update mRowIndex and mColumnIndex with indexes of aCellElement. + * + * @param See above. + */ + MOZ_CAN_RUN_SCRIPT void Update(Element& aCellElement, PresShell* aPresShell, + ErrorResult& aRv); + + /** + * This constructor initializes mRowIndex and mColumnIndex with indexes of + * cell element which contains anchor of Selection. + * + * @param aHTMLEditor The editor which creates the instance. + * @param aSelection The Selection for the editor. + * @param aRv Returns error if there is no cell element + * which contains anchor of Selection, or layout + * information is not available. + */ + MOZ_CAN_RUN_SCRIPT CellIndexes(HTMLEditor& aHTMLEditor, + Selection& aSelection, ErrorResult& aRv) + : mRow(-1), mColumn(-1) { + Update(aHTMLEditor, aSelection, aRv); + } + + /** + * Update mRowIndex and mColumnIndex with indexes of cell element which + * contains anchor of Selection. + * + * @param See above. + */ + MOZ_CAN_RUN_SCRIPT void Update(HTMLEditor& aHTMLEditor, + Selection& aSelection, ErrorResult& aRv); + + bool operator==(const CellIndexes& aOther) const { + return mRow == aOther.mRow && mColumn == aOther.mColumn; + } + bool operator!=(const CellIndexes& aOther) const { + return mRow != aOther.mRow || mColumn != aOther.mColumn; + } + + private: + CellIndexes() : mRow(-1), mColumn(-1) {} + + friend struct CellData; + }; + + struct MOZ_STACK_CLASS CellData final { + RefPtr<Element> mElement; + // Current indexes which this is initialized with. + CellIndexes mCurrent; + // First column/row indexes of the cell. When current position is spanned + // from other column/row, this value becomes different from mCurrent. + CellIndexes mFirst; + // Computed rowspan/colspan values which are specified to the cell. + // Note that if the cell has larger rowspan/colspan value than actual + // table size, these values are the larger values. + int32_t mRowSpan; + int32_t mColSpan; + // Effective rowspan/colspan value at the index. For example, if first + // cell element in first row has rowspan="3", then, if this is initialized + // with 0-0 indexes, effective rowspan is 3. However, if this is + // initialized with 1-0 indexes, effective rowspan is 2. + int32_t mEffectiveRowSpan; + int32_t mEffectiveColSpan; + // mIsSelected is set to true if mElement itself or its parent <tr> or + // <table> is selected. Otherwise, e.g., the cell just contains selection + // range, this is set to false. + bool mIsSelected; + + CellData() + : mRowSpan(-1), + mColSpan(-1), + mEffectiveRowSpan(-1), + mEffectiveColSpan(-1), + mIsSelected(false) {} + + /** + * Those constructors initializes the members with a <table> element and + * both row and column index to specify a cell element. + */ + CellData(HTMLEditor& aHTMLEditor, Element& aTableElement, int32_t aRowIndex, + int32_t aColumnIndex, ErrorResult& aRv) { + Update(aHTMLEditor, aTableElement, aRowIndex, aColumnIndex, aRv); + } + + CellData(HTMLEditor& aHTMLEditor, Element& aTableElement, + const CellIndexes& aIndexes, ErrorResult& aRv) { + Update(aHTMLEditor, aTableElement, aIndexes, aRv); + } + + /** + * Those Update() methods updates the members with a <table> element and + * both row and column index to specify a cell element. + */ + void Update(HTMLEditor& aHTMLEditor, Element& aTableElement, + int32_t aRowIndex, int32_t aColumnIndex, ErrorResult& aRv) { + mCurrent.mRow = aRowIndex; + mCurrent.mColumn = aColumnIndex; + Update(aHTMLEditor, aTableElement, aRv); + } + + void Update(HTMLEditor& aHTMLEditor, Element& aTableElement, + const CellIndexes& aIndexes, ErrorResult& aRv) { + mCurrent = aIndexes; + Update(aHTMLEditor, aTableElement, aRv); + } + + void Update(HTMLEditor& aHTMLEditor, Element& aTableElement, + ErrorResult& aRv); + + /** + * FailedOrNotFound() returns true if this failed to initialize/update + * or succeeded but found no cell element. + */ + bool FailedOrNotFound() const { return !mElement; } + + /** + * IsSpannedFromOtherRowOrColumn(), IsSpannedFromOtherColumn and + * IsSpannedFromOtherRow() return true if there is no cell element + * at the index because of spanning from other row and/or column. + */ + bool IsSpannedFromOtherRowOrColumn() const { + return mElement && mCurrent != mFirst; + } + bool IsSpannedFromOtherColumn() const { + return mElement && mCurrent.mColumn != mFirst.mColumn; + } + bool IsSpannedFromOtherRow() const { + return mElement && mCurrent.mRow != mFirst.mRow; + } + + /** + * NextColumnIndex() and NextRowIndex() return column/row index of + * next cell. Note that this does not check whether there is next + * cell or not actually. + */ + int32_t NextColumnIndex() const { + if (NS_WARN_IF(FailedOrNotFound())) { + return -1; + } + return mCurrent.mColumn + mEffectiveColSpan; + } + int32_t NextRowIndex() const { + if (NS_WARN_IF(FailedOrNotFound())) { + return -1; + } + return mCurrent.mRow + mEffectiveRowSpan; + } + + /** + * LastColumnIndex() and LastRowIndex() return column/row index of + * column/row which is spanned by the cell. + */ + int32_t LastColumnIndex() const { + if (NS_WARN_IF(FailedOrNotFound())) { + return -1; + } + return NextColumnIndex() - 1; + } + int32_t LastRowIndex() const { + if (NS_WARN_IF(FailedOrNotFound())) { + return -1; + } + return NextRowIndex() - 1; + } + + /** + * NumberOfPrecedingColmuns() and NumberOfPrecedingRows() return number of + * preceding columns/rows if current index is spanned from other column/row. + * Otherwise, i.e., current point is not spanned form other column/row, + * returns 0. + */ + int32_t NumberOfPrecedingColmuns() const { + if (NS_WARN_IF(FailedOrNotFound())) { + return -1; + } + return mCurrent.mColumn - mFirst.mColumn; + } + int32_t NumberOfPrecedingRows() const { + if (NS_WARN_IF(FailedOrNotFound())) { + return -1; + } + return mCurrent.mRow - mFirst.mRow; + } + + /** + * NumberOfFollowingColumns() and NumberOfFollowingRows() return + * number of remaining columns/rows if the cell spans to other + * column/row. + */ + int32_t NumberOfFollowingColumns() const { + if (NS_WARN_IF(FailedOrNotFound())) { + return -1; + } + return mEffectiveColSpan - 1; + } + int32_t NumberOfFollowingRows() const { + if (NS_WARN_IF(FailedOrNotFound())) { + return -1; + } + return mEffectiveRowSpan - 1; + } + }; + + /** + * TableSize stores and computes number of rows and columns of a <table> + * element. + */ + struct MOZ_STACK_CLASS TableSize final { + int32_t mRowCount; + int32_t mColumnCount; + + /** + * @param aHTMLEditor The editor which creates the instance. + * @param aTableOrElementInTable If a <table> element, computes number + * of rows and columns of it. + * If another element in a <table> element, + * computes number of rows and columns + * of nearest ancestor <table> element. + * Otherwise, i.e., non-<table> element + * not in <table>, returns error. + * @param aRv Returns error if the element is not + * in <table> or layout information is + * not available. + */ + TableSize(HTMLEditor& aHTMLEditor, Element& aTableOrElementInTable, + ErrorResult& aRv) + : mRowCount(-1), mColumnCount(-1) { + MOZ_ASSERT(!aRv.Failed()); + Update(aHTMLEditor, aTableOrElementInTable, aRv); + } + + /** + * Update mRowCount and mColumnCount for aTableOrElementInTable. + * See above for the detail. + */ + void Update(HTMLEditor& aHTMLEditor, Element& aTableOrElementInTable, + ErrorResult& aRv); + + bool IsEmpty() const { return !mRowCount || !mColumnCount; } + }; + + /** + * GetTableCellElementAt() returns a <td> or <th> element of aTableElement + * if there is a cell at the indexes. + * + * @param aTableElement Must be a <table> element. + * @param aCellIndexes Indexes of cell which you want. + * If rowspan and/or colspan is specified 2 or + * larger, any indexes are allowed to retrieve + * the cell in the area. + * @return The cell element if there is in the <table>. + * Returns nullptr without error if the indexes + * are out of bounds. + */ + Element* GetTableCellElementAt(Element& aTableElement, + const CellIndexes& aCellIndexes) const { + return GetTableCellElementAt(aTableElement, aCellIndexes.mRow, + aCellIndexes.mColumn); + } + Element* GetTableCellElementAt(Element& aTableElement, int32_t aRowIndex, + int32_t aColumnIndex) const; + + /** + * GetSelectedOrParentTableElement() returns <td>, <th>, <tr> or <table> + * element: + * #1 if the first selection range selects a cell, returns it. + * #2 if the first selection range does not select a cell and + * the selection anchor refers a <table>, returns it. + * #3 if the first selection range does not select a cell and + * the selection anchor refers a <tr>, returns it. + * #4 if the first selection range does not select a cell and + * the selection anchor refers a <td>, returns it. + * #5 otherwise, nearest ancestor <td> or <th> element of the + * selection anchor if there is. + * In #1 and #4, *aIsCellSelected will be set to true (i.e,, when + * a selection range selects a cell element). + */ + already_AddRefed<Element> GetSelectedOrParentTableElement( + ErrorResult& aRv, bool* aIsCellSelected = nullptr) const; + + /** + * PasteInternal() pasts text with replacing selected content. + * This tries to dispatch ePaste event first. If its defaultPrevent() is + * called, this does nothing but returns NS_OK. + * + * @param aClipboardType nsIClipboard::kGlobalClipboard or + * nsIClipboard::kSelectionClipboard. + */ + MOZ_CAN_RUN_SCRIPT nsresult PasteInternal(int32_t aClipboardType); + + /** + * 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) final; + + /** + * InsertAsCitedQuotationInternal() inserts a <blockquote> element whose + * cite attribute is aCitation and whose content is aQuotedText. + * Note that this shouldn't be called when IsPlaintextEditor() is true. + * + * @param aQuotedText HTML source if aInsertHTML is true. Otherwise, + * plain text. This is inserted into new <blockquote> + * element. + * @param aCitation cite attribute value of new <blockquote> element. + * @param aInsertHTML true if aQuotedText should be treated as HTML + * source. + * false if aQuotedText should be treated as plain + * text. + * @param aNodeInserted [OUT] The new <blockquote> element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InsertAsCitedQuotationInternal( + const nsAString& aQuotedText, const nsAString& aCitation, + bool aInsertHTML, nsINode** aNodeInserted); + + /** + * InsertNodeIntoProperAncestorWithTransaction() attempts to insert aNode + * into the document, at aPointToInsert. Checks with strict dtd to see if + * containment is allowed. If not allowed, will attempt to find a parent + * in the parent hierarchy of aPointToInsert.GetContainer() that will accept + * aNode as a child. If such a parent is found, will split the document + * tree from aPointToInsert up to parent, and then insert aNode. + * aPointToInsert is then adjusted to point to the actual location that + * aNode was inserted at. aSplitAtEdges specifies if the splitting process + * is allowed to result in empty nodes. + * + * @param aNode Node to insert. + * @param aPointToInsert Insertion point. + * @param aSplitAtEdges Splitting can result in empty nodes? + * @return Returns inserted point if succeeded. + * Otherwise, the result is not set. + */ + MOZ_CAN_RUN_SCRIPT EditorDOMPoint InsertNodeIntoProperAncestorWithTransaction( + nsIContent& aNode, const EditorDOMPoint& aPointToInsert, + SplitAtEdges aSplitAtEdges); + + /** + * InsertBRElementAtSelectionWithTransaction() inserts a new <br> element at + * selection. If there is non-collapsed selection ranges, the selected + * ranges is deleted first. + */ + MOZ_CAN_RUN_SCRIPT nsresult InsertBRElementAtSelectionWithTransaction(); + + /** + * InsertTextWithQuotationsInternal() replaces selection with new content. + * First, this method splits aStringToInsert to multiple chunks which start + * with non-linebreaker except first chunk and end with a linebreaker except + * last chunk. Then, each chunk starting with ">" is inserted after wrapping + * with <span _moz_quote="true">, and each chunk not starting with ">" is + * inserted as normal text. + */ + MOZ_CAN_RUN_SCRIPT nsresult + InsertTextWithQuotationsInternal(const nsAString& aStringToInsert); + + /** + * ReplaceContainerWithTransactionInternal() is implementation of + * ReplaceContainerWithTransaction() and + * ReplaceContainerAndCloneAttributesWithTransaction(). + * + * @param aOldContainer The element which will be replaced with new + * element. + * @param aTagName The name of new element node. + * @param aAttribute Attribute name which will be set to the new + * element. This will be ignored if + * aCloneAllAttributes is set to true. + * @param aAttributeValue Attribute value which will be set to + * aAttribute. + * @param aCloneAllAttributes If true, all attributes of aOldContainer will + * be copied to the new element. + */ + MOZ_CAN_RUN_SCRIPT already_AddRefed<Element> + ReplaceContainerWithTransactionInternal(Element& aElement, nsAtom& aTagName, + nsAtom& aAttribute, + const nsAString& aAttributeValue, + bool aCloneAllAttributes); + + /** + * InsertContainerWithTransactionInternal() creates new element whose name is + * aTagName, moves aContent into the new element, then, inserts the new + * element into where aContent was. If aAttribute is not nsGkAtoms::_empty, + * aAttribute of the new element will be set to aAttributeValue. + * + * @param aContent The content which will be wrapped with new + * element. + * @param aTagName Element name of new element which will wrap + * aContent and be inserted into where aContent + * was. + * @param aAttribute Attribute which should be set to the new + * element. If this is nsGkAtoms::_empty, + * this does not set any attributes to the new + * element. + * @param aAttributeValue Value to be set to aAttribute. + * @return The new element. + */ + MOZ_CAN_RUN_SCRIPT already_AddRefed<Element> + InsertContainerWithTransactionInternal(nsIContent& aContent, nsAtom& aTagName, + nsAtom& aAttribute, + const nsAString& aAttributeValue); + + /** + * DeleteSelectionAndCreateElement() creates a element whose name is aTag. + * And insert it into the DOM tree after removing the selected content. + * + * @param aTag The element name to be created. + * @return Created new element. + */ + MOZ_CAN_RUN_SCRIPT already_AddRefed<Element> DeleteSelectionAndCreateElement( + nsAtom& aTag); + + /** + * This method first deletes the selection, if it's not collapsed. Then if + * the selection lies in a CharacterData node, it splits it. If the + * selection is at this point collapsed in a CharacterData node, it's + * adjusted to be collapsed right before or after the node instead (which is + * always possible, since the node was split). + */ + MOZ_CAN_RUN_SCRIPT nsresult DeleteSelectionAndPrepareToCreateNode(); + + /** + * PrepareToInsertBRElement() returns a point where new <br> element should + * be inserted. If aPointToInsert points middle of a text node, this method + * splits the text node and returns the point before right node. + * + * @param aPointToInsert Candidate point to insert new <br> element. + * @return Computed point to insert new <br> element. + * If something failed, this is unset. + */ + MOZ_CAN_RUN_SCRIPT EditorDOMPoint + PrepareToInsertBRElement(const EditorDOMPoint& aPointToInsert); + + /** + * IndentAsSubAction() indents the content around Selection. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult IndentAsSubAction(); + + /** + * OutdentAsSubAction() outdents the content around Selection. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult OutdentAsSubAction(); + + MOZ_CAN_RUN_SCRIPT nsresult LoadHTML(const nsAString& aInputString); + + /** + * SetInlinePropertyInternal() stores new style with `mTypeInState` if + * `Selection` is collapsed. Otherwise, applying the style at all selection + * ranges. + * + * @param aProperty One of the presentation tag names which we + * support in style editor. + * @param aAttribute For some aProperty values, needs to be set to + * its attribute name. Otherwise, nullptr. + * @param aAttributeValue The value of aAttribute. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetInlinePropertyInternal( + nsAtom& aProperty, nsAtom* aAttribute, const nsAString& aValue); + + /** + * RemoveInlinePropertyInternal() removes specified style from `mTypeInState` + * if `Selection` is collapsed. Otherwise, removing the style. + * + * @param aHTMLProperty nullptr if you want to remove all inline styles. + * Otherwise, one of the presentation tag names + * which we support in style editor. + * @param aAttribute For some aHTMLProperty values, need to be set to + * its attribute name. Otherwise, nullptr. + * @param aRemoveRelatedElements If Yes, this method removes different + * name's elements in the block if + * necessary. For example, if + * aHTMLProperty is nsGkAtoms::b, + * `<strong>` elements are also removed. + */ + enum class RemoveRelatedElements { Yes, No }; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult RemoveInlinePropertyInternal( + nsStaticAtom* aHTMLProperty, nsStaticAtom* aAttribute, + RemoveRelatedElements aRemoveRelatedElements); + + /** + * ReplaceHeadContentsWithSourceWithTransaction() replaces all children of + * <head> element with given source code. This is undoable. + * + * @param aSourceToInsert HTML source fragment to replace the children + * of <head> element. + */ + MOZ_CAN_RUN_SCRIPT nsresult ReplaceHeadContentsWithSourceWithTransaction( + const nsAString& aSourceToInsert); + + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult GetCSSBackgroundColorState( + bool* aMixed, nsAString& aOutColor, bool aBlockLevel); + nsresult GetHTMLBackgroundColorState(bool* aMixed, nsAString& outColor); + + nsresult GetLastCellInRow(nsINode* aRowNode, nsINode** aCellNode); + + /** + * This sets background on the appropriate container element (table, cell,) + * or calls into nsTextEditor to set the page background. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + SetCSSBackgroundColorWithTransaction(const nsAString& aColor); + MOZ_CAN_RUN_SCRIPT nsresult + SetHTMLBackgroundColorWithTransaction(const nsAString& aColor); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void InitializeSelectionAncestorLimit( + nsIContent& aAncestorLimit) const override; + + /** + * Make the given selection span the entire document. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual nsresult SelectEntireDocument() + override; + + /** + * Use this to assure that selection is set after attribute nodes when + * trying to collapse selection at begining of a block node + * e.g., when setting at beginning of a table cell + * This will stop at a table, however, since we don't want to + * "drill down" into nested tables. + */ + MOZ_CAN_RUN_SCRIPT void CollapseSelectionToDeepestNonTableFirstChild( + nsINode* aNode); + /** + * MaybeCollapseSelectionAtFirstEditableNode() may collapse selection at + * proper position to staring to edit. If there is a non-editable node + * before any editable text nodes or inline elements which can have text + * nodes as their children, collapse selection at start of the editing + * host. If there is an editable text node which is not collapsed, collapses + * selection at the start of the text node. If there is an editable inline + * element which cannot have text nodes as its child, collapses selection at + * before the element node. Otherwise, collapses selection at start of the + * editing host. + * + * @param aIgnoreIfSelectionInEditingHost + * This method does nothing if selection is in the + * editing host except if it's collapsed at start of + * the editing host. + * Note that if selection ranges were outside of + * current selection limiter, selection was collapsed + * at the start of the editing host therefore, if + * you call this with setting this to true, you can + * keep selection ranges if user has already been + * changed. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + MaybeCollapseSelectionAtFirstEditableNode( + bool aIgnoreIfSelectionInEditingHost) const; + + class BlobReader final { + typedef EditorBase::AutoEditActionDataSetter AutoEditActionDataSetter; + + public: + BlobReader(dom::BlobImpl* aBlob, HTMLEditor* aHTMLEditor, bool aIsSafe, + Document* aSourceDoc, const EditorDOMPoint& aPointToInsert, + bool aDoDeleteSelection); + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BlobReader) + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(BlobReader) + + MOZ_CAN_RUN_SCRIPT nsresult OnResult(const nsACString& aResult); + nsresult OnError(const nsAString& aErrorName); + + private: + ~BlobReader() = default; + + RefPtr<dom::BlobImpl> mBlob; + RefPtr<HTMLEditor> mHTMLEditor; + RefPtr<dom::DataTransfer> mDataTransfer; + nsCOMPtr<Document> mSourceDoc; + EditorDOMPoint mPointToInsert; + EditAction mEditAction; + bool mIsSafe; + bool mDoDeleteSelection; + bool mNeedsToDispatchBeforeInputEvent; + }; + + virtual void CreateEventListeners() override; + virtual nsresult InstallEventListeners() override; + virtual void RemoveEventListeners() override; + + bool ShouldReplaceRootElement() const; + MOZ_CAN_RUN_SCRIPT void NotifyRootChanged(); + Element* GetBodyElement() const; + + /** + * Get the focused node of this editor. + * @return If the editor has focus, this returns the focused node. + * Otherwise, returns null. + */ + nsINode* GetFocusedNode() const; + + virtual already_AddRefed<Element> GetInputEventTargetElement() const override; + + /** + * Return TRUE if aElement is a table-related elemet and caret was set. + */ + MOZ_CAN_RUN_SCRIPT bool SetCaretInTableCell(dom::Element* aElement); + + /** + * HandleTabKeyPressInTable() handles "Tab" key press in table if selection + * is in a `<table>` element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult + HandleTabKeyPressInTable(WidgetKeyboardEvent* aKeyboardEvent); + + /** + * InsertPosition is an enum to indicate where the method should insert to. + */ + enum class InsertPosition { + // Before selected cell or a cell containing first selection range. + eBeforeSelectedCell, + // After selected cell or a cell containing first selection range. + eAfterSelectedCell, + }; + + /** + * InsertTableCellsWithTransaction() inserts <td> elements before or after + * a cell element containing first selection range. I.e., if the cell + * spans columns and aInsertPosition is eAfterSelectedCell, new columns + * will be inserted after the right-most column which contains the cell. + * Note that this simply inserts <td> elements, i.e., colspan and rowspan + * around the cell containing selection are not modified. So, for example, + * adding a cell to rectangular table changes non-rectangular table. + * And if the cell containing selection is at left of row-spanning cell, + * it may be moved to right side of the row-spanning cell after inserting + * some cell elements before it. Similarly, colspan won't be adjusted + * for keeping table rectangle. + * If first selection range is not in table cell element, this does nothing + * but does not return error. + * + * @param aNumberOfCellssToInsert Number of cells to insert. + * @param aInsertPosition Before or after the target cell which + * contains first selection range. + */ + MOZ_CAN_RUN_SCRIPT nsresult InsertTableCellsWithTransaction( + int32_t aNumberOfCellsToInsert, InsertPosition aInsertPosition); + + /** + * InsertTableColumnsWithTransaction() inserts columns before or after + * a cell element containing first selection range. I.e., if the cell + * spans columns and aInsertPosition is eAfterSelectedCell, new columns + * will be inserted after the right-most row which contains the cell. + * If first selection range is not in table cell element, this does nothing + * but does not return error. + * + * @param aNumberOfColumnsToInsert Number of columns to insert. + * @param aInsertPosition Before or after the target cell which + * contains first selection range. + */ + MOZ_CAN_RUN_SCRIPT nsresult InsertTableColumnsWithTransaction( + int32_t aNumberOfColumnsToInsert, InsertPosition aInsertPosition); + + /** + * InsertTableRowsWithTransaction() inserts <tr> elements before or after + * a cell element containing first selection range. I.e., if the cell + * spans rows and aInsertPosition is eAfterSelectedCell, new rows will be + * inserted after the most-bottom row which contains the cell. If first + * selection range is not in table cell element, this does nothing but + * does not return error. + * + * @param aNumberOfRowsToInsert Number of rows to insert. + * @param aInsertPosition Before or after the target cell which + * contains first selection range. + */ + MOZ_CAN_RUN_SCRIPT nsresult InsertTableRowsWithTransaction( + int32_t aNumberOfRowsToInsert, InsertPosition aInsertPosition); + + /** + * Insert a new cell after or before supplied aCell. + * Optional: If aNewCell supplied, returns the newly-created cell (addref'd, + * of course) + * This doesn't change or use the current selection. + */ + MOZ_CAN_RUN_SCRIPT nsresult InsertCell(Element* aCell, int32_t aRowSpan, + int32_t aColSpan, bool aAfter, + bool aIsHeader, Element** aNewCell); + + /** + * DeleteSelectedTableColumnsWithTransaction() removes cell elements which + * belong to same columns of selected cell elements. + * If only one cell element is selected or first selection range is + * in a cell, removes cell elements which belong to same column. + * If 2 or more cell elements are selected, removes cell elements which + * belong to any of all selected columns. In this case, + * aNumberOfColumnsToDelete is ignored. + * If there is no selection ranges, returns error. + * If selection is not in a cell element, this does not return error, + * just does nothing. + * WARNING: This does not remove <col> nor <colgroup> elements. + * + * @param aNumberOfColumnsToDelete Number of columns to remove. This is + * ignored if 2 ore more cells are + * selected. + */ + MOZ_CAN_RUN_SCRIPT nsresult + DeleteSelectedTableColumnsWithTransaction(int32_t aNumberOfColumnsToDelete); + + /** + * DeleteTableColumnWithTransaction() removes cell elements which belong + * to the specified column. + * This method adjusts colspan attribute value if cells spanning the + * column to delete. + * WARNING: This does not remove <col> nor <colgroup> elements. + * + * @param aTableElement The <table> element which contains the + * column which you want to remove. + * @param aRowIndex Index of the column which you want to remove. + * 0 is the first column. + */ + MOZ_CAN_RUN_SCRIPT nsresult DeleteTableColumnWithTransaction( + Element& aTableElement, int32_t aColumnIndex); + + /** + * DeleteSelectedTableRowsWithTransaction() removes <tr> elements. + * If only one cell element is selected or first selection range is + * in a cell, removes <tr> elements starting from a <tr> element + * containing the selected cell or first selection range. + * If 2 or more cell elements are selected, all <tr> elements + * which contains selected cell(s). In this case, aNumberOfRowsToDelete + * is ignored. + * If there is no selection ranges, returns error. + * If selection is not in a cell element, this does not return error, + * just does nothing. + * + * @param aNumberOfRowsToDelete Number of rows to remove. This is ignored + * if 2 or more cells are selected. + */ + MOZ_CAN_RUN_SCRIPT nsresult + DeleteSelectedTableRowsWithTransaction(int32_t aNumberOfRowsToDelete); + + /** + * DeleteTableRowWithTransaction() removes a <tr> element whose index in + * the <table> is aRowIndex. + * This method adjusts rowspan attribute value if the <tr> element contains + * cells which spans rows. + * + * @param aTableElement The <table> element which contains the + * <tr> element which you want to remove. + * @param aRowIndex Index of the <tr> element which you want to + * remove. 0 is the first row. + */ + MOZ_CAN_RUN_SCRIPT nsresult + DeleteTableRowWithTransaction(Element& aTableElement, int32_t aRowIndex); + + /** + * DeleteTableCellWithTransaction() removes table cell elements. If two or + * more cell elements are selected, this removes all selected cell elements. + * Otherwise, this removes some cell elements starting from selected cell + * element or a cell containing first selection range. When this removes + * last cell element in <tr> or <table>, this removes the <tr> or the + * <table> too. Note that when removing a cell causes number of its row + * becomes less than the others, this method does NOT fill the place with + * rowspan nor colspan. This does not return error even if selection is not + * in cell element, just does nothing. + * + * @param aNumberOfCellsToDelete Number of cells to remove. This is ignored + * if 2 or more cells are selected. + */ + MOZ_CAN_RUN_SCRIPT nsresult + DeleteTableCellWithTransaction(int32_t aNumberOfCellsToDelete); + + /** + * DeleteAllChildrenWithTransaction() removes all children of aElement from + * the tree. + * + * @param aElement The element whose children you want to remove. + */ + MOZ_CAN_RUN_SCRIPT nsresult + DeleteAllChildrenWithTransaction(Element& aElement); + + /** + * Move all contents from aCellToMerge into aTargetCell (append at end). + */ + MOZ_CAN_RUN_SCRIPT nsresult MergeCells(RefPtr<Element> aTargetCell, + RefPtr<Element> aCellToMerge, + bool aDeleteCellToMerge); + + /** + * DeleteTableElementAndChildren() removes aTableElement (and its children) + * from the DOM tree with transaction. + * + * @param aTableElement The <table> element which you want to remove. + */ + MOZ_CAN_RUN_SCRIPT nsresult + DeleteTableElementAndChildrenWithTransaction(Element& aTableElement); + + MOZ_CAN_RUN_SCRIPT nsresult SetColSpan(Element* aCell, int32_t aColSpan); + MOZ_CAN_RUN_SCRIPT nsresult SetRowSpan(Element* aCell, int32_t aRowSpan); + + /** + * Helper used to get nsTableWrapperFrame for a table. + */ + static nsTableWrapperFrame* GetTableFrame(Element* aTable); + + /** + * GetNumberOfCellsInRow() returns number of actual cell elements in the row. + * If some cells appear by "rowspan" in other rows, they are ignored. + * + * @param aTableElement The <table> element. + * @param aRowIndex Valid row index in aTableElement. This method + * counts cell elements in the row. + * @return -1 if this meets unexpected error. + * Otherwise, number of cells which this method found. + */ + int32_t GetNumberOfCellsInRow(Element& aTableElement, int32_t aRowIndex); + + /** + * Test if all cells in row or column at given index are selected. + */ + bool AllCellsInRowSelected(Element* aTable, int32_t aRowIndex, + int32_t aNumberOfColumns); + bool AllCellsInColumnSelected(Element* aTable, int32_t aColIndex, + int32_t aNumberOfRows); + + bool IsEmptyCell(Element* aCell); + + /** + * Most insert methods need to get the same basic context data. + * Any of the pointers may be null if you don't need that datum (for more + * efficiency). + * Input: *aCell is a known cell, + * if null, cell is obtained from the anchor node of the selection. + * Returns NS_EDITOR_ELEMENT_NOT_FOUND if cell is not found even if aCell is + * null. + */ + MOZ_CAN_RUN_SCRIPT nsresult GetCellContext(Element** aTable, Element** aCell, + nsINode** aCellParent, + int32_t* aCellOffset, + int32_t* aRowIndex, + int32_t* aColIndex); + + nsresult GetCellSpansAt(Element* aTable, int32_t aRowIndex, int32_t aColIndex, + int32_t& aActualRowSpan, int32_t& aActualColSpan); + + MOZ_CAN_RUN_SCRIPT nsresult SplitCellIntoColumns( + Element* aTable, int32_t aRowIndex, int32_t aColIndex, + int32_t aColSpanLeft, int32_t aColSpanRight, Element** aNewCell); + + MOZ_CAN_RUN_SCRIPT nsresult SplitCellIntoRows( + Element* aTable, int32_t aRowIndex, int32_t aColIndex, + int32_t aRowSpanAbove, int32_t aRowSpanBelow, Element** aNewCell); + + MOZ_CAN_RUN_SCRIPT nsresult CopyCellBackgroundColor(Element* aDestCell, + Element* aSourceCell); + + /** + * Reduce rowspan/colspan when cells span into nonexistent rows/columns. + */ + MOZ_CAN_RUN_SCRIPT nsresult FixBadRowSpan(Element* aTable, int32_t aRowIndex, + int32_t& aNewRowCount); + MOZ_CAN_RUN_SCRIPT nsresult FixBadColSpan(Element* aTable, int32_t aColIndex, + int32_t& aNewColCount); + + /** + * XXX NormalizeTableInternal() is broken. If it meets a cell which has + * bigger or smaller rowspan or colspan than actual number of cells, + * this always failed to scan the table. Therefore, this does nothing + * when the table should be normalized. + * + * @param aTableOrElementInTable An element which is in a <table> element + * or <table> element itself. Otherwise, + * this returns NS_OK but does nothing. + */ + MOZ_CAN_RUN_SCRIPT nsresult + NormalizeTableInternal(Element& aTableOrElementInTable); + + /** + * Fallback method: Call this after using ClearSelection() and you + * failed to set selection to some other content in the document. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetSelectionAtDocumentStart(); + + // Methods for handling plaintext quotations + MOZ_CAN_RUN_SCRIPT nsresult PasteAsPlaintextQuotation(int32_t aSelectionType); + + /** + * Insert a string as quoted text, replacing the selected text (if any). + * @param aQuotedText The string to insert. + * @param aAddCites Whether to prepend extra ">" to each line + * (usually true, unless those characters + * have already been added.) + * @return aNodeInserted The node spanning the insertion, if applicable. + * If aAddCites is false, this will be null. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InsertAsPlaintextQuotation( + const nsAString& aQuotedText, bool aAddCites, nsINode** aNodeInserted); + + /** + * InsertObject() inserts given object at aPointToInsert. + * + * @param aType one of kFileMime, kJPEGImageMime, kJPGImageMime, + * kPNGImageMime, kGIFImageMime. + */ + MOZ_CAN_RUN_SCRIPT nsresult InsertObject(const nsACString& aType, + nsISupports* aObject, bool aIsSafe, + Document* aSourceDoc, + const EditorDOMPoint& aPointToInsert, + bool aDoDeleteSelection); + + // factored methods for handling insertion of data from transferables + // (drag&drop or clipboard) + virtual nsresult PrepareTransferable( + nsITransferable** aTransferable) override; + + class HTMLTransferablePreparer; + nsresult PrepareHTMLTransferable(nsITransferable** aTransferable) const; + + MOZ_CAN_RUN_SCRIPT nsresult InsertFromTransferable( + nsITransferable* aTransferable, Document* aSourceDoc, + const nsAString& aContextStr, const nsAString& aInfoStr, + bool aHavePrivateHTMLFlavor, bool aDoDeleteSelection); + + /** + * InsertFromDataTransfer() is called only when user drops data into + * this editor. Don't use this method for other purposes. + * + * @param aIndex index of aDataTransfer's item to insert. + */ + MOZ_CAN_RUN_SCRIPT nsresult + InsertFromDataTransfer(const dom::DataTransfer* aDataTransfer, int32_t aIndex, + Document* aSourceDoc, const EditorDOMPoint& aDroppedAt, + bool aDoDeleteSelection); + + static bool HavePrivateHTMLFlavor(nsIClipboard* clipboard); + + /** + * CF_HTML: + * <https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format>. + * + * @param[in] aCfhtml a CF_HTML string as defined above. + * @param[out] aStuffToPaste the fragment, excluding context. + * @param[out] aCfcontext the context, excluding the fragment, including a + * marker (`kInsertionCookie`) indicating where the + * fragment begins. + */ + nsresult ParseCFHTML(const nsCString& aCfhtml, char16_t** aStuffToPaste, + char16_t** aCfcontext); + + /** + * AutoHTMLFragmentBoundariesFixer fixes both edges of topmost child contents + * which are created with SubtreeContentIterator. + */ + class MOZ_STACK_CLASS AutoHTMLFragmentBoundariesFixer final { + public: + /** + * @param aArrayOfTopMostChildContents + * [in/out] The topmost child contents which will be + * inserted into the DOM tree. Both edges, i.e., + * first node and last node in this array will be + * checked whether they can be inserted into + * another DOM tree. If not, it'll replaces some + * orphan nodes around nodes with proper parent. + */ + explicit AutoHTMLFragmentBoundariesFixer( + nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents); + + private: + /** + * EnsureBeginsOrEndsWithValidContent() replaces some nodes starting from + * start or end with proper element node if it's necessary. + * If first or last node of aArrayOfTopMostChildContents is in list and/or + * `<table>` element, looks for topmost list element or `<table>` element + * with `CollectTableAndAnyListElementsOfInclusiveAncestorsAt()` and + * `GetMostDistantAncestorListOrTableElement()`. Then, checks + * whether some nodes are in aArrayOfTopMostChildContents are the topmost + * list/table element or its descendant and if so, removes the nodes from + * aArrayOfTopMostChildContents and inserts the list/table element instead. + * Then, aArrayOfTopMostChildContents won't start/end with list-item nor + * table cells. + */ + enum class StartOrEnd { start, end }; + void EnsureBeginsOrEndsWithValidContent( + StartOrEnd aStartOrEnd, + nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents) + const; + + /** + * CollectTableAndAnyListElementsOfInclusiveAncestorsAt() collects list + * elements and table related elements from the inclusive ancestors + * (https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor) of aNode. + */ + static void CollectTableAndAnyListElementsOfInclusiveAncestorsAt( + nsIContent& aContent, + nsTArray<OwningNonNull<Element>>& aOutArrayOfListAndTableElements); + + /** + * GetMostDistantAncestorListOrTableElement() returns a list or a + * `<table>` element which is in + * aInclusiveAncestorsTableOrListElements and they are actually + * valid ancestor of at least one of aArrayOfTopMostChildContents. + */ + static Element* GetMostDistantAncestorListOrTableElement( + const nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents, + const nsTArray<OwningNonNull<Element>>& + aInclusiveAncestorsTableOrListElements); + + /** + * FindReplaceableTableElement() is a helper method of + * EnsureBeginsOrEndsWithValidContent(). If aNodeMaybeInTableElement is + * a descendant of aTableElement, returns aNodeMaybeInTableElement or its + * nearest ancestor whose tag name is `<td>`, `<th>`, `<tr>`, `<thead>`, + * `<tfoot>`, `<tbody>` or `<caption>`. + * + * @param aTableElement Must be a `<table>` element. + * @param aContentMaybeInTableElement A node which may be in aTableElement. + */ + Element* FindReplaceableTableElement( + Element& aTableElement, nsIContent& aContentMaybeInTableElement) const; + + /** + * IsReplaceableListElement() is a helper method of + * EnsureBeginsOrEndsWithValidContent(). If aNodeMaybeInListElement is a + * descendant of aListElement, returns true. Otherwise, false. + * + * @param aListElement Must be a list element. + * @param aContentMaybeInListElement A node which may be in aListElement. + */ + bool IsReplaceableListElement(Element& aListElement, + nsIContent& aContentMaybeInListElement) const; + }; + + /** + * GetBetterInsertionPointFor() returns better insertion point to insert + * aContentToInsert. + * + * @param aContentToInsert The content to insert. + * @param aPointToInsert A candidate point to insert the node. + * @return Better insertion point if next visible node + * is a <br> element and previous visible node + * is neither none, another <br> element nor + * different block level element. + */ + EditorRawDOMPoint GetBetterInsertionPointFor( + nsIContent& aContentToInsert, + const EditorRawDOMPoint& aPointToInsert) const; + + /** + * MakeDefinitionListItemWithTransaction() replaces parent list of current + * selection with <dl> or create new <dl> element and creates a definition + * list item whose name is aTagName. + * + * @param aTagName Must be nsGkAtoms::dt or nsGkAtoms::dd. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + MakeDefinitionListItemWithTransaction(nsAtom& aTagName); + + /** + * FormatBlockContainerAsSubAction() inserts a block element whose name + * is aTagName at selection. If selection is not collapsed and aTagName is + * nsGkAtoms::normal or nsGkAtoms::_empty, this removes block containers. + * + * @param aTagName A block level element name. Must NOT be + * nsGkAtoms::dt nor nsGkAtoms::dd. + */ + MOZ_CAN_RUN_SCRIPT nsresult FormatBlockContainerAsSubAction(nsAtom& aTagName); + + /** + * Increase/decrease the font size of selection. + */ + MOZ_CAN_RUN_SCRIPT nsresult RelativeFontChange(FontSize aDir); + + MOZ_CAN_RUN_SCRIPT nsresult RelativeFontChangeOnNode(int32_t aSizeChange, + nsIContent* aNode); + MOZ_CAN_RUN_SCRIPT nsresult RelativeFontChangeHelper(int32_t aSizeChange, + nsINode* aNode); + + /** + * Helper routines for inline style. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetInlinePropertyOnTextNode( + Text& aData, uint32_t aStartOffset, uint32_t aEndOffset, + nsAtom& aProperty, nsAtom* aAttribute, const nsAString& aValue); + + nsresult PromoteInlineRange(nsRange& aRange); + nsresult PromoteRangeIfStartsOrEndsInNamedAnchor(nsRange& aRange); + + /** + * RemoveStyleInside() removes elements which represent aProperty/aAttribute + * and removes CSS style. This handles aElement and all its descendants + * (including leaf text nodes) recursively. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + RemoveStyleInside(Element& aElement, nsAtom* aProperty, nsAtom* aAttribute); + + /** + * CollectEditableLeafTextNodes() collects text nodes in aElement. + */ + void CollectEditableLeafTextNodes( + Element& aElement, nsTArray<OwningNonNull<Text>>& aLeafTextNodes) const; + + /** + * IsRemovableParentStyleWithNewSpanElement() checks whether + * aProperty/aAttribute of parent block can be removed from aContent with + * creating `<span>` element. Note that this does NOT check whether the + * specified style comes from parent block or not. + * XXX This may destroy the editor, but using `Result<bool, nsresult>` + * is not reasonable because code for accessing the result becomes + * messy. However, anybody must forget to check `Destroyed()` after + * calling this. Which is the way to smart to make every caller + * must check the editor state? + */ + MOZ_CAN_RUN_SCRIPT bool IsRemovableParentStyleWithNewSpanElement( + nsIContent& aContent, nsAtom* aHTMLProperty, nsAtom* aAttribute) const; + + /** + * XXX These methods seem odd and except the only caller, + * `PromoteInlineRange()`, cannot use them. + */ + bool IsStartOfContainerOrBeforeFirstEditableChild( + const EditorRawDOMPoint& aPoint) const; + bool IsEndOfContainerOrEqualsOrAfterLastEditableChild( + const EditorRawDOMPoint& aPoint) const; + + bool IsOnlyAttribute(const Element* aElement, nsAtom* aAttribute); + + /** + * HasStyleOrIdOrClassAttribute() returns true when at least one of + * `style`, `id` or `class` attribute value of aElement is not empty. + */ + static bool HasStyleOrIdOrClassAttribute(Element& aElement); + + /** + * Whether the outer window of the DOM event target has focus or not. + */ + bool OurWindowHasFocus() const; + + class HTMLWithContextInserter; + + /** + * This function is used to insert a string of HTML input optionally with some + * context information into the editable field. The HTML input either comes + * from a transferable object created as part of a drop/paste operation, or + * from the InsertHTML method. We may want the HTML input to be sanitized + * (for example, if it's coming from a transferable object), in which case + * aTrustedInput should be set to false, otherwise, the caller should set it + * to true, which means that the HTML will be inserted in the DOM verbatim. + * + * aClearStyle should be set to false if you want the paste to be affected by + * local style (e.g., for the insertHTML command). + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult DoInsertHTMLWithContext( + const nsAString& aInputString, const nsAString& aContextStr, + const nsAString& aInfoStr, const nsAString& aFlavor, Document* aSourceDoc, + const EditorDOMPoint& aPointToInsert, bool aDeleteSelection, + bool aTrustedInput, bool aClearStyle = true); + + /** + * sets the position of an element; warning it does NOT check if the + * element is already positioned or not and that's on purpose. + * @param aStyledElement [IN] the element + * @param aX [IN] the x position in pixels. + * @param aY [IN] the y position in pixels. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetTopAndLeftWithTransaction( + nsStyledElement& aStyledElement, int32_t aX, int32_t aY); + + /** + * Reset a selected cell or collapsed selection (the caret) after table + * editing. + * + * @param aTable A table in the document. + * @param aRow The row ... + * @param aCol ... and column defining the cell where we will try to + * place the caret. + * @param aSelected If true, we select the whole cell instead of setting + * caret. + * @param aDirection If cell at (aCol, aRow) is not found, search for + * previous cell in the same column (aPreviousColumn) or + * row (ePreviousRow) or don't search for another cell + * (aNoSearch). If no cell is found, caret is place just + * before table; and if that fails, at beginning of + * document. Thus we generally don't worry about the + * return value and can use the + * AutoSelectionSetterAfterTableEdit stack-based object to + * insure we reset the caret in a table-editing method. + */ + MOZ_CAN_RUN_SCRIPT void SetSelectionAfterTableEdit(Element* aTable, + int32_t aRow, int32_t aCol, + int32_t aDirection, + bool aSelected); + + void RemoveListenerAndDeleteRef(const nsAString& aEvent, + nsIDOMEventListener* aListener, + bool aUseCapture, ManualNACPtr aElement, + PresShell* aPresShell); + void DeleteRefToAnonymousNode(ManualNACPtr aContent, PresShell* aPresShell); + + /** + * RefreshEditingUI() may refresh editing UIs for current Selection, focus, + * etc. If this shows or hides some UIs, it causes reflow. So, this is + * not safe method. + */ + MOZ_CAN_RUN_SCRIPT nsresult RefreshEditingUI(); + + /** + * Returns the offset of an element's frame to its absolute containing block. + */ + nsresult GetElementOrigin(Element& aElement, int32_t& aX, int32_t& aY); + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult GetPositionAndDimensions( + Element& aElement, int32_t& aX, int32_t& aY, int32_t& aW, int32_t& aH, + int32_t& aBorderLeft, int32_t& aBorderTop, int32_t& aMarginLeft, + int32_t& aMarginTop); + + bool IsInObservedSubtree(nsIContent* aChild); + + void UpdateRootElement(); + + /** + * SetAllResizersPosition() moves all resizers to proper position. + * If the resizers are hidden or replaced with another set of resizers + * while this is running, this returns error. So, callers shouldn't + * keep handling the resizers if this returns error. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetAllResizersPosition(); + + /** + * Shows active resizers around an element's frame + * @param aResizedElement [IN] a DOM Element + */ + MOZ_CAN_RUN_SCRIPT nsresult ShowResizersInternal(Element& aResizedElement); + + /** + * Hide resizers if they are visible. If this is called while there is no + * visible resizers, this does not return error, but does nothing. + */ + nsresult HideResizersInternal(); + + /** + * RefreshResizersInternal() moves resizers to proper position. This does + * nothing if there is no resizing target. + */ + MOZ_CAN_RUN_SCRIPT nsresult RefreshResizersInternal(); + + ManualNACPtr CreateResizer(int16_t aLocation, nsIContent& aParentContent); + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + SetAnonymousElementPositionWithoutTransaction(nsStyledElement& aStyledElement, + int32_t aX, int32_t aY); + + ManualNACPtr CreateShadow(nsIContent& aParentContent, + Element& aOriginalObject); + + /** + * SetShadowPosition() moves the shadow element to proper position. + * + * @param aShadowElement Must be mResizingShadow or mPositioningShadow. + * @param aElement The element which has the shadow. + * @param aElementX Left of aElement. + * @param aElementY Top of aElement. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + SetShadowPosition(Element& aShadowElement, Element& aElement, + int32_t aElementLeft, int32_t aElementTop); + + ManualNACPtr CreateResizingInfo(nsIContent& aParentContent); + MOZ_CAN_RUN_SCRIPT nsresult SetResizingInfoPosition(int32_t aX, int32_t aY, + int32_t aW, int32_t aH); + + enum class ResizeAt { + eX, + eY, + eWidth, + eHeight, + }; + int32_t GetNewResizingIncrement(int32_t aX, int32_t aY, ResizeAt aResizeAt); + + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult StartResizing(Element& aHandle); + int32_t GetNewResizingX(int32_t aX, int32_t aY); + int32_t GetNewResizingY(int32_t aX, int32_t aY); + int32_t GetNewResizingWidth(int32_t aX, int32_t aY); + int32_t GetNewResizingHeight(int32_t aX, int32_t aY); + void HideShadowAndInfo(); + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + SetFinalSizeWithTransaction(int32_t aX, int32_t aY); + void SetResizeIncrements(int32_t aX, int32_t aY, int32_t aW, int32_t aH, + bool aPreserveRatio); + + /** + * HideAnonymousEditingUIs() forcibly hides all editing UIs (resizers, + * inline-table-editing UI, absolute positioning UI). + */ + void HideAnonymousEditingUIs(); + + /** + * HideAnonymousEditingUIsIfUnnecessary() hides all editing UIs if some of + * visible UIs are now unnecessary. + */ + void HideAnonymousEditingUIsIfUnnecessary(); + + /** + * sets the z-index of an element. + * @param aElement [IN] the element + * @param aZorder [IN] the z-index + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + SetZIndexWithTransaction(nsStyledElement& aElement, int32_t aZIndex); + + /** + * shows a grabber attached to an arbitrary element. The grabber is an image + * positioned on the left hand side of the top border of the element. Draggin + * and dropping it allows to change the element's absolute position in the + * document. See chrome://editor/content/images/grabber.gif + * @param aElement [IN] the element + */ + MOZ_CAN_RUN_SCRIPT nsresult ShowGrabberInternal(Element& aElement); + + /** + * Setting grabber to proper position for current mAbsolutelyPositionedObject. + * For example, while an element has grabber, the element may be resized + * or repositioned by script or something. Then, you need to reset grabber + * position with this. + */ + MOZ_CAN_RUN_SCRIPT nsresult RefreshGrabberInternal(); + + /** + * hide the grabber if it shown. + */ + void HideGrabberInternal(); + + /** + * CreateGrabberInternal() creates a grabber for moving aParentContent. + * This sets mGrabber to the new grabber. If this returns true, it's + * always non-nullptr. Otherwise, i.e., the grabber is hidden during + * creation, this returns false. + */ + bool CreateGrabberInternal(nsIContent& aParentContent); + + MOZ_CAN_RUN_SCRIPT nsresult StartMoving(); + MOZ_CAN_RUN_SCRIPT nsresult SetFinalPosition(int32_t aX, int32_t aY); + void AddPositioningOffset(int32_t& aX, int32_t& aY); + void SnapToGrid(int32_t& newX, int32_t& newY); + nsresult GrabberClicked(); + MOZ_CAN_RUN_SCRIPT nsresult EndMoving(); + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + GetTemporaryStyleForFocusedPositionedElement(Element& aElement, + nsAString& aReturn); + + /** + * Shows inline table editing UI around a <table> element which contains + * aCellElement. This returns error if creating UI is hidden during this, + * or detects another set of UI during this. In such case, callers + * shouldn't keep handling anything for the UI. + * + * @param aCellElement Must be an <td> or <th> element. + */ + MOZ_CAN_RUN_SCRIPT nsresult + ShowInlineTableEditingUIInternal(Element& aCellElement); + + /** + * Hide all inline table editing UI. + */ + void HideInlineTableEditingUIInternal(); + + /** + * RefreshInlineTableEditingUIInternal() moves inline table editing UI to + * proper position. This returns error if the UI is hidden or replaced + * during moving. + */ + MOZ_CAN_RUN_SCRIPT nsresult RefreshInlineTableEditingUIInternal(); + + /** + * IsEmptyTextNode() returns true if aNode is a text node and does not have + * any visible characters. + */ + bool IsEmptyTextNode(nsINode& aNode) const { + return aNode.IsText() && IsEmptyNode(aNode); + } + + /** + * ElementIsGoodContainerForTheStyle() returns true if aElement is a + * good container for applying the style (aProperty/aAttribute/aValue) + * to a node. I.e., if this returns true, moving nodes into aElement + * is enough to apply the style to them. Otherwise, you need to create + * new element for the style. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<bool, nsresult> + ElementIsGoodContainerForTheStyle(Element& aElement, nsAtom* aProperty, + nsAtom* aAttribute, + const nsAString* aValue); + + MOZ_CAN_RUN_SCRIPT nsresult + SetInlinePropertyOnNodeImpl(nsIContent& aNode, nsAtom& aProperty, + nsAtom* aAttribute, const nsAString& aValue); + typedef enum { eInserted, eAppended } InsertedOrAppended; + MOZ_CAN_RUN_SCRIPT void DoContentInserted( + nsIContent* aChild, InsertedOrAppended aInsertedOrAppended); + + /** + * Returns an anonymous Element of type aTag, + * child of aParentContent. If aIsCreatedHidden is true, the class + * "hidden" is added to the created element. If aAnonClass is not + * the empty string, it becomes the value of the attribute "_moz_anonclass" + * @return a Element + * @param aTag [IN] desired type of the element to create + * @param aParentContent [IN] the parent node of the created anonymous + * element + * @param aAnonClass [IN] contents of the _moz_anonclass attribute + * @param aIsCreatedHidden [IN] a boolean specifying if the class "hidden" + * is to be added to the created anonymous + * element + */ + ManualNACPtr CreateAnonymousElement(nsAtom* aTag, nsIContent& aParentContent, + const nsAString& aAnonClass, + bool aIsCreatedHidden); + + /** + * Reads a blob into memory and notifies the BlobReader object when the read + * operation is finished. + * + * @param aBlob The input blob + * @param aWindow The global object under which the read should happen. + * @param aBlobReader The blob reader object to be notified when finished. + */ + static nsresult SlurpBlob(dom::Blob* aBlob, nsPIDOMWindowOuter* aWindow, + BlobReader* aBlobReader); + + /** + * OnModifyDocumentInternal() is called by OnModifyDocument(). + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult OnModifyDocumentInternal(); + + /** + * For saving allocation cost in the constructor of + * EditorBase::TopLevelEditSubActionData, we should reuse same RangeItem + * instance with all top level edit sub actions. + * The instance is always cleared when TopLevelEditSubActionData is + * destructed and the class is stack only class so that we don't need + * to (and also should not) add the RangeItem into the cycle collection. + */ + already_AddRefed<RangeItem> GetSelectedRangeItemForTopLevelEditSubAction() + const { + if (!mSelectedRangeForTopLevelEditSubAction) { + mSelectedRangeForTopLevelEditSubAction = new RangeItem(); + } + return do_AddRef(mSelectedRangeForTopLevelEditSubAction); + } + + /** + * For saving allocation cost in the constructor of + * EditorBase::TopLevelEditSubActionData, we should reuse same nsRange + * instance with all top level edit sub actions. + * The instance is always cleared when TopLevelEditSubActionData is + * destructed, but AbstractRange::mOwner keeps grabbing the owner document + * so that we need to make it in the cycle collection. + */ + already_AddRefed<nsRange> GetChangedRangeForTopLevelEditSubAction() const { + if (!mChangedRangeForTopLevelEditSubAction) { + mChangedRangeForTopLevelEditSubAction = nsRange::Create(GetDocument()); + } + return do_AddRef(mChangedRangeForTopLevelEditSubAction); + } + + protected: + // Helper for Handle[CSS|HTML]IndentAtSelectionInternal + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + IndentListChild(RefPtr<Element>* aCurList, const EditorDOMPoint& aCurPoint, + nsIContent& aContent); + + RefPtr<TypeInState> mTypeInState; + RefPtr<ComposerCommandsUpdater> mComposerCommandsUpdater; + + // Used by TopLevelEditSubActionData::mSelectedRange. + mutable RefPtr<RangeItem> mSelectedRangeForTopLevelEditSubAction; + // Used by TopLevelEditSubActionData::mChangedRange. + mutable RefPtr<nsRange> mChangedRangeForTopLevelEditSubAction; + + RefPtr<Runnable> mPendingRootElementUpdatedRunner; + RefPtr<Runnable> mPendingDocumentModifiedRunner; + + bool mCRInParagraphCreatesParagraph; + + bool mCSSAware; + UniquePtr<CSSEditUtils> mCSSEditUtils; + + // resizing + bool mIsObjectResizingEnabled; + bool mIsResizing; + bool mPreserveRatio; + bool mResizedObjectIsAnImage; + + // absolute positioning + bool mIsAbsolutelyPositioningEnabled; + bool mResizedObjectIsAbsolutelyPositioned; + bool mGrabberClicked; + bool mIsMoving; + + bool mSnapToGridEnabled; + + // inline table editing + bool mIsInlineTableEditingEnabled; + + // resizing + ManualNACPtr mTopLeftHandle; + ManualNACPtr mTopHandle; + ManualNACPtr mTopRightHandle; + ManualNACPtr mLeftHandle; + ManualNACPtr mRightHandle; + ManualNACPtr mBottomLeftHandle; + ManualNACPtr mBottomHandle; + ManualNACPtr mBottomRightHandle; + + RefPtr<Element> mActivatedHandle; + + ManualNACPtr mResizingShadow; + ManualNACPtr mResizingInfo; + + RefPtr<Element> mResizedObject; + + int32_t mOriginalX; + int32_t mOriginalY; + + int32_t mResizedObjectX; + int32_t mResizedObjectY; + int32_t mResizedObjectWidth; + int32_t mResizedObjectHeight; + + int32_t mResizedObjectMarginLeft; + int32_t mResizedObjectMarginTop; + int32_t mResizedObjectBorderLeft; + int32_t mResizedObjectBorderTop; + + int32_t mXIncrementFactor; + int32_t mYIncrementFactor; + int32_t mWidthIncrementFactor; + int32_t mHeightIncrementFactor; + + int8_t mInfoXIncrement; + int8_t mInfoYIncrement; + + // absolute positioning + int32_t mPositionedObjectX; + int32_t mPositionedObjectY; + int32_t mPositionedObjectWidth; + int32_t mPositionedObjectHeight; + + int32_t mPositionedObjectMarginLeft; + int32_t mPositionedObjectMarginTop; + int32_t mPositionedObjectBorderLeft; + int32_t mPositionedObjectBorderTop; + + RefPtr<Element> mAbsolutelyPositionedObject; + ManualNACPtr mGrabber; + ManualNACPtr mPositioningShadow; + + int32_t mGridSize; + + // inline table editing + RefPtr<Element> mInlineEditedCell; + + ManualNACPtr mAddColumnBeforeButton; + ManualNACPtr mRemoveColumnButton; + ManualNACPtr mAddColumnAfterButton; + + ManualNACPtr mAddRowBeforeButton; + ManualNACPtr mRemoveRowButton; + ManualNACPtr mAddRowAfterButton; + + void AddMouseClickListener(Element* aElement); + void RemoveMouseClickListener(Element* aElement); + + bool mDisabledLinkHandling = false; + bool mOldLinkHandlingEnabled = false; + + bool mHasBeforeInputBeenCanceled = false; + + ParagraphSeparator mDefaultParagraphSeparator; + + friend class AlignStateAtSelection; + friend class AutoSelectionSetterAfterTableEdit; + friend class AutoSetTemporaryAncestorLimiter; + friend class CSSEditUtils; + friend class EditorBase; + friend class EmptyEditableFunctor; + friend class JoinNodeTransaction; + friend class ListElementSelectionState; + friend class ListItemElementSelectionState; + friend class ParagraphStateAtSelection; + friend class SlurpBlobEventListener; + friend class SplitNodeTransaction; + friend class TextEditor; + friend class WhiteSpaceVisibilityKeeper; + friend class WSRunScanner; + friend class WSScanResult; +}; + +/** + * ListElementSelectionState class gets which list element is selected right + * now. + */ +class MOZ_STACK_CLASS ListElementSelectionState final { + public: + ListElementSelectionState() = delete; + ListElementSelectionState(HTMLEditor& aHTMLEditor, ErrorResult& aRv); + + bool IsOLElementSelected() const { return mIsOLElementSelected; } + bool IsULElementSelected() const { return mIsULElementSelected; } + bool IsDLElementSelected() const { return mIsDLElementSelected; } + bool IsNotOneTypeListElementSelected() const { + return (mIsOLElementSelected + mIsULElementSelected + mIsDLElementSelected + + mIsOtherContentSelected) > 1; + } + + private: + bool mIsOLElementSelected = false; + bool mIsULElementSelected = false; + bool mIsDLElementSelected = false; + bool mIsOtherContentSelected = false; +}; + +/** + * ListItemElementSelectionState class gets which list item element is selected + * right now. + */ +class MOZ_STACK_CLASS ListItemElementSelectionState final { + public: + ListItemElementSelectionState() = delete; + ListItemElementSelectionState(HTMLEditor& aHTMLEditor, ErrorResult& aRv); + + bool IsLIElementSelected() const { return mIsLIElementSelected; } + bool IsDTElementSelected() const { return mIsDTElementSelected; } + bool IsDDElementSelected() const { return mIsDDElementSelected; } + bool IsNotOneTypeDefinitionListItemElementSelected() const { + return (mIsDTElementSelected + mIsDDElementSelected + + mIsOtherElementSelected) > 1; + } + + private: + bool mIsLIElementSelected = false; + bool mIsDTElementSelected = false; + bool mIsDDElementSelected = false; + bool mIsOtherElementSelected = false; +}; + +/** + * AlignStateAtSelection class gets alignment at selection. + * XXX This currently returns only first alignment. + */ +class MOZ_STACK_CLASS AlignStateAtSelection final { + public: + AlignStateAtSelection() = delete; + MOZ_CAN_RUN_SCRIPT AlignStateAtSelection(HTMLEditor& aHTMLEditor, + ErrorResult& aRv); + + nsIHTMLEditor::EAlignment AlignmentAtSelectionStart() const { + return mFirstAlign; + } + bool IsSelectionRangesFound() const { return mFoundSelectionRanges; } + + private: + nsIHTMLEditor::EAlignment mFirstAlign = nsIHTMLEditor::eLeft; + bool mFoundSelectionRanges = false; +}; + +/** + * ParagraphStateAtSelection class gets format block types around selection. + */ +class MOZ_STACK_CLASS ParagraphStateAtSelection final { + public: + ParagraphStateAtSelection() = delete; + ParagraphStateAtSelection(HTMLEditor& aHTMLEditor, ErrorResult& aRv); + + /** + * GetFirstParagraphStateAtSelection() returns: + * - nullptr if there is no format blocks nor inline nodes. + * - nsGkAtoms::_empty if first node is not in any format block. + * - a tag name of format block at first node. + * XXX See the private method explanations. If selection ranges contains + * non-format block first, it'll be check after its siblings. Therefore, + * this may return non-first paragraph state. + */ + nsAtom* GetFirstParagraphStateAtSelection() const { + return mFirstParagraphState; + } + + /** + * If selected nodes are not in same format node nor only in no-format blocks, + * this returns true. + */ + bool IsMixed() const { return mIsMixed; } + + private: + using EditorType = EditorBase::EditorType; + + /** + * AppendDescendantFormatNodesAndFirstInlineNode() appends descendant + * format blocks and first inline child node in aNonFormatBlockElement to + * the last of the array (not inserting where aNonFormatBlockElement is, + * so that the node order becomes randomly). + * + * @param aArrayOfContents [in/out] Found descendant format blocks + * and first inline node in each non-format + * block will be appended to this. + * @param aNonFormatBlockElement Must be a non-format block element. + */ + static void AppendDescendantFormatNodesAndFirstInlineNode( + nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, + dom::Element& aNonFormatBlockElement); + + /** + * CollectEditableFormatNodesInSelection() collects only editable nodes + * around selection ranges (with + * `HTMLEditor::CollectEditTargetNodesInExtendedSelectionRanges()`, see its + * document for the detail). If it includes list, list item or table + * related elements, they will be replaced their children. + */ + static nsresult CollectEditableFormatNodesInSelection( + HTMLEditor& aHTMLEditor, + nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents); + + RefPtr<nsAtom> mFirstParagraphState; + bool mIsMixed = false; +}; + +} // namespace mozilla + +mozilla::HTMLEditor* nsIEditor::AsHTMLEditor() { + return static_cast<mozilla::EditorBase*>(this)->IsHTMLEditor() + ? static_cast<mozilla::HTMLEditor*>(this) + : nullptr; +} + +const mozilla::HTMLEditor* nsIEditor::AsHTMLEditor() const { + return static_cast<const mozilla::EditorBase*>(this)->IsHTMLEditor() + ? static_cast<const mozilla::HTMLEditor*>(this) + : nullptr; +} + +#endif // #ifndef mozilla_HTMLEditor_h |