diff --git a/editor/libeditor/HTMLEditRules.cpp b/editor/libeditor/HTMLEditRules.cpp index cf1b56c61447..ab9d06929fd3 100644 --- a/editor/libeditor/HTMLEditRules.cpp +++ b/editor/libeditor/HTMLEditRules.cpp @@ -700,6 +700,52 @@ nsresult HTMLEditRules::AfterEditInner() { return NS_OK; } +EditActionResult HTMLEditor::CanHandleHTMLEditSubAction() const { + MOZ_ASSERT(IsEditActionDataAvailable()); + + if (NS_WARN_IF(Destroyed())) { + return EditActionResult(NS_ERROR_EDITOR_DESTROYED); + } + + // If there is not selection ranges, we should ignore the result. + if (!SelectionRefPtr()->RangeCount()) { + return EditActionCanceled(); + } + + nsRange* range = SelectionRefPtr()->GetRangeAt(0); + nsINode* selStartNode = range->GetStartContainer(); + if (NS_WARN_IF(!selStartNode)) { + return EditActionResult(NS_ERROR_FAILURE); + } + + if (!IsModifiableNode(*selStartNode)) { + return EditActionCanceled(); + } + + nsINode* selEndNode = range->GetEndContainer(); + if (NS_WARN_IF(!selEndNode)) { + return EditActionResult(NS_ERROR_FAILURE); + } + + if (selStartNode == selEndNode) { + return EditActionIgnored(); + } + + if (!IsModifiableNode(*selEndNode)) { + return EditActionCanceled(); + } + + nsINode* commonAncestor = range->GetCommonAncestor(); + if (NS_WARN_IF(!commonAncestor)) { + return EditActionResult(NS_ERROR_FAILURE); + } + if (!IsModifiableNode(*commonAncestor)) { + return EditActionCanceled(); + } + + return EditActionIgnored(); +} + nsresult HTMLEditRules::WillDoAction(EditSubActionInfo& aInfo, bool* aCancel, bool* aHandled) { if (NS_WARN_IF(!CanHandleEditAction())) { @@ -720,43 +766,20 @@ nsresult HTMLEditRules::WillDoAction(EditSubActionInfo& aInfo, bool* aCancel, AutoSafeEditorData setData(*this, *mHTMLEditor); - // Nothing to do if there's no selection to act on - if (NS_WARN_IF(!SelectionRefPtr()->RangeCount())) { - return NS_OK; + EditActionResult result = HTMLEditorRef().CanHandleHTMLEditSubAction(); + if (NS_WARN_IF(result.Failed())) { + return result.Rv(); } - - RefPtr range = SelectionRefPtr()->GetRangeAt(0); - nsCOMPtr selStartNode = range->GetStartContainer(); - if (NS_WARN_IF(!selStartNode)) { - return NS_ERROR_FAILURE; - } - - if (!HTMLEditorRef().IsModifiableNode(*selStartNode)) { + if (result.Canceled()) { + if (!SelectionRefPtr()->RangeCount()) { + // Special case. If there is no selection range, we do nothing here, + // but return as not handled nor canceled. + return NS_OK; + } *aCancel = true; return NS_OK; } - nsCOMPtr selEndNode = range->GetEndContainer(); - if (NS_WARN_IF(!selEndNode)) { - return NS_ERROR_FAILURE; - } - - if (selStartNode != selEndNode) { - if (!HTMLEditorRef().IsModifiableNode(*selEndNode)) { - *aCancel = true; - return NS_OK; - } - - nsINode* commonAncestor = range->GetCommonAncestor(); - if (NS_WARN_IF(!commonAncestor)) { - return NS_ERROR_FAILURE; - } - if (!HTMLEditorRef().IsModifiableNode(*commonAncestor)) { - *aCancel = true; - return NS_OK; - } - } - switch (aInfo.mEditSubAction) { case EditSubAction::eInsertText: case EditSubAction::eInsertTextComingFromIME: @@ -791,8 +814,6 @@ nsresult HTMLEditRules::WillDoAction(EditSubActionInfo& aInfo, bool* aCancel, return WillRemoveAbsolutePosition(aCancel, aHandled); case EditSubAction::eSetOrClearAlignment: return WillAlign(*aInfo.alignType, aCancel, aHandled); - case EditSubAction::eCreateOrRemoveBlock: - return WillMakeBasicBlock(*aInfo.blockType, aCancel, aHandled); case EditSubAction::eRemoveList: { nsresult rv = WillRemoveList(aCancel, aHandled); if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED) || @@ -820,6 +841,7 @@ nsresult HTMLEditRules::WillDoAction(EditSubActionInfo& aInfo, bool* aCancel, return WillRelativeChangeZIndex(-1, aCancel, aHandled); case EditSubAction::eIncreaseZIndex: return WillRelativeChangeZIndex(1, aCancel, aHandled); + case EditSubAction::eCreateOrRemoveBlock: case EditSubAction::eInsertHTMLSource: case EditSubAction::eUndo: case EditSubAction::eRedo: @@ -1848,11 +1870,11 @@ EditActionResult HTMLEditRules::WillInsertParagraphSeparator() { // Insert a new block first MOZ_ASSERT(separator == ParagraphSeparator::div || separator == ParagraphSeparator::p); - // FormatBlockContainer() creates AutoSelectionRestorer. + // FormatBlockContainerWithTransaction() creates AutoSelectionRestorer. // Therefore, even if it returns NS_OK, editor might have been destroyed // at restoring Selection. nsresult rv = MOZ_KnownLive(HTMLEditorRef()) - .FormatBlockContainer( + .FormatBlockContainerWithTransaction( MOZ_KnownLive(ParagraphSeparatorElement(separator))); if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED) || NS_WARN_IF(!CanHandleEditAction())) { @@ -1860,8 +1882,9 @@ EditActionResult HTMLEditRules::WillInsertParagraphSeparator() { } // We warn on failure, but don't handle it, because it might be harmless. // Instead we just check that a new block was actually created. - NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), - "HTMLEditor::FormatBlockContainer() failed"); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "HTMLEditor::FormatBlockContainerWithTransaction() failed"); firstRange = SelectionRefPtr()->GetRangeAt(0); if (NS_WARN_IF(!firstRange)) { @@ -1890,10 +1913,10 @@ EditActionResult HTMLEditRules::WillInsertParagraphSeparator() { // Now, mNewBlockElement is last created block element for wrapping inline // elements around the caret position and AfterEditInner() will move // caret into it. However, it may be different from block parent of - // the caret position. E.g., FormatBlockContainer() may wrap following - // inline elements of a
element which is next sibling of container - // of the caret. So, we need to adjust mNewBlockElement here for avoiding - // jumping caret to odd position. + // the caret position. E.g., FormatBlockContainerWithTransaction() may + // wrap following inline elements of a
element which is next sibling + // of container of the caret. So, we need to adjust mNewBlockElement here + // for avoiding jumping caret to odd position. HTMLEditorRef().TopLevelEditSubActionDataRef().mNewBlockElement = blockParent; } @@ -4535,37 +4558,7 @@ nsresult HTMLEditRules::WillMakeDefListItem(const nsAString* aItemType, return NS_OK; } -nsresult HTMLEditRules::WillMakeBasicBlock(const nsAString& aBlockType, - bool* aCancel, bool* aHandled) { - MOZ_ASSERT(IsEditorDataAvailable()); - MOZ_ASSERT(aCancel && aHandled); - - OwningNonNull blockType = NS_Atomize(aBlockType); - - // FYI: Ignore cancel result of WillInsert(). - nsresult rv = MOZ_KnownLive(HTMLEditorRef()).WillInsert(); - if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { - return NS_ERROR_EDITOR_DESTROYED; - } - NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed"); - - *aCancel = false; - *aHandled = true; - - // FormatBlockContainer() creates AutoSelectionRestorer. - // Therefore, even if it returns NS_OK, editor might have been destroyed - // at restoring Selection. - rv = MOZ_KnownLive(HTMLEditorRef()).FormatBlockContainer(blockType); - if (NS_WARN_IF(!CanHandleEditAction())) { - return NS_ERROR_EDITOR_DESTROYED; - } - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - return NS_OK; -} - -nsresult HTMLEditor::FormatBlockContainer(nsAtom& blockType) { +nsresult HTMLEditor::FormatBlockContainerWithTransaction(nsAtom& blockType) { MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); if (!SelectionRefPtr()->IsCollapsed()) { diff --git a/editor/libeditor/HTMLEditRules.h b/editor/libeditor/HTMLEditRules.h index 781bac3e113f..0c0ddafdb0d1 100644 --- a/editor/libeditor/HTMLEditRules.h +++ b/editor/libeditor/HTMLEditRules.h @@ -366,18 +366,6 @@ class HTMLEditRules : public TextEditRules { bool aEntireList, bool* aCancel, bool* aHandled); - /** - * WillMakeBasicBlock() called before changing block style around Selection. - * This method actually does something with calling FormatBlockContainer(). - * - * @param aBlockType Necessary block style as string. - * @param aCancel Returns true if the operation is canceled. - * @param aHandled Returns true if the edit action is handled. - */ - MOZ_CAN_RUN_SCRIPT - MOZ_MUST_USE nsresult WillMakeBasicBlock(const nsAString& aBlockType, - bool* aCancel, bool* aHandled); - /** * Called after creating a basic block, indenting, outdenting or aligning * contents. This method inserts a padding
element for empty last line diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp index 75a63440a1b5..ea44054ea6f3 100644 --- a/editor/libeditor/HTMLEditor.cpp +++ b/editor/libeditor/HTMLEditor.cpp @@ -1798,9 +1798,9 @@ nsresult HTMLEditor::SetParagraphFormatAsAction( "MakeDefinitionListItemWithTransaction() failed"); return EditorBase::ToGenericNSResult(rv); } - nsresult rv = InsertBasicBlockWithTransaction(*tagName); + nsresult rv = FormatBlockContainerAsSubAction(*tagName); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), - "InsertBasicBlockWithTransaction() failed"); + "FormatBlockContainerAsSubAction() failed"); return EditorBase::ToGenericNSResult(rv); } @@ -2264,7 +2264,7 @@ nsresult HTMLEditor::MakeDefinitionListItemWithTransaction(nsAtom& aTagName) { return NS_OK; } -nsresult HTMLEditor::InsertBasicBlockWithTransaction(nsAtom& aTagName) { +nsresult HTMLEditor::FormatBlockContainerAsSubAction(nsAtom& aTagName) { MOZ_ASSERT(IsEditActionDataAvailable()); if (!mRules) { @@ -2276,75 +2276,37 @@ nsresult HTMLEditor::InsertBasicBlockWithTransaction(nsAtom& aTagName) { // Protect the edit rules object from dying RefPtr rules(mRules); - bool cancel, handled; - AutoPlaceholderBatch treatAsOneTransaction(*this); AutoEditSubActionNotifier startToHandleEditSubAction( *this, EditSubAction::eCreateOrRemoveBlock, nsIEditor::eNext); - nsDependentAtomString tagName(&aTagName); - EditSubActionInfo subActionInfo(EditSubAction::eCreateOrRemoveBlock); - subActionInfo.blockType = &tagName; - nsresult rv = rules->WillDoAction(subActionInfo, &cancel, &handled); - if (cancel || NS_FAILED(rv)) { + EditActionResult result = CanHandleHTMLEditSubAction(); + if (result.Canceled() || NS_WARN_IF(result.Failed())) { + return result.Rv(); + } + + // FYI: Ignore cancel result of WillInsert() since we've already checked + // whether we can or cannot edit current selection range. + nsresult rv = WillInsert(); + if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed, but ignored"); + + // FormatBlockContainerWithTransaction() creates AutoSelectionRestorer. + // Therefore, even if it returns NS_OK, editor might have been destroyed + // at restoring Selection. + rv = FormatBlockContainerWithTransaction(aTagName); + if (NS_WARN_IF(Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - if (!handled && SelectionRefPtr()->IsCollapsed()) { - nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0); - if (NS_WARN_IF(!firstRange)) { - return NS_ERROR_FAILURE; - } - - EditorDOMPoint atStartOfSelection(firstRange->StartRef()); - if (NS_WARN_IF(!atStartOfSelection.IsSet()) || - NS_WARN_IF(!atStartOfSelection.GetContainerAsContent())) { - return NS_ERROR_FAILURE; - } - - // Have to find a place to put the block. - EditorDOMPoint pointToInsertBlock(atStartOfSelection); - - while (!CanContainTag(*pointToInsertBlock.GetContainer(), aTagName)) { - pointToInsertBlock.Set(pointToInsertBlock.GetContainer()); - if (NS_WARN_IF(!pointToInsertBlock.IsSet()) || - NS_WARN_IF(!pointToInsertBlock.GetContainerAsContent())) { - return NS_ERROR_FAILURE; - } - } - - if (pointToInsertBlock.GetContainer() != - atStartOfSelection.GetContainer()) { - // We need to split up to the child of the point to insert a block. - SplitNodeResult splitBlockResult = SplitNodeDeepWithTransaction( - MOZ_KnownLive(*pointToInsertBlock.GetChild()), atStartOfSelection, - SplitAtEdges::eAllowToCreateEmptyContainer); - if (NS_WARN_IF(splitBlockResult.Failed())) { - return splitBlockResult.Rv(); - } - pointToInsertBlock = splitBlockResult.SplitPoint(); - if (NS_WARN_IF(!pointToInsertBlock.IsSet())) { - return NS_ERROR_FAILURE; - } - } - - // Create a block and insert it before the right node if we split some - // parents of start of selection above, or just start of selection - // otherwise. - RefPtr newBlock = - CreateNodeWithTransaction(aTagName, pointToInsertBlock); - if (NS_WARN_IF(!newBlock)) { - return NS_ERROR_FAILURE; - } - - // reposition selection to inside the block - ErrorResult error; - SelectionRefPtr()->Collapse(RawRangeBoundary(newBlock, 0), error); - if (NS_WARN_IF(error.Failed())) { - return error.StealNSResult(); - } - } - + nsDependentAtomString tagName(&aTagName); + EditSubActionInfo subActionInfo(EditSubAction::eCreateOrRemoveBlock); + subActionInfo.blockType = &tagName; rv = rules->DidDoAction(subActionInfo, rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; diff --git a/editor/libeditor/HTMLEditor.h b/editor/libeditor/HTMLEditor.h index 5cdc89ea26cf..01087be45b44 100644 --- a/editor/libeditor/HTMLEditor.h +++ b/editor/libeditor/HTMLEditor.h @@ -1108,6 +1108,17 @@ class HTMLEditor final : public TextEditor, MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult OnModifyDocument(); protected: // edit sub-action handler + /** + * CanHandleHTMLEditSubAction() checks whether there is at least one + * selection range or not, and whether the first range is editable. + * If it's not editable, `Canceled()` of the result returns true. + * If `Selection` is in odd situation, returns an error. + * + * XXX I think that `IsSelectionEditable()` is better name, but it's already + * in `EditorBase`... + */ + EditActionResult CanHandleHTMLEditSubAction() const; + /** * Called before inserting something into the editor. * This method may removes mPaddingBRElementForEmptyEditor if there is. @@ -1696,8 +1707,9 @@ class HTMLEditor final : public TextEditor, nsTArray>& aNodeArray, nsAtom& aBlockTag); /** - * FormatBlockContainer() is implementation of "formatBlock" command of - * `Document.execCommand()`. This applies block style or removes it. + * FormatBlockContainerWithTransaction() is implementation of "formatBlock" + * command of `Document.execCommand()`. This applies block style or removes + * it. * NOTE: This creates AutoSelectionRestorer. Therefore, even when this * return NS_OK, editor may have been destroyed. * @@ -1711,7 +1723,7 @@ class HTMLEditor final : public TextEditor, * called. */ MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult - FormatBlockContainer(nsAtom& aBlockType); + FormatBlockContainerWithTransaction(nsAtom& aBlockType); /** * InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() determines if @@ -2855,14 +2867,14 @@ class HTMLEditor final : public TextEditor, nsresult MakeDefinitionListItemWithTransaction(nsAtom& aTagName); /** - * InsertBasicBlockWithTransaction() inserts a block element whose name - * is aTagName at selection. + * 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 InsertBasicBlockWithTransaction(nsAtom& aTagName); + MOZ_CAN_RUN_SCRIPT nsresult FormatBlockContainerAsSubAction(nsAtom& aTagName); /** * Increase/decrease the font size of selection.