diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /editor/libeditor/HTMLEditor.h | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'editor/libeditor/HTMLEditor.h')
-rw-r--r-- | editor/libeditor/HTMLEditor.h | 4696 |
1 files changed, 4696 insertions, 0 deletions
diff --git a/editor/libeditor/HTMLEditor.h b/editor/libeditor/HTMLEditor.h new file mode 100644 index 0000000000..fabdf1d9e4 --- /dev/null +++ b/editor/libeditor/HTMLEditor.h @@ -0,0 +1,4696 @@ +/* -*- 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/EditorBase.h" +#include "mozilla/EditorForwards.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/ManualNAC.h" +#include "mozilla/Result.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 <functional> + +class nsDocumentFragment; +class nsFrameSelection; +class nsHTMLDocument; +class nsITransferable; +class nsIClipboard; +class nsRange; +class nsStaticAtom; +class nsStyledElement; +class nsTableCellFrame; +class nsTableWrapperFrame; +template <class E> +class nsTArray; + +namespace mozilla { +class AlignStateAtSelection; +class AutoSelectionSetterAfterTableEdit; +class AutoSetTemporaryAncestorLimiter; +class EmptyEditableFunctor; +class ListElementSelectionState; +class ListItemElementSelectionState; +class ParagraphStateAtSelection; +class ResizerSelectionListener; +class Runnable; +template <class T> +class OwningNonNull; +namespace dom { +class AbstractRange; +class Blob; +class DocumentFragment; +class Event; +class HTMLBRElement; +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 EditorBase, + 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, EditorBase) + + // nsStubMutationObserver overrides + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED + + // 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 + + /** + * @param aDocument The document whose content will be editable. + */ + explicit HTMLEditor(const Document& aDocument); + + /** + * @param aDocument The document whose content will be editable. + * @param aComposerCommandsUpdater The composer command updater. + * @param aFlags Some of nsIEditor::eEditor*Mask flags. + */ + MOZ_CAN_RUN_SCRIPT nsresult + Init(Document& aDocument, ComposerCommandsUpdater& aComposerCommandsUpdater, + uint32_t aFlags); + + /** + * PostCreate() should be called after Init, and is the time that the editor + * tells its documentStateObservers that the document has been created. + */ + MOZ_CAN_RUN_SCRIPT nsresult PostCreate(); + + /** + * PreDestroy() is called before the editor goes away, and gives the editor a + * chance to tell its documentStateObservers that the document is going away. + */ + MOZ_CAN_RUN_SCRIPT void PreDestroy(); + + static HTMLEditor* GetFrom(nsIEditor* aEditor) { + return aEditor ? aEditor->GetAsHTMLEditor() : nullptr; + } + static const HTMLEditor* GetFrom(const nsIEditor* aEditor) { + return aEditor ? aEditor->GetAsHTMLEditor() : nullptr; + } + + [[nodiscard]] bool GetReturnInParagraphCreatesNewParagraph() const; + + // EditorBase overrides + MOZ_CAN_RUN_SCRIPT NS_IMETHOD BeginningOfDocument() final; + MOZ_CAN_RUN_SCRIPT NS_IMETHOD EndOfDocument() final; + + NS_IMETHOD GetDocumentCharacterSet(nsACString& aCharacterSet) final; + MOZ_CAN_RUN_SCRIPT NS_IMETHOD + SetDocumentCharacterSet(const nsACString& aCharacterSet) final; + + bool IsEmpty() const final; + + bool CanPaste(int32_t aClipboardType) const final; + using EditorBase::CanPaste; + + MOZ_CAN_RUN_SCRIPT NS_IMETHOD DeleteNode(nsINode* aNode) final; + + MOZ_CAN_RUN_SCRIPT NS_IMETHOD InsertLineBreak() final; + + /** + * 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 nsresult + HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) final; + Element* GetFocusedElement() const final; + bool IsActiveInDOMWindow() const final; + dom::EventTarget* GetDOMEventTarget() const final; + [[nodiscard]] Element* FindSelectionRoot(const nsINode& aNode) const final; + bool IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) const final; + nsresult GetPreferredIMEState(widget::IMEState* aState) final; + MOZ_CAN_RUN_SCRIPT nsresult + OnFocus(const nsINode& aOriginalEventTargetNode) final; + nsresult OnBlur(const dom::EventTarget* aEventTarget) final; + + /** + * Called when aDocument or aElement becomes editable without focus change. + * E.g., when the design mode is enabled or the contenteditable attribute + * is set to the focused element. + */ + MOZ_CAN_RUN_SCRIPT nsresult FocusedElementOrDocumentBecomesEditable( + Document& aDocument, Element* aElement); + + /** + * Called when aDocument or aElement becomes not editable without focus + * change. E.g., when the design mode ends or the contenteditable attribute is + * removed or set to "false". + */ + MOZ_CAN_RUN_SCRIPT static nsresult FocusedElementOrDocumentBecomesNotEditable( + HTMLEditor* aHTMLEditor, Document& aDocument, Element* aElement); + + /** + * 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); + + /** + * PasteNoFormattingAsAction() pastes content in clipboard without any style + * information. + * + * @param aClipboardType nsIClipboard::kGlobalClipboard or + * nsIClipboard::kSelectionClipboard. + * @param aDispatchPasteEvent Yes if this should dispatch ePaste event + * before pasting. Otherwise, No. + * @param aPrincipal Set subject principal if it may be called by + * JS. If set to nullptr, will be treated as + * called by system. + */ + MOZ_CAN_RUN_SCRIPT nsresult PasteNoFormattingAsAction( + int32_t aClipboardType, DispatchPasteEvent aDispatchPasteEvent, + nsIPrincipal* aPrincipal = nullptr); + + bool CanPasteTransferable(nsITransferable* aTransferable) final; + + MOZ_CAN_RUN_SCRIPT nsresult + InsertLineBreakAsAction(nsIPrincipal* aPrincipal = nullptr) final; + + /** + * 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( + const nsStaticAtom& 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 { return mIsCSSPrefChecked; } + + /** + * 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; + } + + /** + * Enable/disable Gecko's traditional join/split node direction, that is, + * creating left node at splitting a node and removing left node at joining 2 + * nodes. This is acceptable only before first join/split transaction is + * created. + */ + bool EnableCompatibleJoinSplitNodeDirection(bool aEnable) { + if (!CanChangeJoinSplitNodeDirection()) { + return false; + } + mUseGeckoTraditionalJoinSplitBehavior = !aEnable; + return true; + } + + /** + * Return true if the instance works with the legacy join/split node + * direction. + */ + [[nodiscard]] bool IsCompatibleJoinSplitNodeDirectionEnabled() const { + return !mUseGeckoTraditionalJoinSplitBehavior; + } + + /** + * Return true if web apps can still change the join split node direction. + * For saving the footprint, each transaction does not store join/split node + * direction at first run. Therefore, join/split node transactions need to + * refer the direction of corresponding HTMLEditor. So if the direction were + * changed after creating join/split transactions, they would break the DOM + * tree with undoing/redoing within wrong direction. Therefore, once this + * instance created a join or split node transaction, this returns false to + * block to change the direction. + */ + [[nodiscard]] bool CanChangeJoinSplitNodeDirection() const { + return !mMaybeHasJoinSplitTransactions; + } + + /** + * 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( + nsStaticAtom& aProperty, nsStaticAtom* aAttribute, + const nsAString& aValue, nsIPrincipal* aPrincipal = nullptr); + + /** + * GetInlineProperty() gets aggregate properties of the current selection. + * All object in the current selection are scanned and their attributes are + * represented in a list of Property object. + * TODO: Make this return Result<Something> instead of bool out arguments. + * + * @param aHTMLProperty the property to get on the selection + * @param aAttribute the attribute of the property, if applicable. + * May be null. + * Example: aHTMLProperty=nsGkAtoms::font, + * aAttribute=nsGkAtoms::color + * @param aValue if aAttribute is not null, the value of the + * attribute. May be null. + * Example: aHTMLProperty=nsGkAtoms::font, + * aAttribute=nsGkAtoms::color, + * aValue="0x00FFFF" + * @param aFirst [OUT] true if the first text node in the + * selection has the property + * @param aAny [OUT] true if any of the text nodes in the + * selection have the property + * @param aAll [OUT] true if all of the text nodes in the + * selection have the property + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult GetInlineProperty( + nsStaticAtom& aHTMLProperty, nsAtom* aAttribute, const nsAString& aValue, + bool* aFirst, bool* aAny, bool* aAll) const; + + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult GetInlinePropertyWithAttrValue( + nsStaticAtom& 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); + + /** + * Detach aComposerCommandsUpdater from this. + */ + void Detach(const ComposerCommandsUpdater& 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; + + /** + * Compute editing host for aContent. If this editor isn't active in the DOM + * window, this returns nullptr. + */ + enum class LimitInBodyElement { No, Yes }; + [[nodiscard]] Element* ComputeEditingHost( + const nsIContent& aContent, + LimitInBodyElement aLimitInBodyElement = LimitInBodyElement::Yes) const { + return ComputeEditingHostInternal(&aContent, aLimitInBodyElement); + } + + /** + * Compute editing host for the focus node of the Selection. If this editor + * isn't active in the DOM window, this returns nullptr. + */ + [[nodiscard]] Element* ComputeEditingHost( + LimitInBodyElement aLimitInBodyElement = LimitInBodyElement::Yes) const { + return ComputeEditingHostInternal(nullptr, aLimitInBodyElement); + } + + /** + * Return true if we're in designMode. + */ + bool IsInDesignMode() const; + + /** + * Return true if entire the document is editable (although the document + * may have non-editable nodes, e.g., + * <body contenteditable><div contenteditable="false"></div></body> + */ + bool EntireDocumentIsEditable() const; + + /** + * Basically, this always returns true if we're for `contenteditable` or + * `designMode` editor in web apps. However, e.g., Composer of SeaMonkey + * can make the editor not tabbable. + */ + bool IsTabbable() const { return IsInteractionAllowed(); } + + /** + * 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); + + MOZ_CAN_RUN_SCRIPT nsresult InsertHTMLAsAction( + const nsAString& aInString, nsIPrincipal* aPrincipal = nullptr); + + /** + * Refresh positions of resizers. If you change size of target of resizers, + * you need to refresh position of resizers with calling this. + */ + MOZ_CAN_RUN_SCRIPT nsresult RefreshResizers(); + + 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. + ****************************************************************************/ + + /** + * InsertBRElement() creates a <br> element and inserts it before + * aPointToInsert. + * + * @param aWithTransaction Whether the inserting is new element is undoable + * or not. WithTransaction::No is useful only when + * the new element is inserted into a new element + * which has not been connected yet. + * @param aPointToInsert The DOM point where should be <br> node inserted + * before. + * @param aSelect If eNone, returns a point to put caret which is + * suggested by InsertNodeTransaction. + * If eNext, returns a point after the new <br> + * element. + * If ePrevious, returns a point at the new <br> + * element. + * @return The new <br> node and suggesting point to put + * caret which respects aSelect. + */ + MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> InsertBRElement( + WithTransaction aWithTransaction, const EditorDOMPoint& aPointToInsert, + EDirection aSelect = eNone); + + /** + * Delete text in the range in aTextNode. If aTextNode is not editable, this + * does nothing. + * + * @param aTextNode The text node which should be modified. + * @param aOffset Start offset of removing text in aTextNode. + * @param aLength Length of removing text. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> + DeleteTextWithTransaction(dom::Text& aTextNode, uint32_t aOffset, + uint32_t aLength); + + /** + * Replace text in the range with aStringToInsert. If there is a DOM range + * exactly same as the replacing range, it'll be collapsed to + * {aTextNode, aOffset} because of the order of deletion and insertion. + * Therefore, the callers may need to handle `Selection` even when callers + * do not want to update `Selection`. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertTextResult, nsresult> + ReplaceTextWithTransaction(dom::Text& aTextNode, uint32_t aOffset, + uint32_t aLength, + const nsAString& aStringToInsert); + + /** + * Insert aStringToInsert to aPointToInsert. If the point is not editable, + * this returns error. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertTextResult, nsresult> + InsertTextWithTransaction(Document& aDocument, + const nsAString& aStringToInsert, + const EditorDOMPoint& aPointToInsert) final; + + /** + * 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. All children of + * this is deleted first. + * @param aEditingHost The editing host. + * @return If succeeded, returns a suggesting point to put + * caret. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + CopyLastEditableChildStylesWithTransaction(Element& aPreviousBlock, + Element& aNewBlock, + const Element& aEditingHost); + + /** + * 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. + * @return If succeeded, returns a suggesting point to put + * caret. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + RemoveBlockContainerWithTransaction(Element& aElement); + + MOZ_CAN_RUN_SCRIPT nsresult RemoveAttributeOrEquivalent( + Element* aElement, nsAtom* aAttribute, bool aSuppressTransaction) final; + MOZ_CAN_RUN_SCRIPT nsresult SetAttributeOrEquivalent( + Element* aElement, nsAtom* aAttribute, const nsAString& aValue, + bool aSuppressTransaction) final; + 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(); + + /** + * 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); + + static dom::Element* GetLinkElement(nsINode* aNode); + + /** + * Helper routines for font size changing. + */ + enum class FontSize { incr, decr }; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> + SetFontSizeOnTextNode(Text& aTextNode, uint32_t aStartOffset, + uint32_t aEndOffset, FontSize aIncrementOrDecrement); + + 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, + }; + + /** + * SplitAncestorStyledInlineElementsAtRangeEdges() splits all ancestor inline + * elements in the block at aRange if given style matches with some of them. + * + * @param aRange Ancestor inline elements of the start and end + * boundaries will be split. + * @param aStyle The style which you want to split. + * RemoveAllStyles instance is allowed to split any + * inline elements. + * @param aSplitAtEdges Whether this should split elements at start or + * end of inline elements or not. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitRangeOffResult, nsresult> + SplitAncestorStyledInlineElementsAtRangeEdges(const EditorDOMRange& aRange, + const EditorInlineStyle& aStyle, + SplitAtEdges aSplitAtEdges); + + /** + * SplitAncestorStyledInlineElementsAt() splits ancestor inline elements at + * aPointToSplit if specified style matches with them. + * + * @param aPointToSplit The point to split style at. + * @param aStyle The style which you want to split. + * RemoveAllStyles instance is allowed to split any + * inline elements. + * @param aSplitAtEdges Whether this should split elements at start or + * end of inline elements or not. + * @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 Result<SplitNodeResult, nsresult> + SplitAncestorStyledInlineElementsAt(const EditorDOMPoint& aPointToSplit, + const EditorInlineStyle& aStyle, + SplitAtEdges aSplitAtEdges); + + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult GetInlinePropertyBase( + const EditorInlineStyle& aStyle, 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 aStyleToRemove The style which you want to clear. + * @param aSpecifiedStyle Whether the class and style attributes should + * be preserved or discarded. + * @return A candidate position to put caret. If there is + * AutoTransactionsConserveSelection instances, this stops + * suggesting caret point only in some cases. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + ClearStyleAt(const EditorDOMPoint& aPoint, + const EditorInlineStyle& aStyleToRemove, + SpecifiedStyle aSpecifiedStyle); + + 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() inserts aNewNode and moves all content before or after + * aStartOfRightNode to aNewNode. + * + * @param aStartOfRightNode The point to split. The container will keep + * having following or previous content of this. + * @param aNewNode The new node called. The previous or following + * content of aStartOfRightNode will be moved into + * this node. + * @param aDirection Whether aNewNode will have previous or following + * content of aStartOfRightNode. + */ + MOZ_CAN_RUN_SCRIPT Result<SplitNodeResult, nsresult> DoSplitNode( + const EditorDOMPoint& aStartOfRightNode, nsIContent& aNewNode, + SplitNodeDirection aDirection); + + /** + * DoJoinNodes() merges contents in aContentToRemove to aContentToKeep and + * remove aContentToRemove from the DOM tree. aContentToRemove and + * aContentToKeep must have same parent. Additionally, if one of + * aContentToRemove or aContentToKeep is a text node, the other must be a + * text node. + * + * @param aContentToKeep The node that will remain after the join. + * @param aContentToRemove The node that will be joined with aContentToKeep. + * There is no requirement that the two nodes be of + * the same type. + * @param aDirection Whether aContentToKeep is right node or left node, + * and whether aContentToRemove is left node or right + * node. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + DoJoinNodes(nsIContent& aContentToKeep, nsIContent& aContentToRemove, + JoinNodesDirection aDirection); + + /** + * Routines for managing the preservation of selection across + * various editor actions. + */ + bool ArePreservingSelection() const; + void PreserveSelectionAcrossActions(); + MOZ_CAN_RUN_SCRIPT nsresult RestorePreservedSelection(); + void StopPreservingSelection(); + + /** + * Called when JoinNodesTransaction::DoTransaction() did its transaction. + * Note that this is not called when undoing nor redoing. + * + * @param aTransaction The transaction which did join nodes. + * @param aDoJoinNodesResult Result of the doing join nodes. + */ + MOZ_CAN_RUN_SCRIPT void DidJoinNodesTransaction( + const JoinNodesTransaction& aTransaction, nsresult aDoJoinNodesResult); + + 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`... + */ + enum class CheckSelectionInReplacedElement { Yes, OnlyWhenNotInSameNode }; + Result<EditActionResult, nsresult> CanHandleHTMLEditSubAction( + CheckSelectionInReplacedElement aCheckSelectionInReplacedElement = + CheckSelectionInReplacedElement::Yes) const; + + /** + * EnsureCaretNotAfterInvisibleBRElement() 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 + EnsureCaretNotAfterInvisibleBRElement(); + + /** + * MaybeCreatePaddingBRElementForEmptyEditor() creates padding <br> element + * for empty editor if there is no children. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + MaybeCreatePaddingBRElementForEmptyEditor(); + + /** + * EnsureNoPaddingBRElementForEmptyEditor() removes padding <br> element + * for empty editor if there is. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + EnsureNoPaddingBRElementForEmptyEditor(); + + /** + * ReflectPaddingBRElementForEmptyEditor() scans the tree from the root + * element and sets mPaddingBRElementForEmptyEditor if exists, or otherwise + * nullptr. Can be used to manage undo/redo. + */ + [[nodiscard]] nsresult ReflectPaddingBRElementForEmptyEditor(); + + /** + * PrepareInlineStylesForCaret() consider inline styles from top level edit + * sub-action and setting it to `mPendingStylesToApplyToNewContent` 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(); + + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> + HandleInsertText(EditSubAction aEditSubAction, + const nsAString& aInsertionString, + SelectionHandling aSelectionHandling) final; + + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InsertDroppedDataTransferAsAction( + AutoEditActionDataSetter& aEditActionData, + dom::DataTransfer& aDataTransfer, const EditorDOMPoint& aDroppedAt, + nsIPrincipal* aSourcePrincipal) final; + + /** + * GetInlineStyles() retrieves the style of aElement and modifies each item of + * aPendingStyleCacheArray. This might cause flushing layout at retrieving + * computed values of CSS properties. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult GetInlineStyles( + Element& aElement, AutoPendingStyleCacheArray& aPendingStyleCacheArray); + + /** + * CacheInlineStyles() caches style of aElement into mCachedPendingStyles of + * TopLevelEditSubAction. This may cause flushing layout at retrieving + * computed value of CSS properties. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + CacheInlineStyles(Element& aElement); + + /** + * 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 + * PendingStyles to proper element node. + * + * @param aPointToInsertText The point to insert text. + * @param aEditingHost The editing host. + * @return A suggest point to put caret or unset point. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + CreateStyleForInsertText(const EditorDOMPoint& aPointToInsertText, + const Element& aEditingHost); + + /** + * GetMostDistantAncestorMailCiteElement() 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* GetMostDistantAncestorMailCiteElement(const nsINode& aNode) const; + + /** + * HandleInsertParagraphInMailCiteElement() splits aMailCiteElement at + * aPointToSplit. + * + * @param aMailCiteElement The mail-cite element which should be split. + * @param aPointToSplit The point to split. + * @param aEditingHost The editing host. + * @return Candidate caret position where is at inserted + * <br> element into the split point. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + HandleInsertParagraphInMailCiteElement(Element& aMailCiteElement, + const EditorDOMPoint& aPointToSplit, + const Element& aEditingHost); + + /** + * HandleInsertBRElement() inserts a <br> element into aPointToBreak. + * This may split container elements at the point and/or may move following + * <br> element to immediately after the new <br> element if necessary. + * + * @param aPointToBreak The point where new <br> element will be + * inserted before. + * @param aEditingHost Current active editing host. + * @return If succeeded, returns new <br> element and + * candidate caret point. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> + HandleInsertBRElement(const EditorDOMPoint& aPointToBreak, + const Element& aEditingHost); + + /** + * HandleInsertLinefeed() inserts a linefeed character into aInsertToBreak. + * + * @param aInsertToBreak The point where new linefeed character will be + * inserted before. + * @param aEditingHost Current active editing host. + * @return A suggest point to put caret. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + HandleInsertLinefeed(const EditorDOMPoint& aInsertToBreak, + const Element& aEditingHost); + + /** + * 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, at 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. + * @param aEditingHost [in] The editing host. + * @param aAncestorLimiter [in/optional] If specified, this stops splitting + * ancestors when meets this node. + * @return A suggest point to put caret if succeeded, but + * it may be unset. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + SplitParentInlineElementsAtRangeBoundaries( + RangeItem& aRangeItem, const Element& aEditingHost, + const nsIContent* aAncestorLimiter = nullptr); + + /** + * 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. + * @return A suggest point to put caret if + * succeeded, but it may unset. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, 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. + * + * @return A suggest point to put caret if + * succeeded, but it may unset. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + MaybeSplitElementsAtEveryBRElement( + nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, + EditSubAction aEditSubAction); + + /** + * 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. + */ + template <typename EditorDOMRangeType> + already_AddRefed<nsRange> CreateRangeIncludingAdjuscentWhiteSpaces( + const EditorDOMRangeType& aRange); + template <typename EditorDOMPointType1, typename EditorDOMPointType2> + already_AddRefed<nsRange> CreateRangeIncludingAdjuscentWhiteSpaces( + const EditorDOMPointType1& aStartPoint, + const EditorDOMPointType2& aEndPoint); + + /** + * GetRangeExtendedToHardLineEdgesForBlockEditAction() returns an extended + * range if aRange should be extended before handling a block level editing. + * If aRange start and/or end point <br> 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]] Result<EditorRawDOMRange, nsresult> + GetRangeExtendedToHardLineEdgesForBlockEditAction( + const nsRange* aRange, const Element& aEditingHost) const; + + /** + * InitializeInsertingElement is a callback type of methods which inserts + * an element into the DOM tree. This is called immediately before inserting + * aNewElement into the DOM tree. + * + * @param aHTMLEditor The HTML editor which modifies the DOM tree. + * @param aNewElement The new element which will be or was inserted into + * the DOM tree. + * @param aPointToInsert The position aNewElement will be or was inserted. + */ + using InitializeInsertingElement = + std::function<nsresult(HTMLEditor& aHTMLEditor, Element& aNewElement, + const EditorDOMPoint& aPointToInsert)>; + static InitializeInsertingElement DoNothingForNewElement; + static InitializeInsertingElement InsertNewBRElement; + + /** + * Helper methods to implement InitializeInsertingElement. + */ + MOZ_CAN_RUN_SCRIPT static Result<CreateElementResult, nsresult> + AppendNewElementToInsertingElement( + HTMLEditor& aHTMLEditor, const nsStaticAtom& aTagName, + Element& aNewElement, + const InitializeInsertingElement& aInitializer = DoNothingForNewElement); + MOZ_CAN_RUN_SCRIPT static Result<CreateElementResult, nsresult> + AppendNewElementWithBRToInsertingElement(HTMLEditor& aHTMLEditor, + const nsStaticAtom& aTagName, + Element& aNewElement); + + /** + * Create an element node whose name is aTag at before aPointToInsert. When + * this succeed to create an element node, this inserts the element to + * aPointToInsert. + * + * @param aWithTransaction Whether the inserting is new element is undoable + * or not. WithTransaction::No is useful only when + * the new element is inserted into a new element + * which has not been connected yet. + * @param aTagName The element name to create. + * @param aPointToInsert The insertion point of new element. + * If this refers end of the container or after, + * the transaction will append the element to the + * container. + * Otherwise, will insert the element before the + * child node referred by this. + * Note that this point will be invalid once this + * method inserts the new element. + * @param aInitializer A function to initialize the new element before + * connecting the element into the DOM tree. Note + * that this should not touch outside given element + * because doing it would break range updater's + * result. + * @return The created new element node and candidate caret + * position. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> + CreateAndInsertElement( + WithTransaction aWithTransaction, nsAtom& aTagName, + const EditorDOMPoint& aPointToInsert, + const InitializeInsertingElement& aInitializer = DoNothingForNewElement); + + /** + * Callback of CopyAttributes(). + * + * @param aHTMLEditor The HTML editor. + * @param aSrcElement The element which have the attribute. + * @param aDestElement The element which will have the attribute. + * @param aAttr [in] The attribute which will be copied. + * @param aValue [in/out] The attribute value which will be copied. + * Once updated, the new value is used. + * @return true if the attribute should be copied, otherwise, + * false. + */ + using AttributeFilter = std::function<bool( + HTMLEditor& aHTMLEditor, Element& aSrcElement, Element& aDestElement, + const dom::Attr& aAttr, nsString& aValue)>; + static AttributeFilter CopyAllAttributes; + static AttributeFilter CopyAllAttributesExceptId; + static AttributeFilter CopyAllAttributesExceptDir; + static AttributeFilter CopyAllAttributesExceptIdAndDir; + + /** + * Copy all attributes of aSrcElement to aDestElement as-is. Different from + * EditorBase::CloneAttributesWithTransaction(), this does not use + * SetAttributeOrEquivalent() nor does not clear existing attributes of + * aDestElement. + * + * @param aWithTransaction Whether recoding with transactions or not. + * @param aDestElement The element will have attributes. + * @param aSrcElement The element whose attributes will be copied. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult CopyAttributes( + WithTransaction aWithTransaction, Element& aDestElement, + Element& aSrcElement, const AttributeFilter& = CopyAllAttributes); + + /** + * 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 in aEditingHost. + * @param aEditingHost The editing host. + * @return When succeeded, SplitPoint() returns + * the point to insert the element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitNodeResult, nsresult> + MaybeSplitAncestorsForInsertWithTransaction( + nsAtom& aTag, const EditorDOMPoint& aStartOfDeepestRightNode, + const Element& aEditingHost); + + /** + * InsertElementWithSplittingAncestorsWithTransaction() is a wrapper of + * MaybeSplitAncestorsForInsertWithTransaction() and CreateAndInsertElement(). + * I.e., will create an element whose tag name is aTagName and split ancestors + * if it's necessary, then, insert it. + * + * @param aTagName The tag name which you want to insert new + * element at aPointToInsert. + * @param aPointToInsert The insertion point. New element will be + * inserted before here. + * @param aBRElementNextToSplitPoint + * Whether <br> element should be deleted or + * kept if and only if a <br> element follows + * split point. + * @param aEditingHost The editing host with which we're handling it. + * @param aInitializer A function to initialize the new element before + * connecting the element into the DOM tree. Note + * that this should not touch outside given element + * because doing it would break range updater's + * result. + * @return If succeeded, returns the new element node and + * suggesting point to put caret. + */ + enum class BRElementNextToSplitPoint { Keep, Delete }; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> + InsertElementWithSplittingAncestorsWithTransaction( + nsAtom& aTagName, const EditorDOMPoint& aPointToInsert, + BRElementNextToSplitPoint aBRElementNextToSplitPoint, + const Element& aEditingHost, + const InitializeInsertingElement& aInitializer = DoNothingForNewElement); + + /** + * SplitRangeOffFromBlock() splits aBlockElement at two points, before + * aStartOfMiddleElement and after aEndOfMiddleElement. If they are very + * start or very end of aBlockElement, 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 Result<SplitRangeOffFromNodeResult, nsresult> + SplitRangeOffFromBlock(Element& aBlockElement, + nsIContent& aStartOfMiddleElement, + nsIContent& aEndOfMiddleElement); + + /** + * RemoveBlockContainerElementWithTransactionBetween() splits the nodes + * at aStartOfRange and aEndOfRange, then, removes the middle element which + * was split off from aBlockContainerElement and moves the ex-children to + * where the middle element was. I.e., all nodes between aStartOfRange and + * aEndOfRange (including themselves) will be unwrapped from + * aBlockContainerElement. + * + * @param aBlockContainerElement The node which will be split. + * @param aStartOfRange The first node which will be unwrapped + * from aBlockContainerElement. + * @param aEndOfRange The last node which will be unwrapped from + * aBlockContainerElement. + * @return The left content is new created left + * element of aBlockContainerElement. + * The right content is split element, + * i.e., must be aBlockContainerElement. + * The middle content is nullptr since + * removing it is the job of this method. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitRangeOffFromNodeResult, nsresult> + RemoveBlockContainerElementWithTransactionBetween( + Element& aBlockContainerElement, nsIContent& aStartOfRange, + nsIContent& aEndOfRange); + + /** + * WrapContentsInBlockquoteElementsWithTransaction() 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. + * @param aEditingHost The editing host. + * @return A blockquote element which is created at last + * and a suggest of caret position if succeeded. + * The caret suggestion may be unset if there is + * no suggestion. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> + WrapContentsInBlockquoteElementsWithTransaction( + const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, + const Element& aEditingHost); + + /** + * RemoveBlockContainerElementsWithTransaction() 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. + * + * @return A suggest point to put caret if succeeded, but it may be + * unset if there is no suggestion. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + RemoveBlockContainerElementsWithTransaction( + const 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. + * @param aEditingHost The editing host. + * @return The latest created new block element and a + * suggest point to put caret. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> + CreateOrChangeBlockContainerElement( + nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, nsAtom& aBlockTag, + const Element& aEditingHost); + + /** + * FormatBlockContainerWithTransaction() is implementation of "formatBlock" + * command of `Document.execCommand()`. This applies block style or removes + * it. + * + * @param aSelectionRanges The ranges which are cloned by selection or + * updated from it with doing something before + * calling this. + * @param aBlockType New block tag name. + * If nsGkAtoms::normal or nsGkAtoms::_empty, + * RemoveBlockContainerElementsWithTransaction() will be + * called. + * If nsGkAtoms::blockquote, + * WrapContentsInBlockquoteElementsWithTransaction() will + * be called. + * Otherwise, CreateOrChangeBlockContainerElement() will be + * called. + * @param aEditingHost The editing host. + * @return If selection should be finally collapsed in a + * created block element, this returns the element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<RefPtr<Element>, nsresult> + FormatBlockContainerWithTransaction(AutoRangeArray& aSelectionRanges, + nsAtom& aBlockType, + const Element& aEditingHost); + + /** + * Determine if aPointToInsert is start of a hard line and end of the line + * (i.e, in an empty line) and the line ends with block boundary, inserts a + * `<br>` element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> + InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary( + const EditorDOMPoint& aPointToInsert); + + /** + * 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(); + + /** + * SplitParagraphWithTransaction() 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 aMayBecomeVisibleBRElement + * Next <br> element of the split point if there + * is. Otherwise, nullptr. If this is not nullptr, + * the <br> element may be removed if it becomes + * visible. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitNodeResult, nsresult> + SplitParagraphWithTransaction(Element& aParentDivOrP, + const EditorDOMPoint& aStartOfRightNode, + dom::HTMLBRElement* aMayBecomeVisibleBRElement); + + /** + * HandleInsertParagraphInParagraph() does the right thing for Enter key + * press or 'insertParagraph' command in aParentDivOrP. aParentDivOrP will + * be split **around** aCandidatePointToSplit. If this thinks that it should + * be handled to insert a <br> instead, this returns "not handled". + * + * @param aParentDivOrP The parent block. This must be <p> or <div> + * element. + * @param aCandidatePointToSplit + * The point where the caller want to split + * aParentDivOrP. However, in some cases, this is not + * used as-is. E.g., this method avoids to create new + * empty <a href> in the right paragraph. So this may + * be adjusted to proper position around it. + * @param aEditingHost The editing host. + * @return If the caller should default to inserting <br> + * element, returns "not handled". + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitNodeResult, nsresult> + HandleInsertParagraphInParagraph(Element& aParentDivOrP, + const EditorDOMPoint& aCandidatePointToSplit, + const Element& aEditingHost); + + /** + * HandleInsertParagraphInHeadingElement() handles insertParagraph command + * (i.e., handling Enter key press) in a heading element. This splits + * aHeadingElement element at aPointToSplit. 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 aHeadingElement The heading element to be split. + * @param aPointToSplit The point to split aHeadingElement. + * @return New paragraph element, meaning right heading + * element if aHeadingElement is split, or newly + * created or existing paragraph element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertParagraphResult, nsresult> + HandleInsertParagraphInHeadingElement(Element& aHeadingElement, + const EditorDOMPoint& aPointToSplit); + + /** + * HandleInsertParagraphInListItemElement() handles insertParagraph command + * (i.e., handling Enter key press) in a list item element. + * + * @param aListItemElement The list item which has the following point. + * @param aPointToSplit The point to split aListItemElement. + * @param aEditingHost The editing host. + * @return New paragraph element, meaning right list item + * element if aListItemElement is split, or newly + * created paragraph element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertParagraphResult, nsresult> + HandleInsertParagraphInListItemElement(Element& aListItemElement, + const EditorDOMPoint& aPointToSplit, + const Element& aEditingHost); + + /** + * InsertParagraphSeparatorAsSubAction() handles insertPargraph commad + * (i.e., handling Enter key press) with the above helper methods. + * + * @param aEditingHost The editing host. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> + InsertParagraphSeparatorAsSubAction(const Element& aEditingHost); + + /** + * InsertLineBreakAsSubAction() inserts a new <br> element or a linefeed + * character at selection. If there is non-collapsed selection ranges, the + * selected ranges is deleted first. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InsertLineBreakAsSubAction(); + + /** + * 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 Result<CreateElementResult, nsresult> + ChangeListElementType(Element& aListElement, nsAtom& aListType, + nsAtom& aItemType); + + class AutoListElementCreator; + + /** + * 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 Result<EditActionResult, nsresult> + MakeOrChangeListAndListItemAsSubAction( + const nsStaticAtom& 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 Result<CaretPoint, nsresult> + DeleteTextAndTextNodesWithTransaction( + const EditorDOMPointType& aStartPoint, + const EditorDOMPointType& aEndPoint, + TreatEmptyTextNodes aTreatEmptyTextNodes); + + /** + * JoinNodesWithTransaction() joins aLeftContent and aRightContent. Content + * of aLeftContent will be merged into aRightContent. Actual implemenation of + * this method is JoinNodesImpl(). So, see its explanation for the detail. + * + * @param aLeftContent Will be removed from the DOM tree. + * @param aRightContent The node which will be new container of the content + * of aLeftContent. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<JoinNodesResult, nsresult> + JoinNodesWithTransaction(nsIContent& aLeftContent, nsIContent& aRightContent); + + /** + * 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. + */ + [[nodiscard]] inline MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> + ReplaceContainerAndCloneAttributesWithTransaction(Element& aOldContainer, + const nsAtom& aTagName); + + /** + * 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. + */ + [[nodiscard]] inline MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> + ReplaceContainerWithTransaction(Element& aOldContainer, + const nsAtom& aTagName, + const nsAtom& aAttribute, + const nsAString& aAttributeValue); + + /** + * 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. + */ + [[nodiscard]] inline MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> + ReplaceContainerWithTransaction(Element& aOldContainer, + const nsAtom& aTagName); + + /** + * RemoveContainerWithTransaction() removes aElement from the DOM tree and + * moves all its children to the parent of aElement. + * + * @param aElement The element to be removed. + * @return A suggestion point to put caret. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + RemoveContainerWithTransaction(Element& aElement); + + /** + * InsertContainerWithTransaction() creates new element whose name is + * aWrapperTagName, moves aContentToBeWrapped into the new element, then, + * inserts the new element into where aContentToBeWrapped was. + * NOTE: This method does not check if aContentToBeWrapped is valid child + * of the new element. So, callers need to guarantee it. + * + * @param aContentToBeWrapped The content which will be wrapped with new + * element. + * @param aWrapperTagName Element name of new element which will wrap + * aContent and be inserted into where aContent + * was. + * @param aInitializer A callback to initialize new element before + * inserting to the DOM tree. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> + InsertContainerWithTransaction( + nsIContent& aContentToBeWrapped, const nsAtom& aWrapperTagName, + const InitializeInsertingElement& aInitializer = DoNothingForNewElement); + + /** + * MoveNodeWithTransaction() moves aContentToMove to aPointToInsert. + * + * @param aContentToMove The node to be moved. + * @param aPointToInsert The point where aContentToMove will be inserted. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<MoveNodeResult, nsresult> + MoveNodeWithTransaction(nsIContent& aContentToMove, + const EditorDOMPoint& aPointToInsert); + + /** + * MoveNodeToEndWithTransaction() moves aContentToMove to end of + * aNewContainer. + * + * @param aContentToMove The node to be moved. + * @param aNewContainer The new container which will contain aContentToMove + * as its last child. + */ + [[nodiscard]] inline MOZ_CAN_RUN_SCRIPT Result<MoveNodeResult, nsresult> + MoveNodeToEndWithTransaction(nsIContent& aContentToMove, + nsINode& aNewContainer); + + /** + * 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. + * @param aPreserveWhiteSpaceStyle + * If yes and if it's possible to keep white-space + * style, this method will set `style` attribute to + * moving node or creating new <span> element. + * @param aRemoveIfCommentNode + * If yes, this removes a comment node instead of + * moving it to the destination. Note that this + * does not remove comment nodes in moving nodes + * because it requires additional scan. + */ + enum class PreserveWhiteSpaceStyle { No, Yes }; + enum class RemoveIfCommentNode { No, Yes }; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<MoveNodeResult, nsresult> + MoveNodeOrChildrenWithTransaction( + nsIContent& aContentToMove, const EditorDOMPoint& aPointToInsert, + PreserveWhiteSpaceStyle aPreserveWhiteSpaceStyle, + RemoveIfCommentNode aRemoveIfCommentNode); + + /** + * 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. + * @param aPreserveWhiteSpaceStyle + * If yes and if it's possible to keep white-space + * style, this method will set `style` attribute to + * moving node or creating new <span> element. + * @param aRemoveIfCommentNode + * If yes, this removes a comment node instead of + * moving it to the destination. Note that this + * does not remove comment nodes in moving nodes + * because it requires additional scan. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<MoveNodeResult, nsresult> + MoveChildrenWithTransaction(Element& aElement, + const EditorDOMPoint& aPointToInsert, + PreserveWhiteSpaceStyle aPreserveWhiteSpaceStyle, + RemoveIfCommentNode aRemoveIfCommentNode); + + /** + * 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); + + /** + * MoveInclusiveNextSiblings() moves aChild and all siblings after it to + * before aPointToInsert.GetChild(). + * See explanation of MoveChildrenBetween() for the detail of the behavior. + * + * @param aChild The node which is first 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 MoveInclusiveNextSiblings(nsIContent& aChild, + const EditorRawDOMPoint& aPointToInsert, + ErrorResult& aError); + + /** + * 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. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitNodeResult, nsresult> + SplitNodeWithTransaction(const EditorDOMPoint& aStartOfRightNode); + + /** + * 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. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitNodeResult, nsresult> + SplitNodeDeepWithTransaction(nsIContent& aMostAncestorToSplit, + const EditorDOMPoint& aDeepestStartOfRightNode, + SplitAtEdges aSplitAtEdges); + + /** + * 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<CaretPoint, 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 except a linefeed in a preformatted + // node. + PreformattedLineBreak, // Preformatted linebreak + }; + + /** + * 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); + template <typename EditorDOMPointType> + static CharPointType GetCharPointType(const EditorDOMPointType& aPoint); + + /** + * 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 IsCollapsibleWhiteSpace() 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) const; + + /** + * This method handles "delete selection" commands. + * + * @param aDirectionAndAmount Direction of the deletion. + * @param aStripWrappers Must be eStrip or eNoStrip. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> + HandleDeleteSelection(nsIEditor::EDirection aDirectionAndAmount, + nsIEditor::EStripWrappers aStripWrappers) final; + + class AutoDeleteRangesHandler; + class AutoMoveOneLineHandler; + + /** + * 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. + * + * @param aEditingHost The editing host. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + RemoveListAtSelectionAsSubAction(const Element& aEditingHost); + + /** + * 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. + * + * @param aElement The element whose start margin should be + * changed. + * @param aChangeMargin Whether increase or decrease the margin. + * @param aEditingHost The editing host. + * @return May suggest a suggest point to put caret. + */ + enum class ChangeMargin { Increase, Decrease }; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + ChangeMarginStart(Element& aElement, ChangeMargin aChangeMargin, + const Element& aEditingHost); + + /** + * HandleCSSIndentAroundRanges() indents around aRanges with CSS. + * + * @param aRanges The ranges where the content should be indented. + * @param aEditingHost The editing host. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleCSSIndentAroundRanges( + AutoRangeArray& aRanges, const Element& aEditingHost); + + /** + * HandleCSSIndentAroundRanges() indents around aRanges with HTML. + * + * @param aRanges The ranges where the content should be indented. + * @param aEditingHost The editing host. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleHTMLIndentAroundRanges( + AutoRangeArray& aRanges, const Element& aEditingHost); + + /** + * HandleIndentAtSelection() indents around Selection with HTML or CSS. + * + * @param aEditingHost The editing host. + */ + // TODO: Make this take AutoRangeArray instead of retrieving `Selection` + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> + HandleIndentAtSelection(const Element& aEditingHost); + + /** + * 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. + * @param aEditingHost The editing host. + * @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 Result<SplitRangeOffFromNodeResult, nsresult> + OutdentPartOfBlock(Element& aBlockElement, nsIContent& aStartOfOutdent, + nsIContent& aEndOfOutdent, + BlockIndentedWith aBlockIndentedWith, + const Element& aEditingHost); + + /** + * 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. + * + * @param aEditingHost The editing host. + * @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 Result<SplitRangeOffFromNodeResult, nsresult> + HandleOutdentAtSelectionInternal(const Element& aEditingHost); + + /** + * HandleOutdentAtSelection() outdents contents around Selection. + * + * @param aEditingHost The editing host. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> + HandleOutdentAtSelection(const Element& aEditingHost); + + /** + * 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. + * @return A candidate position to put caret. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + AlignBlockContentsWithDivElement(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>`. + * @return A candidate position to put caret. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, 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 Result<CreateElementResult, nsresult> + EnsureHardLineBeginsWithFirstChildOf(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 Result<CreateElementResult, nsresult> + EnsureHardLineEndsWithLastChildOf(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. + * @return A candidate point to put caret. + */ + enum class EditTarget { + OnlyDescendantsExceptTable, + NodeAndDescendantsExceptTable + }; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, 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. + * @return A candidate point to put caret. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + SetBlockElementAlign(Element& aBlockOrHRElement, const nsAString& aAlignType, + EditTarget aEditTarget); + + /** + * InsertDivElementToAlignContents() inserts a new <div> element (which has + * only a padding <br> element) to aPointToInsert for a placeholder whose + * contents will be aligned. + * + * @param aPointToInsert A point to insert new empty <div>. + * @param aAlignType New align attribute value where the contents + * should be aligned to. + * @param aEditingHost The editing host. + * @return New <div> element which has only a padding <br> + * element and is styled to align contents. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> + InsertDivElementToAlignContents(const EditorDOMPoint& aPointToInsert, + const nsAString& aAlignType, + const Element& aEditingHost); + + /** + * 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. + * @param aEditingHost The editing host. + * @return Last created <div> element which should contain + * caret and candidate position which may be + * outside the <div> element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> + AlignNodesAndDescendants( + nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, + const nsAString& aAlignType, const Element& aEditingHost); + + /** + * AlignContentsAtRanges() aligns contents around aRanges to aAlignType. + * + * @param aRanges The ranges where should be aligned. + * @param aAlignType New align attribute value where the contents + * should be aligned to. + * @param aEditingHost The editing host. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + AlignContentsAtRanges(AutoRangeArray& aRanges, const nsAString& aAlignType, + const Element& aEditingHost); + + /** + * AlignAsSubAction() handles "align" command with `Selection`. + * + * @param aAlignType New align attribute value where the contents + * should be aligned to. + * @param aEditingHost The editing host. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> + AlignAsSubAction(const nsAString& aAlignType, const Element& aEditingHost); + + /** + * 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(const EditorDOMRange& 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(); + + /** + * 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. + * @param aEditingHost The editing host. + */ + // TODO: Rewrite this with `Result<RefPtr<Element>, nsresult>`. + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + MoveSelectedContentsToDivElementToMakeItAbsolutePosition( + RefPtr<Element>* aTargetElement, const Element& aEditingHost); + + /** + * SetSelectionToAbsoluteAsSubAction() move selected contents to first + * selected `<div>` element or newly created `<div>` element and make + * the `<div>` element positioned absolutely. + * + * @param aEditingHost The editing host. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> + SetSelectionToAbsoluteAsSubAction(const Element& aEditingHost); + + /** + * SetSelectionToStaticAsSubAction() sets the `position` property of a + * selection parent's block whose `position` is `absolute` to `static`. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> + 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 Result<EditActionResult, nsresult> + AddZIndexAsSubAction(int32_t aChange); + + /** + * OnDocumentModified() is called when editor content is changed. + */ + MOZ_CAN_RUN_SCRIPT nsresult OnDocumentModified(); + + protected: // Called by helper classes. + /** + * Get split/join node(s) direction for **this** instance. + */ + [[nodiscard]] inline SplitNodeDirection GetSplitNodeDirection() const; + [[nodiscard]] inline JoinNodesDirection GetJoinNodesDirection() const; + + MOZ_CAN_RUN_SCRIPT void OnStartToHandleTopLevelEditSubAction( + EditSubAction aTopLevelEditSubAction, + nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, + ErrorResult& aRv) final; + MOZ_CAN_RUN_SCRIPT nsresult OnEndHandlingTopLevelEditSubAction() final; + + protected: // Shouldn't be used by friend classes + virtual ~HTMLEditor(); + + /** + * 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. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InitEditorContentAndSelection(); + + /** + * Collapse `Selection` to the last leaf content of the <body> or the document + * element. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + CollapseSelectionToEndOfLastLeafNodeOfDocument() const; + + MOZ_CAN_RUN_SCRIPT nsresult SelectAllInternal() final; + + [[nodiscard]] Element* ComputeEditingHostInternal( + const nsIContent* aContent, LimitInBodyElement aLimitInBodyElement) const; + + /** + * Creates a range with just the supplied node and appends that to the + * selection. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + AppendContentToSelectionAsRange(nsIContent& aContent); + + /** + * When you are using AppendContentToSelectionAsRange(), call this first to + * start a new selection. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult ClearSelection(); + + /** + * 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); + + /** + * 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, const 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. + * When aTableOrElementInTable is neither <table> nor in a <table> element, + * returns NS_ERROR_FAILURE. However, if <table> does not have <tr> element, + * returns nullptr. + * + * @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. + */ + Result<RefPtr<Element>, nsresult> GetFirstTableRowElement( + const Element& aTableOrElementInTable) const; + + /** + * GetNextTableRowElement() returns next <tr> element of aTableRowElement. + * This won't cross <table> element boundary but may cross table section + * elements like <tbody>. + * Note that if given element is <tr> but there is no next <tr> element, this + * returns nullptr but does not return error. + * + * @param aTableRowElement A <tr> element. + */ + Result<RefPtr<Element>, nsresult> GetNextTableRowElement( + const Element& aTableRowElement) 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. + */ + MOZ_CAN_RUN_SCRIPT CellIndexes(Element& aCellElement, PresShell* aPresShell) + : mRow(-1), mColumn(-1) { + Update(aCellElement, aPresShell); + } + + /** + * Update mRowIndex and mColumnIndex with indexes of aCellElement. + * + * @param See above. + */ + MOZ_CAN_RUN_SCRIPT void Update(Element& aCellElement, + PresShell* aPresShell); + + /** + * 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. + */ + MOZ_CAN_RUN_SCRIPT CellIndexes(HTMLEditor& aHTMLEditor, + Selection& aSelection) + : mRow(-1), mColumn(-1) { + Update(aHTMLEditor, aSelection); + } + + /** + * 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); + + 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; + } + + [[nodiscard]] bool isErr() const { return mRow < 0 || mColumn < 0; } + + private: + CellIndexes() : mRow(-1), mColumn(-1) {} + CellIndexes(int32_t aRowIndex, int32_t aColumnIndex) + : mRow(aRowIndex), mColumn(aColumnIndex) {} + + friend struct CellData; + }; + + struct MOZ_STACK_CLASS CellData final { + MOZ_KNOWN_LIVE 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 = -1; + int32_t mColSpan = -1; + // 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 = -1; + int32_t mEffectiveColSpan = -1; + // 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 = false; + + CellData() = delete; + + /** + * This returns an instance which is initialized with a <table> element and + * both row and column index to specify a cell element. + */ + [[nodiscard]] static CellData AtIndexInTableElement( + const HTMLEditor& aHTMLEditor, const Element& aTableElement, + int32_t aRowIndex, int32_t aColumnIndex); + [[nodiscard]] static CellData AtIndexInTableElement( + const HTMLEditor& aHTMLEditor, const Element& aTableElement, + const CellIndexes& aIndexes) { + MOZ_ASSERT(!aIndexes.isErr()); + return AtIndexInTableElement(aHTMLEditor, aTableElement, aIndexes.mRow, + aIndexes.mColumn); + } + + /** + * Treated as error if fails to compute current index or first index of the + * cell. Note that even if the cell is not found due to no corresponding + * frame at current index, it's not an error situation. + */ + [[nodiscard]] bool isOk() const { return !isErr(); } + [[nodiscard]] bool isErr() const { return mFirst.isErr(); } + + /** + * FailedOrNotFound() returns true if this failed to initialize/update + * or succeeded but found no cell element. + */ + [[nodiscard]] bool FailedOrNotFound() const { return isErr() || !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. + */ + [[nodiscard]] bool IsSpannedFromOtherRowOrColumn() const { + return mElement && mCurrent != mFirst; + } + [[nodiscard]] bool IsSpannedFromOtherColumn() const { + return mElement && mCurrent.mColumn != mFirst.mColumn; + } + [[nodiscard]] bool IsSpannedFromOtherRow() const { + return mElement && mCurrent.mRow != mFirst.mRow; + } + [[nodiscard]] bool IsNextColumnSpannedFromOtherColumn() const { + return mElement && mCurrent.mColumn + 1 < NextColumnIndex(); + } + + /** + * NextColumnIndex() and NextRowIndex() return column/row index of + * next cell. Note that this does not check whether there is next + * cell or not actually. + */ + [[nodiscard]] int32_t NextColumnIndex() const { + if (NS_WARN_IF(FailedOrNotFound())) { + return -1; + } + return mCurrent.mColumn + mEffectiveColSpan; + } + [[nodiscard]] 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. + */ + [[nodiscard]] int32_t LastColumnIndex() const { + if (NS_WARN_IF(FailedOrNotFound())) { + return -1; + } + return NextColumnIndex() - 1; + } + [[nodiscard]] 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. + */ + [[nodiscard]] int32_t NumberOfPrecedingColmuns() const { + if (NS_WARN_IF(FailedOrNotFound())) { + return -1; + } + return mCurrent.mColumn - mFirst.mColumn; + } + [[nodiscard]] 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. + */ + [[nodiscard]] int32_t NumberOfFollowingColumns() const { + if (NS_WARN_IF(FailedOrNotFound())) { + return -1; + } + return mEffectiveColSpan - 1; + } + [[nodiscard]] int32_t NumberOfFollowingRows() const { + if (NS_WARN_IF(FailedOrNotFound())) { + return -1; + } + return mEffectiveRowSpan - 1; + } + + private: + explicit CellData(int32_t aCurrentRowIndex, int32_t aCurrentColumnIndex, + int32_t aFirstRowIndex, int32_t aFirstColumnIndex) + : mCurrent(aCurrentRowIndex, aCurrentColumnIndex), + mFirst(aFirstRowIndex, aFirstColumnIndex) {} + explicit CellData(Element& aElement, int32_t aRowIndex, + int32_t aColumnIndex, nsTableCellFrame& aTableCellFrame, + nsTableWrapperFrame& aTableWrapperFrame); + + [[nodiscard]] static CellData Error(int32_t aRowIndex, + int32_t aColumnIndex) { + return CellData(aRowIndex, aColumnIndex, -1, -1); + } + [[nodiscard]] static CellData NotFound(int32_t aRowIndex, + int32_t aColumnIndex) { + return CellData(aRowIndex, aColumnIndex, aRowIndex, aColumnIndex); + } + }; + + /** + * 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; + + TableSize() = delete; + + /** + * @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. + */ + [[nodiscard]] static Result<TableSize, nsresult> Create( + HTMLEditor& aHTMLEditor, Element& aTableOrElementInTable); + + [[nodiscard]] bool IsEmpty() const { return !mRowCount || !mColumnCount; } + + private: + TableSize(int32_t aRowCount, int32_t aColumCount) + : mRowCount(aRowCount), mColumnCount(aColumCount) {} + }; + + /** + * 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. + */ + [[nodiscard]] inline Element* GetTableCellElementAt( + Element& aTableElement, const CellIndexes& aCellIndexes) const; + [[nodiscard]] 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). + */ + Result<RefPtr<Element>, nsresult> GetSelectedOrParentTableElement( + bool* aIsCellSelected = nullptr) const; + + /** + * GetFirstSelectedCellElementInTable() returns <td> or <th> element at + * first selection (using GetSelectedOrParentTableElement). If found cell + * element is not in <table> or <tr> element, this returns nullptr. + */ + Result<RefPtr<Element>, nsresult> GetFirstSelectedCellElementInTable() const; + + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandlePaste( + AutoEditActionDataSetter& aEditActionData, int32_t aClipboardType) final; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandlePasteAsQuotation( + AutoEditActionDataSetter& aEditActionData, int32_t aClipboardType) final; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + HandlePasteTransferable(AutoEditActionDataSetter& aEditActionData, + nsITransferable& aTransferable) final; + + /** + * 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); + + [[nodiscard]] MOZ_CAN_RUN_SCRIPT 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 IsInPlaintextMode() 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 aContent The content node to insert. + * @param aPointToInsert Insertion point. + * @param aSplitAtEdges Splitting can result in empty nodes? + */ + template <typename NodeType> + [[nodiscard]] MOZ_CAN_RUN_SCRIPT + Result<CreateNodeResultBase<NodeType>, nsresult> + InsertNodeIntoProperAncestorWithTransaction( + NodeType& aContent, const EditorDOMPoint& aPointToInsert, + SplitAtEdges aSplitAtEdges); + + /** + * 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. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> + ReplaceContainerWithTransactionInternal(Element& aOldContainer, + const nsAtom& aTagName, + const nsAtom& aAttribute, + const nsAString& aAttributeValue, + bool aCloneAllAttributes); + + /** + * 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. + * @param aInitializer A function to initialize the new element before + * or after (depends on the pref) connecting the + * element into the DOM tree. Note that this should + * not touch outside given element because doing it + * would break range updater's result. + */ + MOZ_CAN_RUN_SCRIPT Result<RefPtr<Element>, nsresult> + DeleteSelectionAndCreateElement( + nsAtom& aTag, + const InitializeInsertingElement& aInitializer = DoNothingForNewElement); + + /** + * 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 return error. + */ + MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> PrepareToInsertBRElement( + const EditorDOMPoint& aPointToInsert); + + /** + * IndentAsSubAction() indents the content around Selection. + * + * @param aEditingHost The editing host. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> + IndentAsSubAction(const Element& aEditingHost); + + /** + * OutdentAsSubAction() outdents the content around Selection. + * + * @param aEditingHost The editing host. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> + OutdentAsSubAction(const Element& aEditingHost); + + MOZ_CAN_RUN_SCRIPT nsresult LoadHTML(const nsAString& aInputString); + + /** + * UpdateMetaCharsetWithTransaction() scans all <meta> elements in the + * document and if and only if there is a <meta> element having `httpEquiv` + * attribute and whose value includes `content-type`, updates its `content` + * attribute value to aCharacterSet. + */ + MOZ_CAN_RUN_SCRIPT bool UpdateMetaCharsetWithTransaction( + Document& aDocument, const nsACString& aCharacterSet); + + /** + * SetInlinePropertiesAsSubAction() stores new styles with + * mPendingStylesToApplyToNewContent if `Selection` is collapsed. Otherwise, + * applying the styles to all selected contents. + * + * @param aStylesToSet The styles which should be applied to the + * selected content. + */ + template <size_t N> + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetInlinePropertiesAsSubAction( + const AutoTArray<EditorInlineStyleAndValue, N>& aStylesToSet); + + /** + * SetInlinePropertiesAroundRanges() applying the styles to the ranges even if + * the ranges are collapsed. + */ + template <size_t N> + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetInlinePropertiesAroundRanges( + AutoRangeArray& aRanges, + const AutoTArray<EditorInlineStyleAndValue, N>& aStylesToSet, + const Element& aEditingHost); + + /** + * RemoveInlinePropertiesAsSubAction() removes specified styles from + * mPendingStylesToApplyToNewContent if `Selection` is collapsed. Otherwise, + * removing the style. + * + * @param aStylesToRemove Styles to remove from the selected contents. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult RemoveInlinePropertiesAsSubAction( + const nsTArray<EditorInlineStyle>& aStylesToRemove); + + /** + * Helper method to call RemoveInlinePropertiesAsSubAction(). If you want to + * remove other elements to remove the style completely, this will append + * related elements of aStyleToRemove and aStyleToRemove itself to the array. + * E.g., nsGkAtoms::strong and nsGkAtoms::b will be appended if aStyleToRemove + * is nsGkAtoms::b. + */ + void AppendInlineStyleAndRelatedStyle( + const EditorInlineStyle& aStyleToRemove, + nsTArray<EditorInlineStyle>& aStylesToRemove) const; + + /** + * 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); + + /** + * This sets background on the appropriate container element (table, cell,) + * or calls to set the page background. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + SetBlockBackgroundColorWithCSSAsSubAction(const nsAString& aColor); + MOZ_CAN_RUN_SCRIPT nsresult + SetHTMLBackgroundColorWithTransaction(const nsAString& aColor); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY void InitializeSelectionAncestorLimit( + nsIContent& aAncestorLimit) const final; + + /** + * Make the given selection span the entire document. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SelectEntireDocument() final; + + /** + * 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 { + using AutoEditActionDataSetter = EditorBase::AutoEditActionDataSetter; + + public: + MOZ_CAN_RUN_SCRIPT BlobReader(dom::BlobImpl* aBlob, HTMLEditor* aHTMLEditor, + SafeToInsertData aSafeToInsertData, + const EditorDOMPoint& aPointToInsert, + DeleteSelectedContent aDeleteSelectedContent); + + 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; + EditorDOMPoint mPointToInsert; + EditAction mEditAction; + SafeToInsertData mSafeToInsertData; + DeleteSelectedContent mDeleteSelectedContent; + bool mNeedsToDispatchBeforeInputEvent; + }; + + void CreateEventListeners() final; + nsresult InstallEventListeners() final; + void RemoveEventListeners() final; + + 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; + + already_AddRefed<Element> GetInputEventTargetElement() const final; + + /** + * 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 Result<EditActionResult, nsresult> + 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 at aPointToInsert. + * 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. + * Finally, puts caret into previous cell of the insertion point or the + * first inserted cell if aPointToInsert is start of the row. + * + * @param aPointToInsert The place to insert one or more cell + * elements. The container must be a + * <tr> element. + * @param aNumberOfCellsToInsert Number of cells to insert. + * @return The first inserted cell element and + * start of the last inserted cell element + * as a point to put caret. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> + InsertTableCellsWithTransaction(const EditorDOMPoint& aPointToInsert, + int32_t aNumberOfCellsToInsert); + + /** + * InsertTableColumnsWithTransaction() inserts cell elements to every rows + * at same column index as the cell specified by aPointToInsert. + * + * @param aNumberOfColumnsToInsert Number of columns to insert. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InsertTableColumnsWithTransaction( + const EditorDOMPoint& aPointToInsert, int32_t aNumberOfColumnsToInsert); + + /** + * InsertTableRowsWithTransaction() inserts <tr> elements before or after + * aCellElement. When aCellElement spans rows and aInsertPosition is + * eAfterSelectedCell, new rows will be inserted after the most-bottom row + * which contains the cell. + * + * @param aCellElement The cell element pinting where this will + * insert a row before or after. + * @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( + Element& aCellElement, 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(const 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, + SafeToInsertData aSafeToInsertData, const EditorDOMPoint& aPointToInsert, + DeleteSelectedContent aDeleteSelectedContent); + + class HTMLTransferablePreparer; + nsresult PrepareHTMLTransferable(nsITransferable** aTransferable) const; + + enum class HavePrivateHTMLFlavor { No, Yes }; + MOZ_CAN_RUN_SCRIPT nsresult InsertFromTransferableAtSelection( + nsITransferable* aTransferable, const nsAString& aContextStr, + const nsAString& aInfoStr, HavePrivateHTMLFlavor aHavePrivateHTMLFlavor); + + /** + * 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, uint32_t aIndex, + nsIPrincipal* aSourcePrincipal, const EditorDOMPoint& aDroppedAt, + DeleteSelectedContent aDeleteSelectedContent); + + static HavePrivateHTMLFlavor ClipboardHasPrivateHTMLFlavor( + 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; + }; + + /** + * 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 + IncrementOrDecrementFontSizeAsSubAction(FontSize aIncrementOrDecrement); + + /** + * Wrap aContent in <big> or <small> element and make children of + * <font size=n> wrap with <big> or <small> too. Note that if there is + * opposite element for aIncrementOrDecrement, their children will be just + * unwrapped. + * + * @param aDir Whether increase or decrease the font size of aContent. + * @param aContent The content node whose font size will be changed. + * @return A suggest point to put caret. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + SetFontSizeWithBigOrSmallElement(nsIContent& aContent, + FontSize aIncrementOrDecrement); + + /** + * Adjust font size of font element children recursively with handling + * <big> and <small> elements. + * + * @param aDir Whether increase or decrease the font size of aContent. + * @param aContent The content node whose font size will be changed. + * All descendants will be handled recursively. + * @return A suggest point to put caret. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + SetFontSizeOfFontElementChildren(nsIContent& aContent, + FontSize aIncrementOrDecrement); + + /** + * Get extended range to select element whose all children are selected by + * aRange. + */ + EditorRawDOMRange GetExtendedRangeWrappingEntirelySelectedElements( + const EditorRawDOMRange& aRange) const; + + /** + * Get extended range to select ancestor <a name> elements. + */ + EditorRawDOMRange GetExtendedRangeWrappingNamedAnchor( + const EditorRawDOMRange& aRange) const; + + // Declared in HTMLEditorNestedClasses.h and defined in HTMLStyleEditor.cpp + class AutoInlineStyleSetter; + + /** + * RemoveStyleInside() removes elements which represent aStyleToRemove + * and removes CSS style. This handles aElement and all its descendants + * (including leaf text nodes) recursively. + * TODO: Rename this to explain that this maybe remove aElement from the DOM + * tree. + * + * @param aSpecifiedStyle Whether the class and style attributes should + * be preserved or discarded. + * @return A suggest point to put caret. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + RemoveStyleInside(Element& aElement, const EditorInlineStyle& aStyleToRemove, + SpecifiedStyle aSpecifiedStyle); + + /** + * CollectEditableLeafTextNodes() collects text nodes in aElement. + */ + void CollectEditableLeafTextNodes( + Element& aElement, nsTArray<OwningNonNull<Text>>& aLeafTextNodes) const; + + /** + * IsRemovableParentStyleWithNewSpanElement() checks whether aStyle 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 Result<bool, nsresult> + IsRemovableParentStyleWithNewSpanElement( + nsIContent& aContent, const EditorInlineStyle& aStyle) const; + + /** + * 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. + */ + enum class InlineStylesAtInsertionPoint { + Preserve, // If you want the paste to be affected by local style, e.g., + // for the insertHTML command, use "Preserve" + Clear, // If you want the paste to be keep its own style, e.g., pasting + // from clipboard, use "Clear" + }; + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InsertHTMLWithContextAsSubAction( + const nsAString& aInputString, const nsAString& aContextStr, + const nsAString& aInfoStr, const nsAString& aFlavor, + SafeToInsertData aSafeToInsertData, const EditorDOMPoint& aPointToInsert, + DeleteSelectedContent aDeleteSelectedContent, + InlineStylesAtInsertionPoint aInlineStylesAtInsertionPoint); + + /** + * 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. + */ + [[nodiscard]] 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, + }; + [[nodiscard]] int32_t GetNewResizingIncrement(int32_t aX, int32_t aY, + ResizeAt aResizeAt) const; + + [[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. + */ + [[nodiscard]] 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 SnapToGrid(int32_t& newX, int32_t& newY) const; + 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. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult + RefreshInlineTableEditingUIInternal(); + + enum class ContentNodeIs { Inserted, Appended }; + MOZ_CAN_RUN_SCRIPT void DoContentInserted(nsIContent* aChild, + ContentNodeIs aContentNodeIs); + + /** + * 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 aGlobal 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, nsIGlobalObject* aGlobal, + 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. + */ + [[nodiscard]] inline already_AddRefed<RangeItem> + GetSelectedRangeItemForTopLevelEditSubAction() const; + + /** + * 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. + */ + [[nodiscard]] inline already_AddRefed<nsRange> + GetChangedRangeForTopLevelEditSubAction() const; + + MOZ_CAN_RUN_SCRIPT void DidDoTransaction( + TransactionManager& aTransactionManager, nsITransaction& aTransaction, + nsresult aDoTransactionResult) { + if (mComposerCommandsUpdater) { + RefPtr<ComposerCommandsUpdater> updater(mComposerCommandsUpdater); + updater->DidDoTransaction(aTransactionManager); + } + } + + MOZ_CAN_RUN_SCRIPT void DidUndoTransaction( + TransactionManager& aTransactionManager, nsITransaction& aTransaction, + nsresult aUndoTransactionResult) { + if (mComposerCommandsUpdater) { + RefPtr<ComposerCommandsUpdater> updater(mComposerCommandsUpdater); + updater->DidUndoTransaction(aTransactionManager); + } + } + + MOZ_CAN_RUN_SCRIPT void DidRedoTransaction( + TransactionManager& aTransactionManager, nsITransaction& aTransaction, + nsresult aRedoTransactionResult) { + if (mComposerCommandsUpdater) { + RefPtr<ComposerCommandsUpdater> updater(mComposerCommandsUpdater); + updater->DidRedoTransaction(aTransactionManager); + } + } + + protected: + /** + * IndentListChildWithTransaction() is a helper method of + * Handle(CSS|HTML)IndentAtSelectionInternal(). + * + * @param aSubListElement [in/out] Specify a sub-list element of the + * container of aPointInListElement or nullptr. + * When there is no proper sub-list element to + * move aContentMovingToSubList, this method + * inserts a new sub-list element and update this + * to it. + * @param aPointInListElement A point in a list element whose child should + * be indented. If this method creates new list + * element into the list element, this inserts + * the new list element to this point. + * @param aContentMovingToSubList + * A content node which is a child of a list + * element and should be moved into a sub-list + * element. + * @param aEditingHost The editing host. + * @return A candidate caret position. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + IndentListChildWithTransaction(RefPtr<Element>* aSubListElement, + const EditorDOMPoint& aPointInListElement, + nsIContent& aContentMovingToSubList, + const Element& aEditingHost); + + /** + * Stack based helper class for saving/restoring selection. Note that this + * assumes that the nodes involved are still around afterwords! + */ + class AutoSelectionRestorer final { + public: + AutoSelectionRestorer() = delete; + explicit AutoSelectionRestorer(const AutoSelectionRestorer& aOther) = + delete; + AutoSelectionRestorer(AutoSelectionRestorer&& aOther) = delete; + + /** + * Constructor responsible for remembering all state needed to restore + * aSelection. + * XXX This constructor and the destructor should be marked as + * `MOZ_CAN_RUN_SCRIPT`, but it's impossible due to this may be used + * with `Maybe`. + */ + MOZ_CAN_RUN_SCRIPT_BOUNDARY explicit AutoSelectionRestorer( + HTMLEditor& aHTMLEditor); + + /** + * Destructor restores mSelection to its former state + */ + MOZ_CAN_RUN_SCRIPT_BOUNDARY ~AutoSelectionRestorer(); + + /** + * Abort() cancels to restore the selection. + */ + void Abort(); + + bool MaybeRestoreSelectionLater() const { return !!mHTMLEditor; } + + protected: + // The lifetime must be guaranteed by the creator of this instance. + MOZ_KNOWN_LIVE HTMLEditor* mHTMLEditor = nullptr; + }; + + /** + * Stack based helper class for calling EditorBase::EndTransactionInternal(). + * NOTE: This does not suppress multiple input events. In most cases, + * only one "input" event should be fired for an edit action rather + * than per edit sub-action. In such case, you should use + * EditorBase::AutoPlaceholderBatch instead. + */ + class MOZ_RAII AutoTransactionBatch final { + public: + /** + * @param aRequesterFuncName function name which wants to end the batch. + * This won't be stored nor exposed to selection listeners etc, used only + * for logging. This MUST be alive when the destructor runs. + */ + MOZ_CAN_RUN_SCRIPT explicit AutoTransactionBatch( + HTMLEditor& aHTMLEditor, const char* aRequesterFuncName) + : mHTMLEditor(aHTMLEditor), mRequesterFuncName(aRequesterFuncName) { + MOZ_KnownLive(mHTMLEditor).BeginTransactionInternal(mRequesterFuncName); + } + + MOZ_CAN_RUN_SCRIPT ~AutoTransactionBatch() { + MOZ_KnownLive(mHTMLEditor).EndTransactionInternal(mRequesterFuncName); + } + + protected: + // The lifetime must be guaranteed by the creator of this instance. + MOZ_KNOWN_LIVE HTMLEditor& mHTMLEditor; + const char* const mRequesterFuncName; + }; + + RefPtr<PendingStyles> mPendingStylesToApplyToNewContent; + 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; + + // mPaddingBRElementForEmptyEditor should be used for placing caret + // at proper position when editor is empty. + RefPtr<dom::HTMLBRElement> mPaddingBRElementForEmptyEditor; + + bool mCRInParagraphCreatesParagraph; + + // Whether use Blink/WebKit compatible joining nodes and split a node + // direction or Gecko's traditional direction. + bool mUseGeckoTraditionalJoinSplitBehavior; + + // 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; + + bool mIsCSSPrefChecked; + + // 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; + + // Set to true once the instance creates a JoinNodesTransaction or + // SplitNodeTransaction. See also CanChangeJoinSplitNodeDirection(). + bool mMaybeHasJoinSplitTransactions = false; + + ParagraphSeparator mDefaultParagraphSeparator; + + friend class AlignStateAtSelection; // CollectEditableTargetNodes, + // CollectNonEditableNodes + friend class AutoRangeArray; // RangeUpdaterRef, SplitNodeWithTransaction, + // SplitParentInlineElementsAtRangeBoundaries + friend class AutoSelectionSetterAfterTableEdit; // SetSelectionAfterEdit + friend class + AutoSetTemporaryAncestorLimiter; // InitializeSelectionAncestorLimit + friend class CSSEditUtils; // DoTransactionInternal, HasAttributes, + // RemoveContainerWithTransaction + friend class EditorBase; // ComputeTargetRanges, + // GetChangedRangeForTopLevelEditSubAction, + // GetSelectedRangeItemForTopLevelEditSubAction, + // MaybeCreatePaddingBRElementForEmptyEditor, + // PrepareToInsertBRElement, + // ReflectPaddingBRElementForEmptyEditor, + // RefreshEditingUI, + // RemoveEmptyInclusiveAncestorInlineElements, + // mComposerUpdater, mHasBeforeInputBeenCanceled + friend class JoinNodesTransaction; // DidJoinNodesTransaction, DoJoinNodes, + // DoSplitNode, GetJoinNodesDirection, + // RangeUpdaterRef + friend class ListElementSelectionState; // CollectEditTargetNodes, + // CollectNonEditableNodes + friend class ListItemElementSelectionState; // CollectEditTargetNodes, + // CollectNonEditableNodes + friend class MoveNodeTransaction; // AllowsTransactionsToChangeSelection, + // CollapseSelectionTo, MarkElementDirty, + // RangeUpdaterRef + friend class ParagraphStateAtSelection; // CollectChildren, + // CollectEditTargetNodes, + // CollectListChildren, + // CollectNonEditableNodes, + // CollectTableChildren + friend class SlurpBlobEventListener; // BlobReader + friend class SplitNodeTransaction; // DoJoinNodes, DoSplitNode, + // GetSplitNodeDirection + friend class TransactionManager; // DidDoTransaction, DidRedoTransaction, + // DidUndoTransaction + friend class + WhiteSpaceVisibilityKeeper; // AutoMoveOneLineHandler + // CanMoveChildren, + // ChangeListElementType, + // DeleteNodeWithTransaction, + // DeleteTextAndTextNodesWithTransaction, + // JoinNearestEditableNodesWithTransaction, + // MoveChildrenWithTransaction, + // SplitAncestorStyledInlineElementsAt, + // TreatEmptyTextNodes +}; + +/** + * 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 + * `AutoRangeArray::ExtendRangesToWrapLinesToHandleBlockLevelEditAction()` and + * `HTMLEditor::CollectEditTargetNodes()`, 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, const dom::Element& aEditingHost, + nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents); + + RefPtr<nsAtom> mFirstParagraphState; + bool mIsMixed = false; +}; + +} // namespace mozilla + +mozilla::HTMLEditor* nsIEditor::AsHTMLEditor() { + MOZ_DIAGNOSTIC_ASSERT(IsHTMLEditor()); + return static_cast<mozilla::HTMLEditor*>(this); +} + +const mozilla::HTMLEditor* nsIEditor::AsHTMLEditor() const { + MOZ_DIAGNOSTIC_ASSERT(IsHTMLEditor()); + return static_cast<const mozilla::HTMLEditor*>(this); +} + +mozilla::HTMLEditor* nsIEditor::GetAsHTMLEditor() { + return AsEditorBase()->IsHTMLEditor() ? AsHTMLEditor() : nullptr; +} + +const mozilla::HTMLEditor* nsIEditor::GetAsHTMLEditor() const { + return AsEditorBase()->IsHTMLEditor() ? AsHTMLEditor() : nullptr; +} + +#endif // #ifndef mozilla_HTMLEditor_h |