diff options
Diffstat (limited to 'editor/libeditor')
34 files changed, 925 insertions, 626 deletions
diff --git a/editor/libeditor/AutoRangeArray.cpp b/editor/libeditor/AutoRangeArray.cpp index be89671760..57b9b26a35 100644 --- a/editor/libeditor/AutoRangeArray.cpp +++ b/editor/libeditor/AutoRangeArray.cpp @@ -86,6 +86,15 @@ AutoRangeArray::~AutoRangeArray() { } } +AutoRangeArray::AutoRangeArray(nsRange& aRange) { + MOZ_ASSERT(aRange.IsPositioned()); + if (NS_WARN_IF(!aRange.IsPositioned())) { + return; + } + mRanges.AppendElement(aRange); + mAnchorFocusRange = &aRange; +} + // static bool AutoRangeArray::IsEditableRange(const dom::AbstractRange& aRange, const Element& aEditingHost) { diff --git a/editor/libeditor/AutoRangeArray.h b/editor/libeditor/AutoRangeArray.h index e2dd2e0149..3f3c4bf5b6 100644 --- a/editor/libeditor/AutoRangeArray.h +++ b/editor/libeditor/AutoRangeArray.h @@ -43,6 +43,7 @@ class MOZ_STACK_CLASS AutoRangeArray final { explicit AutoRangeArray(const EditorDOMRangeBase<PointType>& aRange); template <typename PT, typename CT> explicit AutoRangeArray(const EditorDOMPointBase<PT, CT>& aPoint); + explicit AutoRangeArray(nsRange& aRange); // The copy constructor copies everything except saved ranges. explicit AutoRangeArray(const AutoRangeArray& aOther); diff --git a/editor/libeditor/EditorBase.cpp b/editor/libeditor/EditorBase.cpp index 22452c59d2..e8120d93d9 100644 --- a/editor/libeditor/EditorBase.cpp +++ b/editor/libeditor/EditorBase.cpp @@ -4869,6 +4869,29 @@ nsresult EditorBase::DeleteSelectionWithTransaction( return NS_OK; } +Result<CaretPoint, nsresult> EditorBase::DeleteRangeWithTransaction( + nsIEditor::EDirection aDirectionAndAmount, + nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete) { + MOZ_ASSERT(IsEditActionDataAvailable()); + MOZ_ASSERT(!Destroyed()); + MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); + + HowToHandleCollapsedRange howToHandleCollapsedRange = + EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount); + if (MOZ_UNLIKELY(aRangeToDelete.Collapsed() && + howToHandleCollapsedRange == + HowToHandleCollapsedRange::Ignore)) { + return CaretPoint(EditorDOMPoint(aRangeToDelete.StartRef())); + } + + AutoRangeArray rangesToDelete(aRangeToDelete); + Result<CaretPoint, nsresult> result = DeleteRangesWithTransaction( + aDirectionAndAmount, aStripWrappers, rangesToDelete); + NS_WARNING_ASSERTION(result.isOk(), + "EditorBase::DeleteRangesWithTransaction() failed"); + return result; +} + Result<CaretPoint, nsresult> EditorBase::DeleteRangesWithTransaction( nsIEditor::EDirection aDirectionAndAmount, nsIEditor::EStripWrappers aStripWrappers, diff --git a/editor/libeditor/EditorBase.h b/editor/libeditor/EditorBase.h index 952b8aa3f6..83f53ff215 100644 --- a/editor/libeditor/EditorBase.h +++ b/editor/libeditor/EditorBase.h @@ -2519,6 +2519,25 @@ class EditorBase : public nsIEditor, nsIEditor::EStripWrappers aStripWrappers); /** + * DeleteRangeWithTransaction() removes content in aRangeToDelete or content + * around collapsed aRangeToDelete with transactions and remove empty + * inclusive ancestor inline elements of the collapsed range after removing + * the contents. + * + * @param aDirectionAndAmount How much range should be removed. + * @param aStripWrappers Whether the parent blocks should be removed + * when they become empty. + * Note that this must be `nsIEditor::eNoStrip` + * if this is a TextEditor because anyway it'll + * be ignored. + * @param aRangeToDelete The range to delete content. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> + DeleteRangeWithTransaction(nsIEditor::EDirection aDirectionAndAmount, + nsIEditor::EStripWrappers aStripWrappers, + nsRange& aRangeToDelete); + + /** * DeleteRangesWithTransaction() removes content in aRangesToDelete or content * around collapsed ranges in aRangesToDelete with transactions and remove * empty inclusive ancestor inline elements of collapsed ranges after diff --git a/editor/libeditor/EditorCommands.cpp b/editor/libeditor/EditorCommands.cpp index beb4f060ad..45f9e3198f 100644 --- a/editor/libeditor/EditorCommands.cpp +++ b/editor/libeditor/EditorCommands.cpp @@ -269,7 +269,8 @@ bool UndoCommand::IsCommandEnabled(Command aCommand, if (!aEditorBase) { return false; } - return aEditorBase->IsSelectionEditable() && aEditorBase->CanUndo(); + return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable() && + aEditorBase->CanUndo(); } nsresult UndoCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, @@ -297,7 +298,8 @@ bool RedoCommand::IsCommandEnabled(Command aCommand, if (!aEditorBase) { return false; } - return aEditorBase->IsSelectionEditable() && aEditorBase->CanRedo(); + return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable() && + aEditorBase->CanRedo(); } nsresult RedoCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, @@ -548,7 +550,7 @@ bool SwitchTextDirectionCommand::IsCommandEnabled( if (!aEditorBase) { return false; } - return aEditorBase->IsSelectionEditable(); + return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable(); } nsresult SwitchTextDirectionCommand::DoCommand(Command aCommand, @@ -581,7 +583,8 @@ bool DeleteCommand::IsCommandEnabled(Command aCommand, // We can generally delete whenever the selection is editable. However, // cmd_delete doesn't make sense if the selection is collapsed because it's // directionless. - bool isEnabled = aEditorBase->IsSelectionEditable(); + bool isEnabled = + aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable(); if (aCommand == Command::Delete && isEnabled) { return aEditorBase->CanDeleteSelection(); @@ -820,7 +823,7 @@ bool InsertPlaintextCommand::IsCommandEnabled(Command aCommand, if (!aEditorBase) { return false; } - return aEditorBase->IsSelectionEditable(); + return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable(); } nsresult InsertPlaintextCommand::DoCommand(Command aCommand, @@ -877,7 +880,7 @@ bool InsertParagraphCommand::IsCommandEnabled(Command aCommand, if (!aEditorBase || aEditorBase->IsSingleLineEditor()) { return false; } - return aEditorBase->IsSelectionEditable(); + return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable(); } nsresult InsertParagraphCommand::DoCommand(Command aCommand, @@ -918,7 +921,7 @@ bool InsertLineBreakCommand::IsCommandEnabled(Command aCommand, if (!aEditorBase || aEditorBase->IsSingleLineEditor()) { return false; } - return aEditorBase->IsSelectionEditable(); + return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable(); } nsresult InsertLineBreakCommand::DoCommand(Command aCommand, diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp index 9f4aab3dab..e9ea8887b7 100644 --- a/editor/libeditor/HTMLEditor.cpp +++ b/editor/libeditor/HTMLEditor.cpp @@ -719,14 +719,33 @@ void HTMLEditor::UpdateRootElement() { nsresult HTMLEditor::FocusedElementOrDocumentBecomesEditable( Document& aDocument, Element* aElement) { + const bool isInDesignMode = + (IsInDesignMode() && (!aElement || aElement->IsInDesignMode())); + // If we should've already handled focus event, selection limiter should not - // be set. Therefore, if it's set, we should do nothing here. + // be set. However, IMEStateManager is not notified the pseudo focus change + // in this case. Therefore, we need to notify IMEStateManager of this. if (GetSelectionAncestorLimiter()) { + if (isInDesignMode) { + return NS_OK; + } + // Although editor is already initialized due to re-used, ISM may not + // create IME content observer yet. So we have to create it. + IMEState newState; + nsresult rv = GetPreferredIMEState(&newState); + if (NS_FAILED(rv)) { + NS_WARNING("EditorBase::GetPreferredIMEState() failed"); + return NS_OK; + } + if (const RefPtr<Element> focusedElement = GetFocusedElement()) { + MOZ_ASSERT(focusedElement == aElement); + IMEStateManager::UpdateIMEState(newState, focusedElement, *this); + } return NS_OK; } // If we should be in the design mode, we want to handle focus event fired // on the document node. Therefore, we should emulate it here. - if (IsInDesignMode() && (!aElement || aElement->IsInDesignMode())) { + if (isInDesignMode) { MOZ_ASSERT(&aDocument == GetDocument()); nsresult rv = OnFocus(aDocument); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::OnFocus() failed"); @@ -5949,14 +5968,23 @@ bool HTMLEditor::IsEmpty() const { return true; } - // XXX Oddly, we check body or document element's state instead of - // active editing host. Must be a bug. - Element* bodyOrDocumentElement = GetRoot(); - if (!bodyOrDocumentElement) { - return true; + const Element* activeElement = + GetDocument() ? GetDocument()->GetActiveElement() : nullptr; + const Element* editingHostOrBodyOrRootElement = + activeElement && activeElement->IsEditable() + ? ComputeEditingHost(*activeElement, LimitInBodyElement::No) + : ComputeEditingHost(LimitInBodyElement::No); + if (MOZ_UNLIKELY(!editingHostOrBodyOrRootElement)) { + // If there is no active element nor no selection range in the document, + // let's check entire the document as what we do traditionally. + editingHostOrBodyOrRootElement = GetRoot(); + if (!editingHostOrBodyOrRootElement) { + return true; + } } - for (nsIContent* childContent = bodyOrDocumentElement->GetFirstChild(); + for (nsIContent* childContent = + editingHostOrBodyOrRootElement->GetFirstChild(); childContent; childContent = childContent->GetNextSibling()) { if (!childContent->IsText() || childContent->Length()) { return false; diff --git a/editor/libeditor/HTMLEditorCommands.cpp b/editor/libeditor/HTMLEditorCommands.cpp index 1864299a3a..ecccc7c491 100644 --- a/editor/libeditor/HTMLEditorCommands.cpp +++ b/editor/libeditor/HTMLEditorCommands.cpp @@ -52,7 +52,7 @@ bool StateUpdatingCommandBase::IsCommandEnabled(Command aCommand, if (!htmlEditor) { return false; } - if (!htmlEditor->IsSelectionEditable()) { + if (!htmlEditor->IsModifiable() || !htmlEditor->IsSelectionEditable()) { return false; } if (aCommand == Command::FormatAbsolutePosition) { @@ -355,8 +355,7 @@ bool RemoveListCommand::IsCommandEnabled(Command aCommand, if (!htmlEditor) { return false; } - - if (!htmlEditor->IsSelectionEditable()) { + if (!htmlEditor->IsModifiable() || !htmlEditor->IsSelectionEditable()) { return false; } @@ -401,7 +400,7 @@ bool IndentCommand::IsCommandEnabled(Command aCommand, if (!htmlEditor) { return false; } - return htmlEditor->IsSelectionEditable(); + return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable(); } nsresult IndentCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, @@ -434,7 +433,7 @@ bool OutdentCommand::IsCommandEnabled(Command aCommand, if (!htmlEditor) { return false; } - return htmlEditor->IsSelectionEditable(); + return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable(); } nsresult OutdentCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, @@ -467,7 +466,7 @@ bool MultiStateCommandBase::IsCommandEnabled(Command aCommand, return false; } // should be disabled sometimes, like if the current selection is an image - return htmlEditor->IsSelectionEditable(); + return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable(); } nsresult MultiStateCommandBase::DoCommand(Command aCommand, @@ -1047,7 +1046,7 @@ bool RemoveStylesCommand::IsCommandEnabled(Command aCommand, return false; } // test if we have any styles? - return htmlEditor->IsSelectionEditable(); + return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable(); } nsresult RemoveStylesCommand::DoCommand(Command aCommand, @@ -1085,7 +1084,7 @@ bool IncreaseFontSizeCommand::IsCommandEnabled(Command aCommand, return false; } // test if we are at max size? - return htmlEditor->IsSelectionEditable(); + return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable(); } nsresult IncreaseFontSizeCommand::DoCommand(Command aCommand, @@ -1121,7 +1120,7 @@ bool DecreaseFontSizeCommand::IsCommandEnabled(Command aCommand, return false; } // test if we are at min size? - return htmlEditor->IsSelectionEditable(); + return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable(); } nsresult DecreaseFontSizeCommand::DoCommand(Command aCommand, @@ -1156,7 +1155,7 @@ bool InsertHTMLCommand::IsCommandEnabled(Command aCommand, if (!htmlEditor) { return false; } - return htmlEditor->IsSelectionEditable(); + return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable(); } nsresult InsertHTMLCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, @@ -1213,7 +1212,7 @@ bool InsertTagCommand::IsCommandEnabled(Command aCommand, if (!htmlEditor) { return false; } - return htmlEditor->IsSelectionEditable(); + return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable(); } // corresponding STATE_ATTRIBUTE is: src (img) and href (a) diff --git a/editor/libeditor/HTMLEditorDeleteHandler.cpp b/editor/libeditor/HTMLEditorDeleteHandler.cpp index 18f9eda88e..39bb95151e 100644 --- a/editor/libeditor/HTMLEditorDeleteHandler.cpp +++ b/editor/libeditor/HTMLEditorDeleteHandler.cpp @@ -152,9 +152,20 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { const Element& aEditingHost); private: - bool IsHandlingRecursively() const { return mParent != nullptr; } + [[nodiscard]] bool IsHandlingRecursively() const { + return mParent != nullptr; + } - bool CanFallbackToDeleteRangesWithTransaction( + [[nodiscard]] bool CanFallbackToDeleteRangeWithTransaction( + const nsRange& aRangeToDelete) const { + return !IsHandlingRecursively() && + (!aRangeToDelete.Collapsed() || + EditorBase::HowToHandleCollapsedRangeFor( + mOriginalDirectionAndAmount) != + EditorBase::HowToHandleCollapsedRange::Ignore); + } + + [[nodiscard]] bool CanFallbackToDeleteRangesWithTransaction( const AutoRangeArray& aRangesToDelete) const { return !IsHandlingRecursively() && !aRangesToDelete.Ranges().IsEmpty() && (!aRangesToDelete.IsCollapsed() || @@ -405,6 +416,20 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { const EditorDOMPoint& aPoint); [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> + FallbackToDeleteRangeWithTransaction(HTMLEditor& aHTMLEditor, + nsRange& aRangeToDelete) const { + MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); + MOZ_ASSERT(CanFallbackToDeleteRangeWithTransaction(aRangeToDelete)); + Result<CaretPoint, nsresult> caretPointOrError = + aHTMLEditor.DeleteRangeWithTransaction(mOriginalDirectionAndAmount, + mOriginalStripWrappers, + aRangeToDelete); + NS_WARNING_ASSERTION(caretPointOrError.isOk(), + "EditorBase::DeleteRangeWithTransaction() failed"); + return caretPointOrError; + } + + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> FallbackToDeleteRangesWithTransaction(HTMLEditor& aHTMLEditor, AutoRangeArray& aRangesToDelete) const { MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); @@ -414,26 +439,68 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { mOriginalStripWrappers, aRangesToDelete); NS_WARNING_ASSERTION(caretPointOrError.isOk(), - "HTMLEditor::DeleteRangesWithTransaction() failed"); + "EditorBase::DeleteRangesWithTransaction() failed"); return caretPointOrError; } /** - * ComputeRangesToDeleteRangesWithTransaction() computes target ranges - * which will be called by `EditorBase::DeleteRangesWithTransaction()`. + * Compute target range(s) which will be called by + * `EditorBase::DeleteRangeWithTransaction()` or + * `EditorBase::DeleteRangesWithTransaction()`. * TODO: We should not use it for consistency with each deletion handler * in this and nested classes. */ + nsresult ComputeRangeToDeleteRangeWithTransaction( + const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, + nsRange& aRange, const Element& aEditingHost) const; nsresult ComputeRangesToDeleteRangesWithTransaction( const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, - AutoRangeArray& aRangesToDelete) const; + AutoRangeArray& aRangesToDelete, const Element& aEditingHost) const { + MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); + const EditorBase::HowToHandleCollapsedRange howToHandleCollapsedRange = + EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount); + if (NS_WARN_IF(aRangesToDelete.IsCollapsed() && + howToHandleCollapsedRange == + EditorBase::HowToHandleCollapsedRange::Ignore)) { + return NS_ERROR_FAILURE; + } + for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { + if (range->Collapsed()) { + continue; + } + nsresult rv = ComputeRangeToDeleteRangeWithTransaction( + aHTMLEditor, aDirectionAndAmount, range, aEditingHost); + if (NS_FAILED(rv)) { + NS_WARNING( + "AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction(" + ") failed"); + return rv; + } + } + return NS_OK; + } + + nsresult FallbackToComputeRangeToDeleteRangeWithTransaction( + const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete, + const Element& aEditingHost) const { + MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); + MOZ_ASSERT(CanFallbackToDeleteRangeWithTransaction(aRangeToDelete)); + nsresult rv = ComputeRangeToDeleteRangeWithTransaction( + aHTMLEditor, mOriginalDirectionAndAmount, aRangeToDelete, aEditingHost); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "AutoDeleteRangesHandler::" + "ComputeRangeToDeleteRangeWithTransaction() failed"); + return rv; + } nsresult FallbackToComputeRangesToDeleteRangesWithTransaction( - const HTMLEditor& aHTMLEditor, AutoRangeArray& aRangesToDelete) const { + const HTMLEditor& aHTMLEditor, AutoRangeArray& aRangesToDelete, + const Element& aEditingHost) const { MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); MOZ_ASSERT(CanFallbackToDeleteRangesWithTransaction(aRangesToDelete)); nsresult rv = ComputeRangesToDeleteRangesWithTransaction( - aHTMLEditor, mOriginalDirectionAndAmount, aRangesToDelete); + aHTMLEditor, mOriginalDirectionAndAmount, aRangesToDelete, + aEditingHost); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoDeleteRangesHandler::" "ComputeRangesToDeleteRangesWithTransaction() failed"); @@ -493,18 +560,18 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { const WSRunScanner& aWSRunScannerAtCaret); /** - * PrepareToDeleteNonCollapsedRanges() considers left block element and + * PrepareToDeleteNonCollapsedRange() considers left block element and * right block element which are inclusive ancestor block element of - * start and end container of first range of aRangesToDelete. + * start and end container of aRangeToDelete * * @param aHTMLEditor The HTML editor. - * @param aRangesToDelete Ranges to delete. Must not be + * @param aRangeToDelete The range to delete. Must not be * collapsed. * @return true if can continue to handle the * deletion. */ - bool PrepareToDeleteNonCollapsedRanges( - const HTMLEditor& aHTMLEditor, const AutoRangeArray& aRangesToDelete); + bool PrepareToDeleteNonCollapsedRange(const HTMLEditor& aHTMLEditor, + const nsRange& aRangeToDelete); /** * Run() executes the joining. @@ -514,14 +581,13 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { * @param aStripWrappers Must be eStrip or eNoStrip. * @param aCaretPoint The caret point (i.e., selection start * or end). - * @param aRangesToDelete Ranges to delete of the caller. - * This should be collapsed and match - * with aCaretPoint. + * @param aRangeToDelete The range to delete. This should be + * collapsed and match with aCaretPoint. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run( HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, nsIEditor::EStripWrappers aStripWrappers, - const EditorDOMPoint& aCaretPoint, AutoRangeArray& aRangesToDelete, + const EditorDOMPoint& aCaretPoint, nsRange& aRangeToDelete, const Element& aEditingHost) { switch (mMode) { case Mode::JoinCurrentBlock: { @@ -537,7 +603,7 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { Result<EditActionResult, nsresult> result = HandleDeleteAtOtherBlockBoundary(aHTMLEditor, aDirectionAndAmount, aStripWrappers, aCaretPoint, - aRangesToDelete, aEditingHost); + aRangeToDelete, aEditingHost); NS_WARNING_ASSERTION(result.isOk(), "AutoBlockElementsJoiner::" "HandleDeleteAtOtherBlockBoundary() failed"); @@ -552,8 +618,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { return result; } case Mode::JoinBlocksInSameParent: - case Mode::DeleteContentInRanges: - case Mode::DeleteNonCollapsedRanges: + case Mode::DeleteContentInRange: + case Mode::DeleteNonCollapsedRange: MOZ_ASSERT_UNREACHABLE( "This mode should be handled in the other Run()"); return Err(NS_ERROR_UNEXPECTED); @@ -563,41 +629,41 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { return Err(NS_ERROR_NOT_INITIALIZED); } - nsresult ComputeRangesToDelete(const HTMLEditor& aHTMLEditor, - nsIEditor::EDirection aDirectionAndAmount, - const EditorDOMPoint& aCaretPoint, - AutoRangeArray& aRangesToDelete, - const Element& aEditingHost) const { + nsresult ComputeRangeToDelete(const HTMLEditor& aHTMLEditor, + nsIEditor::EDirection aDirectionAndAmount, + const EditorDOMPoint& aCaretPoint, + nsRange& aRangeToDelete, + const Element& aEditingHost) const { switch (mMode) { case Mode::JoinCurrentBlock: { - nsresult rv = ComputeRangesToDeleteAtCurrentBlockBoundary( - aHTMLEditor, aCaretPoint, aRangesToDelete, aEditingHost); + nsresult rv = ComputeRangeToDeleteAtCurrentBlockBoundary( + aHTMLEditor, aCaretPoint, aRangeToDelete, aEditingHost); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), "AutoBlockElementsJoiner::" - "ComputeRangesToDeleteAtCurrentBlockBoundary() failed"); + "ComputeRangeToDeleteAtCurrentBlockBoundary() failed"); return rv; } case Mode::JoinOtherBlock: { - nsresult rv = ComputeRangesToDeleteAtOtherBlockBoundary( - aHTMLEditor, aDirectionAndAmount, aCaretPoint, aRangesToDelete, + nsresult rv = ComputeRangeToDeleteAtOtherBlockBoundary( + aHTMLEditor, aDirectionAndAmount, aCaretPoint, aRangeToDelete, aEditingHost); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), "AutoBlockElementsJoiner::" - "ComputeRangesToDeleteAtOtherBlockBoundary() failed"); + "ComputeRangeToDeleteAtOtherBlockBoundary() failed"); return rv; } case Mode::DeleteBRElement: { - nsresult rv = ComputeRangesToDeleteBRElement(aRangesToDelete); + nsresult rv = ComputeRangeToDeleteBRElement(aRangeToDelete); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoBlockElementsJoiner::" - "ComputeRangesToDeleteBRElement() failed"); + "ComputeRangeToDeleteBRElement() failed"); return rv; } case Mode::JoinBlocksInSameParent: - case Mode::DeleteContentInRanges: - case Mode::DeleteNonCollapsedRanges: + case Mode::DeleteContentInRange: + case Mode::DeleteNonCollapsedRange: MOZ_ASSERT_UNREACHABLE( "This mode should be handled in the other " "ComputeRangesToDelete()"); @@ -615,7 +681,7 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { * @param aDirectionAndAmount Direction of the deletion. * @param aStripWrappers Whether delete or keep new empty * ancestor elements. - * @param aRangesToDelete Ranges to delete. Must not be + * @param aRangeToDelete The range to delete. Must not be * collapsed. * @param aSelectionWasCollapsed Whether selection was or was not * collapsed when starting to handle @@ -623,8 +689,7 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run( HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, - nsIEditor::EStripWrappers aStripWrappers, - AutoRangeArray& aRangesToDelete, + nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete, AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, const Element& aEditingHost) { switch (mMode) { @@ -638,26 +703,25 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { Result<EditActionResult, nsresult> result = JoinBlockElementsInSameParent( aHTMLEditor, aDirectionAndAmount, aStripWrappers, - aRangesToDelete, aSelectionWasCollapsed, aEditingHost); + aRangeToDelete, aSelectionWasCollapsed, aEditingHost); NS_WARNING_ASSERTION(result.isOk(), "AutoBlockElementsJoiner::" "JoinBlockElementsInSameParent() failed"); return result; } - case Mode::DeleteContentInRanges: { - Result<EditActionResult, nsresult> result = - DeleteContentInRanges(aHTMLEditor, aDirectionAndAmount, - aStripWrappers, aRangesToDelete); + case Mode::DeleteContentInRange: { + Result<EditActionResult, nsresult> result = DeleteContentInRange( + aHTMLEditor, aDirectionAndAmount, aStripWrappers, aRangeToDelete); NS_WARNING_ASSERTION( result.isOk(), - "AutoBlockElementsJoiner::DeleteContentInRanges() failed"); + "AutoBlockElementsJoiner::DeleteContentInRange() failed"); return result; } - case Mode::DeleteNonCollapsedRanges: { + case Mode::DeleteNonCollapsedRange: { Result<EditActionResult, nsresult> result = - HandleDeleteNonCollapsedRanges( + HandleDeleteNonCollapsedRange( aHTMLEditor, aDirectionAndAmount, aStripWrappers, - aRangesToDelete, aSelectionWasCollapsed, aEditingHost); + aRangeToDelete, aSelectionWasCollapsed, aEditingHost); NS_WARNING_ASSERTION(result.isOk(), "AutoBlockElementsJoiner::" "HandleDeleteNonCollapsedRange() failed"); @@ -671,10 +735,9 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { return Err(NS_ERROR_NOT_INITIALIZED); } - nsresult ComputeRangesToDelete( + nsresult ComputeRangeToDelete( const HTMLEditor& aHTMLEditor, - nsIEditor::EDirection aDirectionAndAmount, - AutoRangeArray& aRangesToDelete, + nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete, AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, const Element& aEditingHost) const { switch (mMode) { @@ -686,25 +749,25 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { "ComputeRangesToDelete()"); return NS_ERROR_UNEXPECTED; case Mode::JoinBlocksInSameParent: { - nsresult rv = ComputeRangesToJoinBlockElementsInSameParent( - aHTMLEditor, aDirectionAndAmount, aRangesToDelete); + nsresult rv = ComputeRangeToJoinBlockElementsInSameParent( + aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aEditingHost); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), "AutoBlockElementsJoiner::" "ComputeRangesToJoinBlockElementsInSameParent() failed"); return rv; } - case Mode::DeleteContentInRanges: { - nsresult rv = ComputeRangesToDeleteContentInRanges( - aHTMLEditor, aDirectionAndAmount, aRangesToDelete); + case Mode::DeleteContentInRange: { + nsresult rv = ComputeRangeToDeleteContentInRange( + aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aEditingHost); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoBlockElementsJoiner::" "ComputeRangesToDeleteContentInRanges() failed"); return rv; } - case Mode::DeleteNonCollapsedRanges: { - nsresult rv = ComputeRangesToDeleteNonCollapsedRanges( - aHTMLEditor, aDirectionAndAmount, aRangesToDelete, + case Mode::DeleteNonCollapsedRange: { + nsresult rv = ComputeRangeToDeleteNonCollapsedRange( + aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aSelectionWasCollapsed, aEditingHost); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), @@ -731,63 +794,59 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { HandleDeleteAtCurrentBlockBoundary( HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, const EditorDOMPoint& aCaretPoint, const Element& aEditingHost); - nsresult ComputeRangesToDeleteAtCurrentBlockBoundary( + nsresult ComputeRangeToDeleteAtCurrentBlockBoundary( const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint, - AutoRangeArray& aRangesToDelete, const Element& aEditingHost) const; + nsRange& aRangeToDelete, const Element& aEditingHost) const; [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> HandleDeleteAtOtherBlockBoundary(HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, nsIEditor::EStripWrappers aStripWrappers, const EditorDOMPoint& aCaretPoint, - AutoRangeArray& aRangesToDelete, + nsRange& aRangeToDelete, const Element& aEditingHost); // FYI: This method may modify selection, but it won't cause running // script because of `AutoHideSelectionChanges` which blocks // selection change listeners and the selection change event // dispatcher. MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult - ComputeRangesToDeleteAtOtherBlockBoundary( + ComputeRangeToDeleteAtOtherBlockBoundary( const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, - const EditorDOMPoint& aCaretPoint, AutoRangeArray& aRangesToDelete, + const EditorDOMPoint& aCaretPoint, nsRange& aRangeToDelete, const Element& aEditingHost) const; [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> JoinBlockElementsInSameParent( HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, - nsIEditor::EStripWrappers aStripWrappers, - AutoRangeArray& aRangesToDelete, + nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete, AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, const Element& aEditingHost); - nsresult ComputeRangesToJoinBlockElementsInSameParent( + nsresult ComputeRangeToJoinBlockElementsInSameParent( const HTMLEditor& aHTMLEditor, - nsIEditor::EDirection aDirectionAndAmount, - AutoRangeArray& aRangesToDelete) const; + nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete, + const Element& aEditingHost) const; [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> DeleteBRElement(HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, const Element& aEditingHost); - nsresult ComputeRangesToDeleteBRElement( - AutoRangeArray& aRangesToDelete) const; + nsresult ComputeRangeToDeleteBRElement(nsRange& aRangeToDelete) const; [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> - DeleteContentInRanges(HTMLEditor& aHTMLEditor, - nsIEditor::EDirection aDirectionAndAmount, - nsIEditor::EStripWrappers aStripWrappers, - AutoRangeArray& aRangesToDelete); - nsresult ComputeRangesToDeleteContentInRanges( + DeleteContentInRange(HTMLEditor& aHTMLEditor, + nsIEditor::EDirection aDirectionAndAmount, + nsIEditor::EStripWrappers aStripWrappers, + nsRange& aRangeToDelete); + nsresult ComputeRangeToDeleteContentInRange( const HTMLEditor& aHTMLEditor, - nsIEditor::EDirection aDirectionAndAmount, - AutoRangeArray& aRangesToDelete) const; + nsIEditor::EDirection aDirectionAndAmount, nsRange& aRange, + const Element& aEditingHost) const; [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> - HandleDeleteNonCollapsedRanges( + HandleDeleteNonCollapsedRange( HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, - nsIEditor::EStripWrappers aStripWrappers, - AutoRangeArray& aRangesToDelete, + nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete, AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, const Element& aEditingHost); - nsresult ComputeRangesToDeleteNonCollapsedRanges( + nsresult ComputeRangeToDeleteNonCollapsedRange( const HTMLEditor& aHTMLEditor, - nsIEditor::EDirection aDirectionAndAmount, - AutoRangeArray& aRangesToDelete, + nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete, AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, const Element& aEditingHost) const; @@ -829,7 +888,7 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed) const; Result<bool, nsresult> - ComputeRangesToDeleteNodesEntirelyInRangeButKeepTableStructure( + ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure( const HTMLEditor& aHTMLEditor, nsRange& aRange, AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed) const; @@ -900,17 +959,17 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { } /** - * ComputeRangesToDelete() extends aRangesToDelete includes the element + * ComputeRangesToDelete() extends aRangeToDelete includes the element * boundaries between joining blocks. If they won't be joined, this * collapses the range to aCaretPoint. */ - nsresult ComputeRangesToDelete(const HTMLEditor& aHTMLEditor, - const EditorDOMPoint& aCaretPoint, - AutoRangeArray& aRangesToDelete) const; + nsresult ComputeRangeToDelete(const HTMLEditor& aHTMLEditor, + const EditorDOMPoint& aCaretPoint, + nsRange& aRangeToDelete) const; /** * Join inclusive ancestor block elements which are found by preceding - * Preare() call. + * Prepare() call. * The right element is always joined to the left element. * If the elements are the same type and not nested within each other, * JoinEditableNodesWithTransaction() is called (example, joining two @@ -971,8 +1030,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { JoinOtherBlock, JoinBlocksInSameParent, DeleteBRElement, - DeleteContentInRanges, - DeleteNonCollapsedRanges, + DeleteContentInRange, + DeleteNonCollapsedRange, }; AutoDeleteRangesHandler* mDeleteRangesHandler; const AutoDeleteRangesHandler& mDeleteRangesHandlerConst; @@ -1291,7 +1350,7 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete( return NS_SUCCESS_DOM_NO_OPERATION; } nsresult rv = FallbackToComputeRangesToDeleteRangesWithTransaction( - aHTMLEditor, aRangesToDelete); + aHTMLEditor, aRangesToDelete, *editingHost); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), "AutoDeleteRangesHandler::" @@ -1770,40 +1829,57 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges( if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) { return NS_ERROR_FAILURE; } - AutoBlockElementsJoiner joiner(*this); - if (!joiner.PrepareToDeleteAtOtherBlockBoundary( - aHTMLEditor, aDirectionAndAmount, - *aScanFromCaretPointResult.ElementPtr(), - aWSRunScannerAtCaret.ScanStartRef(), aWSRunScannerAtCaret)) { - return NS_SUCCESS_DOM_NO_OPERATION; + MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); + bool handled = false; + for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { + MOZ_ASSERT(range->IsPositioned()); + AutoBlockElementsJoiner joiner(*this); + if (!joiner.PrepareToDeleteAtOtherBlockBoundary( + aHTMLEditor, aDirectionAndAmount, + *aScanFromCaretPointResult.ElementPtr(), + aWSRunScannerAtCaret.ScanStartRef(), aWSRunScannerAtCaret)) { + continue; + } + handled = true; + nsresult rv = joiner.ComputeRangeToDelete( + aHTMLEditor, aDirectionAndAmount, aWSRunScannerAtCaret.ScanStartRef(), + range, aEditingHost); + if (NS_FAILED(rv)) { + NS_WARNING( + "AutoBlockElementsJoiner::ComputeRangeToDelete() failed (other " + "block boundary)"); + return rv; + } } - nsresult rv = joiner.ComputeRangesToDelete( - aHTMLEditor, aDirectionAndAmount, aWSRunScannerAtCaret.ScanStartRef(), - aRangesToDelete, aEditingHost); - NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), - "AutoBlockElementsJoiner::ComputeRangesToDelete() " - "failed (other block boundary)"); - return rv; + return handled ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION; } if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary()) { if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) { return NS_ERROR_FAILURE; } - AutoBlockElementsJoiner joiner(*this); - if (!joiner.PrepareToDeleteAtCurrentBlockBoundary( - aHTMLEditor, aDirectionAndAmount, - *aScanFromCaretPointResult.ElementPtr(), - aWSRunScannerAtCaret.ScanStartRef())) { - return NS_SUCCESS_DOM_NO_OPERATION; + MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); + bool handled = false; + for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { + AutoBlockElementsJoiner joiner(*this); + if (!joiner.PrepareToDeleteAtCurrentBlockBoundary( + aHTMLEditor, aDirectionAndAmount, + *aScanFromCaretPointResult.ElementPtr(), + aWSRunScannerAtCaret.ScanStartRef())) { + continue; + } + handled = true; + nsresult rv = joiner.ComputeRangeToDelete( + aHTMLEditor, aDirectionAndAmount, aWSRunScannerAtCaret.ScanStartRef(), + range, aEditingHost); + if (NS_FAILED(rv)) { + NS_WARNING( + "AutoBlockElementsJoiner::ComputeRangeToDelete() failed (current " + "block boundary)"); + return rv; + } } - nsresult rv = joiner.ComputeRangesToDelete( - aHTMLEditor, aDirectionAndAmount, aWSRunScannerAtCaret.ScanStartRef(), - aRangesToDelete, aEditingHost); - NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), - "AutoBlockElementsJoiner::ComputeRangesToDelete() " - "failed (current block boundary)"); - return rv; + return handled ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION; } return NS_OK; @@ -1946,40 +2022,62 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges( if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) { return Err(NS_ERROR_FAILURE); } - AutoBlockElementsJoiner joiner(*this); - if (!joiner.PrepareToDeleteAtOtherBlockBoundary( - aHTMLEditor, aDirectionAndAmount, - *aScanFromCaretPointResult.ElementPtr(), - aWSRunScannerAtCaret.ScanStartRef(), aWSRunScannerAtCaret)) { - return EditActionResult::CanceledResult(); + MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); + bool allRangesNotHandled = true; + auto ret = EditActionResult::IgnoredResult(); + for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { + AutoBlockElementsJoiner joiner(*this); + if (!joiner.PrepareToDeleteAtOtherBlockBoundary( + aHTMLEditor, aDirectionAndAmount, + *aScanFromCaretPointResult.ElementPtr(), + aWSRunScannerAtCaret.ScanStartRef(), aWSRunScannerAtCaret)) { + continue; + } + allRangesNotHandled = false; + Result<EditActionResult, nsresult> result = + joiner.Run(aHTMLEditor, aDirectionAndAmount, aStripWrappers, + aWSRunScannerAtCaret.ScanStartRef(), MOZ_KnownLive(range), + aEditingHost); + if (MOZ_UNLIKELY(result.isErr())) { + NS_WARNING( + "AutoBlockElementsJoiner::Run() failed (other block boundary)"); + return result; + } + ret |= result.inspect(); } - Result<EditActionResult, nsresult> result = joiner.Run( - aHTMLEditor, aDirectionAndAmount, aStripWrappers, - aWSRunScannerAtCaret.ScanStartRef(), aRangesToDelete, aEditingHost); - NS_WARNING_ASSERTION( - result.isOk(), - "AutoBlockElementsJoiner::Run() failed (other block boundary)"); - return result; + return allRangesNotHandled ? EditActionResult::CanceledResult() + : std::move(ret); } if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary()) { if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) { return Err(NS_ERROR_FAILURE); } - AutoBlockElementsJoiner joiner(*this); - if (!joiner.PrepareToDeleteAtCurrentBlockBoundary( - aHTMLEditor, aDirectionAndAmount, - *aScanFromCaretPointResult.ElementPtr(), - aWSRunScannerAtCaret.ScanStartRef())) { - return EditActionResult::CanceledResult(); + MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); + bool allRangesNotHandled = true; + auto ret = EditActionResult::IgnoredResult(); + for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { + AutoBlockElementsJoiner joiner(*this); + if (!joiner.PrepareToDeleteAtCurrentBlockBoundary( + aHTMLEditor, aDirectionAndAmount, + *aScanFromCaretPointResult.ElementPtr(), + aWSRunScannerAtCaret.ScanStartRef())) { + continue; + } + allRangesNotHandled = false; + Result<EditActionResult, nsresult> result = + joiner.Run(aHTMLEditor, aDirectionAndAmount, aStripWrappers, + aWSRunScannerAtCaret.ScanStartRef(), MOZ_KnownLive(range), + aEditingHost); + if (MOZ_UNLIKELY(result.isErr())) { + NS_WARNING( + "AutoBlockElementsJoiner::Run() failed (current block boundary)"); + return result; + } + ret |= result.inspect(); } - Result<EditActionResult, nsresult> result = joiner.Run( - aHTMLEditor, aDirectionAndAmount, aStripWrappers, - aWSRunScannerAtCaret.ScanStartRef(), aRangesToDelete, aEditingHost); - NS_WARNING_ASSERTION( - result.isOk(), - "AutoBlockElementsJoiner::Run() failed (current block boundary)"); - return result; + return allRangesNotHandled ? EditActionResult::CanceledResult() + : std::move(ret); } MOZ_ASSERT_UNREACHABLE("New type of reached content hasn't been handled yet"); @@ -2439,13 +2537,14 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: } nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: - ComputeRangesToDeleteBRElement(AutoRangeArray& aRangesToDelete) const { + ComputeRangeToDeleteBRElement(nsRange& aRangeToDelete) const { MOZ_ASSERT(mBRElement); // XXX Why don't we scan invisible leading white-spaces which follows the // `<br>` element? - nsresult rv = aRangesToDelete.SelectNode(*mBRElement); - NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::SelectNode() failed"); - return rv; + IgnoredErrorResult error; + aRangeToDelete.SelectNode(*mBRElement, error); + NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SelectNode() failed"); + return error.StealNSResult(); } Result<EditActionResult, nsresult> @@ -2545,10 +2644,10 @@ HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement( } nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: - ComputeRangesToDeleteAtOtherBlockBoundary( + ComputeRangeToDeleteAtOtherBlockBoundary( const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, - const EditorDOMPoint& aCaretPoint, AutoRangeArray& aRangesToDelete, + const EditorDOMPoint& aCaretPoint, nsRange& aRangeToDelete, const Element& aEditingHost) const { MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); MOZ_ASSERT(aCaretPoint.IsSetAndValid()); @@ -2557,20 +2656,19 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: if (HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent) != HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent)) { - if (!mDeleteRangesHandlerConst.CanFallbackToDeleteRangesWithTransaction( - aRangesToDelete)) { - nsresult rv = aRangesToDelete.Collapse(aCaretPoint); - NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), - "AutoRangeArray::Collapse() failed"); + if (!mDeleteRangesHandlerConst.CanFallbackToDeleteRangeWithTransaction( + aRangeToDelete)) { + nsresult rv = aRangeToDelete.CollapseTo(aCaretPoint.ToRawRangeBoundary()); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::CollapseTo() failed"); return rv; } nsresult rv = mDeleteRangesHandlerConst - .FallbackToComputeRangesToDeleteRangesWithTransaction( - aHTMLEditor, aRangesToDelete); + .FallbackToComputeRangeToDeleteRangeWithTransaction( + aHTMLEditor, aRangeToDelete, aEditingHost); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), "AutoDeleteRangesHandler::" - "FallbackToComputeRangesToDeleteRangesWithTransaction() failed"); + "FallbackToComputeRangeToDeleteRangeWithTransaction() failed"); return rv; } @@ -2585,11 +2683,10 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: if (canJoinThem.inspect() && joiner.CanJoinBlocks() && !joiner.ShouldDeleteLeafContentInstead()) { nsresult rv = - joiner.ComputeRangesToDelete(aHTMLEditor, aCaretPoint, aRangesToDelete); - NS_WARNING_ASSERTION( - NS_SUCCEEDED(rv), - "AutoInclusiveAncestorBlockElementsJoiner::ComputeRangesToDelete() " - "failed"); + joiner.ComputeRangeToDelete(aHTMLEditor, aCaretPoint, aRangeToDelete); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "AutoInclusiveAncestorBlockElementsJoiner::" + "ComputeRangeToDelete() failed"); return rv; } @@ -2612,11 +2709,11 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: : EditorRawDOMPoint(mLeafContentInOtherBlock, 0); // If new caret position is same as current caret position, we can do // nothing anymore. - if (aRangesToDelete.IsCollapsed() && - aRangesToDelete.FocusRef() == newCaretPoint.ToRawRangeBoundary()) { + if (aRangeToDelete.Collapsed() && + aRangeToDelete.EndRef() == newCaretPoint.ToRawRangeBoundary()) { return NS_OK; } - // TODO: Stop modifying the `Selection` for computing the targer ranges. + // TODO: Stop modifying the `Selection` for computing the target ranges. nsresult rv = aHTMLEditor.CollapseSelectionTo(newCaretPoint); if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { NS_WARNING( @@ -2626,13 +2723,25 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionTo() failed"); if (NS_SUCCEEDED(rv)) { - aRangesToDelete.Initialize(aHTMLEditor.SelectionRef()); + AutoRangeArray rangeArray(aHTMLEditor.SelectionRef()); AutoDeleteRangesHandler anotherHandler(mDeleteRangesHandlerConst); rv = anotherHandler.ComputeRangesToDelete(aHTMLEditor, aDirectionAndAmount, - aRangesToDelete, aEditingHost); - NS_WARNING_ASSERTION( - NS_SUCCEEDED(rv), - "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() failed"); + rangeArray, aEditingHost); + if (NS_SUCCEEDED(rv)) { + if (MOZ_LIKELY(!rangeArray.Ranges().IsEmpty())) { + MOZ_ASSERT(rangeArray.Ranges().Length() == 1); + aRangeToDelete.SetStartAndEnd(rangeArray.FirstRangeRef()->StartRef(), + rangeArray.FirstRangeRef()->EndRef()); + } else { + NS_WARNING( + "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() " + "returned no range"); + rv = NS_ERROR_FAILURE; + } + } else { + NS_WARNING( + "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() failed"); + } } // Restore selection. nsresult rvCollapsingSelectionTo = @@ -2654,7 +2763,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: AutoBlockElementsJoiner::HandleDeleteAtOtherBlockBoundary( HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, nsIEditor::EStripWrappers aStripWrappers, - const EditorDOMPoint& aCaretPoint, AutoRangeArray& aRangesToDelete, + const EditorDOMPoint& aCaretPoint, nsRange& aRangeToDelete, const Element& aEditingHost) { MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); MOZ_ASSERT(aCaretPoint.IsSetAndValid()); @@ -2666,13 +2775,13 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent)) { // If we have not deleted `<br>` element and are not called recursively, // we should call `DeleteRangesWithTransaction()` here. - if (!mDeleteRangesHandler->CanFallbackToDeleteRangesWithTransaction( - aRangesToDelete)) { + if (!mDeleteRangesHandler->CanFallbackToDeleteRangeWithTransaction( + aRangeToDelete)) { return EditActionResult::IgnoredResult(); } Result<CaretPoint, nsresult> caretPointOrError = - mDeleteRangesHandler->FallbackToDeleteRangesWithTransaction( - aHTMLEditor, aRangesToDelete); + mDeleteRangesHandler->FallbackToDeleteRangeWithTransaction( + aHTMLEditor, aRangeToDelete); if (MOZ_UNLIKELY(caretPointOrError.isErr())) { NS_WARNING( "AutoDeleteRangesHandler::FallbackToDeleteRangesWithTransaction() " @@ -2789,8 +2898,8 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: : EditorRawDOMPoint(mLeafContentInOtherBlock, 0); // If new caret position is same as current caret position, we can do // nothing anymore. - if (aRangesToDelete.IsCollapsed() && - aRangesToDelete.FocusRef() == newCaretPoint.ToRawRangeBoundary()) { + if (aRangeToDelete.Collapsed() && + aRangeToDelete.EndRef() == newCaretPoint.ToRawRangeBoundary()) { return EditActionResult::CanceledResult(); } nsresult rv = aHTMLEditor.CollapseSelectionTo(newCaretPoint); @@ -2945,9 +3054,9 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: } nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: - ComputeRangesToDeleteAtCurrentBlockBoundary( + ComputeRangeToDeleteAtCurrentBlockBoundary( const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint, - AutoRangeArray& aRangesToDelete, const Element& aEditingHost) const { + nsRange& aRangeToDelete, const Element& aEditingHost) const { MOZ_ASSERT(mLeftContent); MOZ_ASSERT(mRightContent); @@ -2961,7 +3070,7 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: } if (canJoinThem.inspect()) { nsresult rv = - joiner.ComputeRangesToDelete(aHTMLEditor, aCaretPoint, aRangesToDelete); + joiner.ComputeRangeToDelete(aHTMLEditor, aCaretPoint, aRangeToDelete); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoInclusiveAncestorBlockElementsJoiner::" "ComputeRangesToDelete() failed"); @@ -2970,8 +3079,8 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: // In this case, nothing will be deleted so that the affected range should // be collapsed. - nsresult rv = aRangesToDelete.Collapse(aCaretPoint); - NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::Collapse() failed"); + nsresult rv = aRangeToDelete.CollapseTo(aCaretPoint.ToRawRangeBoundary()); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::CollapseTo() failed"); return rv; } @@ -3145,7 +3254,7 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteNonCollapsedRanges( aRangesToDelete.FirstRangeRef()->GetEndContainer()) { if (!aRangesToDelete.FirstRangeRef()->Collapsed()) { nsresult rv = ComputeRangesToDeleteRangesWithTransaction( - aHTMLEditor, aDirectionAndAmount, aRangesToDelete); + aHTMLEditor, aDirectionAndAmount, aRangesToDelete, aEditingHost); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), "AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction(" @@ -3169,17 +3278,23 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteNonCollapsedRanges( aDirectionAndAmount = nsIEditor::ePrevious; } - AutoBlockElementsJoiner joiner(*this); - if (!joiner.PrepareToDeleteNonCollapsedRanges(aHTMLEditor, aRangesToDelete)) { - return NS_ERROR_FAILURE; + for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { + if (MOZ_UNLIKELY(range->Collapsed())) { + continue; + } + AutoBlockElementsJoiner joiner(*this); + if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range)) { + return NS_ERROR_FAILURE; + } + nsresult rv = + joiner.ComputeRangeToDelete(aHTMLEditor, aDirectionAndAmount, range, + aSelectionWasCollapsed, aEditingHost); + if (NS_FAILED(rv)) { + NS_WARNING("AutoBlockElementsJoiner::ComputeRangeToDelete() failed"); + return rv; + } } - nsresult rv = joiner.ComputeRangesToDelete( - aHTMLEditor, aDirectionAndAmount, aRangesToDelete, aSelectionWasCollapsed, - aEditingHost); - NS_WARNING_ASSERTION( - NS_SUCCEEDED(rv), - "AutoBlockElementsJoiner::ComputeRangesToDelete() failed"); - return rv; + return NS_OK; } Result<EditActionResult, nsresult> @@ -3351,29 +3466,40 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges( aDirectionAndAmount = nsIEditor::ePrevious; } - AutoBlockElementsJoiner joiner(*this); - if (!joiner.PrepareToDeleteNonCollapsedRanges(aHTMLEditor, aRangesToDelete)) { - return Err(NS_ERROR_FAILURE); + MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); + auto ret = EditActionResult::IgnoredResult(); + for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { + if (MOZ_UNLIKELY(range->Collapsed())) { + continue; + } + AutoBlockElementsJoiner joiner(*this); + if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range)) { + return Err(NS_ERROR_FAILURE); + } + Result<EditActionResult, nsresult> result = + joiner.Run(aHTMLEditor, aDirectionAndAmount, aStripWrappers, + MOZ_KnownLive(range), aSelectionWasCollapsed, aEditingHost); + if (MOZ_UNLIKELY(result.isErr())) { + NS_WARNING("AutoBlockElementsJoiner::Run() failed"); + return result; + } + ret |= result.inspect(); } - Result<EditActionResult, nsresult> result = - joiner.Run(aHTMLEditor, aDirectionAndAmount, aStripWrappers, - aRangesToDelete, aSelectionWasCollapsed, aEditingHost); - NS_WARNING_ASSERTION(result.isOk(), "AutoBlockElementsJoiner::Run() failed"); - return result; + return ret; } bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: - PrepareToDeleteNonCollapsedRanges(const HTMLEditor& aHTMLEditor, - const AutoRangeArray& aRangesToDelete) { + PrepareToDeleteNonCollapsedRange(const HTMLEditor& aHTMLEditor, + const nsRange& aRangeToDelete) { MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); - MOZ_ASSERT(!aRangesToDelete.IsCollapsed()); + MOZ_ASSERT(!aRangeToDelete.Collapsed()); mLeftContent = HTMLEditUtils::GetInclusiveAncestorElement( - *aRangesToDelete.FirstRangeRef()->GetStartContainer()->AsContent(), + *aRangeToDelete.GetStartContainer()->AsContent(), HTMLEditUtils::ClosestEditableBlockElement, BlockInlineCheck::UseComputedDisplayOutsideStyle); mRightContent = HTMLEditUtils::GetInclusiveAncestorElement( - *aRangesToDelete.FirstRangeRef()->GetEndContainer()->AsContent(), + *aRangeToDelete.GetEndContainer()->AsContent(), HTMLEditUtils::ClosestEditableBlockElement, BlockInlineCheck::UseComputedDisplayOutsideStyle); // Note that mLeftContent and/or mRightContent can be nullptr if editing host @@ -3381,15 +3507,11 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: // one or one reaches an inline editing host, we can just delete the content // in ranges. if (mLeftContent == mRightContent || !mLeftContent || !mRightContent) { - MOZ_ASSERT_IF(!mLeftContent || !mRightContent, - aRangesToDelete.FirstRangeRef() - ->GetStartContainer() - ->AsContent() - ->GetEditingHost() == aRangesToDelete.FirstRangeRef() - ->GetEndContainer() - ->AsContent() - ->GetEditingHost()); - mMode = Mode::DeleteContentInRanges; + MOZ_ASSERT_IF( + !mLeftContent || !mRightContent, + aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost() == + aRangeToDelete.GetEndContainer()->AsContent()->GetEditingHost()); + mMode = Mode::DeleteContentInRange; return true; } @@ -3406,100 +3528,79 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: return true; } - mMode = Mode::DeleteNonCollapsedRanges; + mMode = Mode::DeleteNonCollapsedRange; return true; } nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: - ComputeRangesToDeleteContentInRanges( + ComputeRangeToDeleteContentInRange( const HTMLEditor& aHTMLEditor, - nsIEditor::EDirection aDirectionAndAmount, - AutoRangeArray& aRangesToDelete) const { + nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete, + const Element& aEditingHost) const { MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); - MOZ_ASSERT(!aRangesToDelete.IsCollapsed()); - MOZ_ASSERT(mMode == Mode::DeleteContentInRanges); - MOZ_ASSERT(aRangesToDelete.FirstRangeRef() - ->GetStartContainer() - ->AsContent() - ->GetEditingHost()); - MOZ_ASSERT(aRangesToDelete.FirstRangeRef() - ->GetStartContainer() - ->AsContent() - ->GetEditingHost() == aRangesToDelete.FirstRangeRef() - ->GetEndContainer() - ->AsContent() - ->GetEditingHost()); + MOZ_ASSERT(!aRangeToDelete.Collapsed()); + MOZ_ASSERT(mMode == Mode::DeleteContentInRange); + MOZ_ASSERT(aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost()); + MOZ_ASSERT( + aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost() == + aRangeToDelete.GetEndContainer()->AsContent()->GetEditingHost()); MOZ_ASSERT(!mLeftContent == !mRightContent); MOZ_ASSERT_IF(mLeftContent, mLeftContent->IsElement()); - MOZ_ASSERT_IF(mLeftContent, aRangesToDelete.FirstRangeRef() - ->GetStartContainer() - ->IsInclusiveDescendantOf(mLeftContent)); + MOZ_ASSERT_IF(mLeftContent, + aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf( + mLeftContent)); MOZ_ASSERT_IF(mRightContent, mRightContent->IsElement()); - MOZ_ASSERT_IF(mRightContent, aRangesToDelete.FirstRangeRef() - ->GetEndContainer() - ->IsInclusiveDescendantOf(mRightContent)); - MOZ_ASSERT_IF(!mLeftContent, - HTMLEditUtils::IsInlineContent( - *aRangesToDelete.FirstRangeRef() - ->GetStartContainer() - ->AsContent() - ->GetEditingHost(), - BlockInlineCheck::UseComputedDisplayOutsideStyle)); + MOZ_ASSERT_IF( + mRightContent, + aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent)); + MOZ_ASSERT_IF( + !mLeftContent, + HTMLEditUtils::IsInlineContent( + *aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost(), + BlockInlineCheck::UseComputedDisplayOutsideStyle)); nsresult rv = - mDeleteRangesHandlerConst.ComputeRangesToDeleteRangesWithTransaction( - aHTMLEditor, aDirectionAndAmount, aRangesToDelete); + mDeleteRangesHandlerConst.ComputeRangeToDeleteRangeWithTransaction( + aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aEditingHost); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoDeleteRangesHandler::" - "ComputeRangesToDeleteRangesWithTransaction() failed"); + "ComputeRangeToDeleteRangeWithTransaction() failed"); return rv; } Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: - AutoBlockElementsJoiner::DeleteContentInRanges( + AutoBlockElementsJoiner::DeleteContentInRange( HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, - nsIEditor::EStripWrappers aStripWrappers, - AutoRangeArray& aRangesToDelete) { + nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete) { MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); - MOZ_ASSERT(!aRangesToDelete.IsCollapsed()); - MOZ_ASSERT(mMode == Mode::DeleteContentInRanges); + MOZ_ASSERT(!aRangeToDelete.Collapsed()); + MOZ_ASSERT(mMode == Mode::DeleteContentInRange); MOZ_ASSERT(mDeleteRangesHandler); - MOZ_ASSERT(aRangesToDelete.FirstRangeRef() - ->GetStartContainer() - ->AsContent() - ->GetEditingHost()); - MOZ_ASSERT(aRangesToDelete.FirstRangeRef() - ->GetStartContainer() - ->AsContent() - ->GetEditingHost() == aRangesToDelete.FirstRangeRef() - ->GetEndContainer() - ->AsContent() - ->GetEditingHost()); + MOZ_ASSERT(aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost()); + MOZ_ASSERT( + aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost() == + aRangeToDelete.GetEndContainer()->AsContent()->GetEditingHost()); MOZ_ASSERT_IF(mLeftContent, mLeftContent->IsElement()); - MOZ_ASSERT_IF(mLeftContent, aRangesToDelete.FirstRangeRef() - ->GetStartContainer() - ->IsInclusiveDescendantOf(mLeftContent)); + MOZ_ASSERT_IF(mLeftContent, + aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf( + mLeftContent)); MOZ_ASSERT_IF(mRightContent, mRightContent->IsElement()); - MOZ_ASSERT_IF(mRightContent, aRangesToDelete.FirstRangeRef() - ->GetEndContainer() - ->IsInclusiveDescendantOf(mRightContent)); - MOZ_ASSERT_IF(!mLeftContent, - HTMLEditUtils::IsInlineContent( - *aRangesToDelete.FirstRangeRef() - ->GetStartContainer() - ->AsContent() - ->GetEditingHost(), - BlockInlineCheck::UseComputedDisplayOutsideStyle)); - - // XXX This is also odd. We do we simply use - // `DeleteRangesWithTransaction()` only when **first** range is in - // same block? + MOZ_ASSERT_IF( + mRightContent, + aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent)); + MOZ_ASSERT_IF( + !mLeftContent, + HTMLEditUtils::IsInlineContent( + *aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost(), + BlockInlineCheck::UseComputedDisplayOutsideStyle)); + { + AutoRangeArray rangesToDelete(aRangeToDelete); AutoTrackDOMRange firstRangeTracker(aHTMLEditor.RangeUpdaterRef(), - &aRangesToDelete.FirstRangeRef()); + &rangesToDelete.FirstRangeRef()); Result<CaretPoint, nsresult> caretPointOrError = - aHTMLEditor.DeleteRangesWithTransaction( - aDirectionAndAmount, aStripWrappers, aRangesToDelete); + aHTMLEditor.DeleteRangesWithTransaction(aDirectionAndAmount, + aStripWrappers, rangesToDelete); if (MOZ_UNLIKELY(caretPointOrError.isErr())) { if (NS_WARN_IF(caretPointOrError.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) { @@ -3522,15 +3623,15 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: } } - if (NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->IsPositioned())) { + if (NS_WARN_IF(!aRangeToDelete.IsPositioned())) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } nsresult rv = mDeleteRangesHandler->DeleteUnnecessaryNodesAndCollapseSelection( aHTMLEditor, aDirectionAndAmount, - EditorDOMPoint(aRangesToDelete.FirstRangeRef()->StartRef()), - EditorDOMPoint(aRangesToDelete.FirstRangeRef()->EndRef())); + EditorDOMPoint(aRangeToDelete.StartRef()), + EditorDOMPoint(aRangeToDelete.EndRef())); if (NS_FAILED(rv)) { NS_WARNING( "AutoDeleteRangesHandler::DeleteUnnecessaryNodesAndCollapseSelection() " @@ -3541,63 +3642,59 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: } nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: - ComputeRangesToJoinBlockElementsInSameParent( + ComputeRangeToJoinBlockElementsInSameParent( const HTMLEditor& aHTMLEditor, - nsIEditor::EDirection aDirectionAndAmount, - AutoRangeArray& aRangesToDelete) const { + nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete, + const Element& aEditingHost) const { MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); - MOZ_ASSERT(!aRangesToDelete.IsCollapsed()); + MOZ_ASSERT(!aRangeToDelete.Collapsed()); MOZ_ASSERT(mMode == Mode::JoinBlocksInSameParent); MOZ_ASSERT(mLeftContent); MOZ_ASSERT(mLeftContent->IsElement()); - MOZ_ASSERT(aRangesToDelete.FirstRangeRef() - ->GetStartContainer() - ->IsInclusiveDescendantOf(mLeftContent)); + MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf( + mLeftContent)); MOZ_ASSERT(mRightContent); MOZ_ASSERT(mRightContent->IsElement()); - MOZ_ASSERT(aRangesToDelete.FirstRangeRef() - ->GetEndContainer() - ->IsInclusiveDescendantOf(mRightContent)); + MOZ_ASSERT( + aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent)); MOZ_ASSERT(mLeftContent->GetParentNode() == mRightContent->GetParentNode()); nsresult rv = - mDeleteRangesHandlerConst.ComputeRangesToDeleteRangesWithTransaction( - aHTMLEditor, aDirectionAndAmount, aRangesToDelete); + mDeleteRangesHandlerConst.ComputeRangeToDeleteRangeWithTransaction( + aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aEditingHost); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoDeleteRangesHandler::" - "ComputeRangesToDeleteRangesWithTransaction() failed"); + "ComputeRangeToDeleteRangeWithTransaction() failed"); return rv; } Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: AutoBlockElementsJoiner::JoinBlockElementsInSameParent( HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, - nsIEditor::EStripWrappers aStripWrappers, - AutoRangeArray& aRangesToDelete, + nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete, SelectionWasCollapsed aSelectionWasCollapsed, const Element& aEditingHost) { MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); - MOZ_ASSERT(!aRangesToDelete.IsCollapsed()); + MOZ_ASSERT(!aRangeToDelete.Collapsed()); MOZ_ASSERT(mMode == Mode::JoinBlocksInSameParent); MOZ_ASSERT(mLeftContent); MOZ_ASSERT(mLeftContent->IsElement()); - MOZ_ASSERT(aRangesToDelete.FirstRangeRef() - ->GetStartContainer() - ->IsInclusiveDescendantOf(mLeftContent)); + MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf( + mLeftContent)); MOZ_ASSERT(mRightContent); MOZ_ASSERT(mRightContent->IsElement()); - MOZ_ASSERT(aRangesToDelete.FirstRangeRef() - ->GetEndContainer() - ->IsInclusiveDescendantOf(mRightContent)); + MOZ_ASSERT( + aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent)); MOZ_ASSERT(mLeftContent->GetParentNode() == mRightContent->GetParentNode()); const bool backspaceInRightBlock = aSelectionWasCollapsed == SelectionWasCollapsed::Yes && nsIEditor::DirectionIsBackspace(aDirectionAndAmount); + AutoRangeArray rangesToDelete(aRangeToDelete); Result<CaretPoint, nsresult> caretPointOrError = aHTMLEditor.DeleteRangesWithTransaction(aDirectionAndAmount, - aStripWrappers, aRangesToDelete); + aStripWrappers, rangesToDelete); if (MOZ_UNLIKELY(caretPointOrError.isErr())) { NS_WARNING("EditorBase::DeleteRangesWithTransaction() failed"); return caretPointOrError.propagateErr(); @@ -3686,7 +3783,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: Result<bool, nsresult> HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: - ComputeRangesToDeleteNodesEntirelyInRangeButKeepTableStructure( + ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure( const HTMLEditor& aHTMLEditor, nsRange& aRange, AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed) const { @@ -3841,39 +3938,34 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: } nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: - ComputeRangesToDeleteNonCollapsedRanges( + ComputeRangeToDeleteNonCollapsedRange( const HTMLEditor& aHTMLEditor, - nsIEditor::EDirection aDirectionAndAmount, - AutoRangeArray& aRangesToDelete, + nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete, AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, const Element& aEditingHost) const { MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); - MOZ_ASSERT(!aRangesToDelete.IsCollapsed()); + MOZ_ASSERT(!aRangeToDelete.Collapsed()); MOZ_ASSERT(mLeftContent); MOZ_ASSERT(mLeftContent->IsElement()); - MOZ_ASSERT(aRangesToDelete.FirstRangeRef() - ->GetStartContainer() - ->IsInclusiveDescendantOf(mLeftContent)); + MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf( + mLeftContent)); MOZ_ASSERT(mRightContent); MOZ_ASSERT(mRightContent->IsElement()); - MOZ_ASSERT(aRangesToDelete.FirstRangeRef() - ->GetEndContainer() - ->IsInclusiveDescendantOf(mRightContent)); + MOZ_ASSERT( + aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent)); - for (OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { - Result<bool, nsresult> result = - ComputeRangesToDeleteNodesEntirelyInRangeButKeepTableStructure( - aHTMLEditor, range, aSelectionWasCollapsed); - if (result.isErr()) { - NS_WARNING( - "AutoBlockElementsJoiner::" - "ComputeRangesToDeleteNodesEntirelyInRangeButKeepTableStructure() " - "failed"); - return result.unwrapErr(); - } - if (!result.unwrap()) { - return NS_OK; - } + Result<bool, nsresult> result = + ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure( + aHTMLEditor, aRangeToDelete, aSelectionWasCollapsed); + if (result.isErr()) { + NS_WARNING( + "AutoBlockElementsJoiner::" + "ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure() " + "failed"); + return result.unwrapErr(); + } + if (!result.unwrap()) { + return NS_OK; } AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent, @@ -3893,68 +3985,62 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: return NS_OK; } - nsresult rv = joiner.ComputeRangesToDelete(aHTMLEditor, EditorDOMPoint(), - aRangesToDelete); + nsresult rv = joiner.ComputeRangeToDelete(aHTMLEditor, EditorDOMPoint(), + aRangeToDelete); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), - "AutoInclusiveAncestorBlockElementsJoiner::ComputeRangesToDelete() " + "AutoInclusiveAncestorBlockElementsJoiner::ComputeRangeToDelete() " "failed"); return rv; } Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: - AutoBlockElementsJoiner::HandleDeleteNonCollapsedRanges( + AutoBlockElementsJoiner::HandleDeleteNonCollapsedRange( HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, - nsIEditor::EStripWrappers aStripWrappers, - AutoRangeArray& aRangesToDelete, + nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete, AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, const Element& aEditingHost) { MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); - MOZ_ASSERT(!aRangesToDelete.IsCollapsed()); + MOZ_ASSERT(!aRangeToDelete.Collapsed()); MOZ_ASSERT(mDeleteRangesHandler); MOZ_ASSERT(mLeftContent); MOZ_ASSERT(mLeftContent->IsElement()); - MOZ_ASSERT(aRangesToDelete.FirstRangeRef() - ->GetStartContainer() - ->IsInclusiveDescendantOf(mLeftContent)); + MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf( + mLeftContent)); MOZ_ASSERT(mRightContent); MOZ_ASSERT(mRightContent->IsElement()); - MOZ_ASSERT(aRangesToDelete.FirstRangeRef() - ->GetEndContainer() - ->IsInclusiveDescendantOf(mRightContent)); + MOZ_ASSERT( + aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent)); const bool backspaceInRightBlock = aSelectionWasCollapsed == SelectionWasCollapsed::Yes && nsIEditor::DirectionIsBackspace(aDirectionAndAmount); - // Otherwise, delete every nodes in all ranges, then, clean up something. + // Otherwise, delete every nodes in the range, then, clean up something. EditActionResult result = EditActionResult::IgnoredResult(); EditorDOMPoint pointToPutCaret; while (true) { + OwningNonNull<nsRange> rangeToDelete(aRangeToDelete); AutoTrackDOMRange firstRangeTracker(aHTMLEditor.RangeUpdaterRef(), - &aRangesToDelete.FirstRangeRef()); - - bool joinInclusiveAncestorBlockElements = true; - for (auto& range : aRangesToDelete.Ranges()) { - Result<bool, nsresult> deleteResult = - DeleteNodesEntirelyInRangeButKeepTableStructure( - aHTMLEditor, MOZ_KnownLive(range), aSelectionWasCollapsed); - if (MOZ_UNLIKELY(deleteResult.isErr())) { - NS_WARNING( - "AutoBlockElementsJoiner::" - "DeleteNodesEntirelyInRangeButKeepTableStructure() failed"); - return deleteResult.propagateErr(); - } - // XXX Completely odd. Why don't we join blocks around each range? - joinInclusiveAncestorBlockElements &= deleteResult.unwrap(); + &rangeToDelete); + + Result<bool, nsresult> deleteResult = + DeleteNodesEntirelyInRangeButKeepTableStructure( + aHTMLEditor, rangeToDelete, aSelectionWasCollapsed); + if (MOZ_UNLIKELY(deleteResult.isErr())) { + NS_WARNING( + "AutoBlockElementsJoiner::" + "DeleteNodesEntirelyInRangeButKeepTableStructure() failed"); + return deleteResult.propagateErr(); } + const bool joinInclusiveAncestorBlockElements = deleteResult.unwrap(); + // Check endpoints for possible text deletion. We can assume that if - // text node is found, we can delete to end or to begining as + // text node is found, we can delete to end or to beginning as // appropriate, since the case where both sel endpoints in same text // node was already handled (we wouldn't be here) - nsresult rv = DeleteTextAtStartAndEndOfRange( - aHTMLEditor, MOZ_KnownLive(aRangesToDelete.FirstRangeRef())); + nsresult rv = DeleteTextAtStartAndEndOfRange(aHTMLEditor, rangeToDelete); if (NS_FAILED(rv)) { NS_WARNING( "AutoBlockElementsJoiner::DeleteTextAtStartAndEndOfRange() failed"); @@ -3978,7 +4064,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: // be collapsed to the end of the selection, if deleting backward the // selection should be collapsed to the beginning of the selection. // But if we're not joining then the selection should collapse to the - // beginning of the selection if we'redeleting forward, because the + // beginning of the selection if we're deleting forward, because the // end of the selection will still be in the next block. And same // thing for deleting backwards (selection should collapse to the end, // because the beginning will still be in the first block). See Bug @@ -4024,7 +4110,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: // AutoInclusiveAncestorBlockElementsJoiner computed new caret position, we // should use it. Otherwise, we should keep the traditional behavior. if (result.Handled() && pointToPutCaret.IsSet()) { - EditorDOMRange range(aRangesToDelete.FirstRangeRef()); + EditorDOMRange range(aRangeToDelete); nsresult rv = mDeleteRangesHandler->DeleteUnnecessaryNodes(aHTMLEditor, range); if (NS_FAILED(rv)) { @@ -4054,8 +4140,8 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: nsresult rv = mDeleteRangesHandler->DeleteUnnecessaryNodesAndCollapseSelection( aHTMLEditor, aDirectionAndAmount, - EditorDOMPoint(aRangesToDelete.FirstRangeRef()->StartRef()), - EditorDOMPoint(aRangesToDelete.FirstRangeRef()->EndRef())); + EditorDOMPoint(aRangeToDelete.StartRef()), + EditorDOMPoint(aRangeToDelete.EndRef())); if (NS_FAILED(rv)) { NS_WARNING( "AutoDeleteRangesHandler::DeleteUnnecessaryNodesAndCollapseSelection() " @@ -4316,21 +4402,26 @@ HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty( } nsresult -HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction( +HTMLEditor::AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction( const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, - AutoRangeArray& aRangesToDelete) const { + nsRange& aRangeToDelete, const Element& aEditingHost) const { MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); - MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); - EditorBase::HowToHandleCollapsedRange howToHandleCollapsedRange = + const EditorBase::HowToHandleCollapsedRange howToHandleCollapsedRange = EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount); - if (NS_WARN_IF(aRangesToDelete.IsCollapsed() && - howToHandleCollapsedRange == - EditorBase::HowToHandleCollapsedRange::Ignore)) { - return NS_ERROR_FAILURE; + if (MOZ_UNLIKELY(aRangeToDelete.Collapsed() && + howToHandleCollapsedRange == + EditorBase::HowToHandleCollapsedRange::Ignore)) { + return NS_SUCCESS_DOM_NO_OPERATION; } - auto extendRangeToSelectCharacterForward = + // If it's not collapsed, `DeleteRangeTransaction::Create()` will be called + // with it and `DeleteRangeTransaction` won't modify the range. + if (!aRangeToDelete.Collapsed()) { + return NS_OK; + } + + const auto ExtendRangeToSelectCharacterForward = [](nsRange& aRange, const EditorRawDOMPointInText& aCaretPoint) -> void { const nsTextFragment& textFragment = aCaretPoint.ContainerAs<Text>()->TextFragment(); @@ -4352,7 +4443,7 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction( NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsRange::SetStartAndEnd() failed"); }; - auto extendRangeToSelectCharacterBackward = + const auto ExtendRangeToSelectCharacterBackward = [](nsRange& aRange, const EditorRawDOMPointInText& aCaretPoint) -> void { if (aCaretPoint.IsStartOfContainer()) { return; @@ -4378,127 +4469,116 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction( "nsRange::SetStartAndEnd() failed"); }; - RefPtr<Element> editingHost = aHTMLEditor.ComputeEditingHost(); - for (OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { - // If it's not collapsed, `DeleteRangeTransaction::Create()` will be called - // with it and `DeleteRangeTransaction` won't modify the range. - if (!range->Collapsed()) { - continue; + // In the other cases, `EditorBase::CreateTransactionForCollapsedRange()` + // will handle the collapsed range. + EditorRawDOMPoint caretPoint(aRangeToDelete.StartRef()); + if (howToHandleCollapsedRange == + EditorBase::HowToHandleCollapsedRange::ExtendBackward && + caretPoint.IsStartOfContainer()) { + nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent( + *caretPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost); + if (!previousEditableContent) { + return NS_OK; } - - if (howToHandleCollapsedRange == - EditorBase::HowToHandleCollapsedRange::Ignore) { - continue; + if (!previousEditableContent->IsText()) { + IgnoredErrorResult ignoredError; + aRangeToDelete.SelectNode(*previousEditableContent, ignoredError); + NS_WARNING_ASSERTION(!ignoredError.Failed(), + "nsRange::SelectNode() failed"); + return NS_OK; } - // In the other cases, `EditorBase::CreateTransactionForCollapsedRange()` - // will handle the collapsed range. - EditorRawDOMPoint caretPoint(range->StartRef()); - if (howToHandleCollapsedRange == - EditorBase::HowToHandleCollapsedRange::ExtendBackward && - caretPoint.IsStartOfContainer()) { - nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent( - *caretPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, editingHost); - if (!previousEditableContent) { - continue; - } - if (!previousEditableContent->IsText()) { - IgnoredErrorResult ignoredError; - range->SelectNode(*previousEditableContent, ignoredError); - NS_WARNING_ASSERTION(!ignoredError.Failed(), - "nsRange::SelectNode() failed"); - continue; - } + ExtendRangeToSelectCharacterBackward( + aRangeToDelete, + EditorRawDOMPointInText::AtEndOf(*previousEditableContent->AsText())); + return NS_OK; + } - extendRangeToSelectCharacterBackward( - range, - EditorRawDOMPointInText::AtEndOf(*previousEditableContent->AsText())); - continue; + if (howToHandleCollapsedRange == + EditorBase::HowToHandleCollapsedRange::ExtendForward && + caretPoint.IsEndOfContainer()) { + nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent( + *caretPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost); + if (!nextEditableContent) { + return NS_OK; } - if (howToHandleCollapsedRange == - EditorBase::HowToHandleCollapsedRange::ExtendForward && - caretPoint.IsEndOfContainer()) { - nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent( - *caretPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, editingHost); - if (!nextEditableContent) { - continue; - } - - if (!nextEditableContent->IsText()) { - IgnoredErrorResult ignoredError; - range->SelectNode(*nextEditableContent, ignoredError); - NS_WARNING_ASSERTION(!ignoredError.Failed(), - "nsRange::SelectNode() failed"); - continue; - } - - extendRangeToSelectCharacterForward( - range, EditorRawDOMPointInText(nextEditableContent->AsText(), 0)); - continue; + if (!nextEditableContent->IsText()) { + IgnoredErrorResult ignoredError; + aRangeToDelete.SelectNode(*nextEditableContent, ignoredError); + NS_WARNING_ASSERTION(!ignoredError.Failed(), + "nsRange::SelectNode() failed"); + return NS_OK; } - if (caretPoint.IsInTextNode()) { - if (howToHandleCollapsedRange == - EditorBase::HowToHandleCollapsedRange::ExtendBackward) { - extendRangeToSelectCharacterBackward( - range, EditorRawDOMPointInText(caretPoint.ContainerAs<Text>(), - caretPoint.Offset())); - continue; - } - extendRangeToSelectCharacterForward( - range, EditorRawDOMPointInText(caretPoint.ContainerAs<Text>(), - caretPoint.Offset())); - continue; + ExtendRangeToSelectCharacterForward( + aRangeToDelete, + EditorRawDOMPointInText(nextEditableContent->AsText(), 0)); + return NS_OK; + } + + if (caretPoint.IsInTextNode()) { + if (howToHandleCollapsedRange == + EditorBase::HowToHandleCollapsedRange::ExtendBackward) { + ExtendRangeToSelectCharacterBackward( + aRangeToDelete, + EditorRawDOMPointInText(caretPoint.ContainerAs<Text>(), + caretPoint.Offset())); + return NS_OK; } + ExtendRangeToSelectCharacterForward( + aRangeToDelete, EditorRawDOMPointInText(caretPoint.ContainerAs<Text>(), + caretPoint.Offset())); + return NS_OK; + } - nsIContent* editableContent = + nsIContent* editableContent = + howToHandleCollapsedRange == + EditorBase::HowToHandleCollapsedRange::ExtendBackward + ? HTMLEditUtils::GetPreviousContent( + caretPoint, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost) + : HTMLEditUtils::GetNextContent( + caretPoint, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost); + if (!editableContent) { + return NS_OK; + } + while (editableContent && editableContent->IsCharacterData() && + !editableContent->Length()) { + editableContent = howToHandleCollapsedRange == EditorBase::HowToHandleCollapsedRange::ExtendBackward ? HTMLEditUtils::GetPreviousContent( - caretPoint, {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, editingHost) + *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost) : HTMLEditUtils::GetNextContent( - caretPoint, {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, editingHost); - if (!editableContent) { - continue; - } - while (editableContent && editableContent->IsCharacterData() && - !editableContent->Length()) { - editableContent = - howToHandleCollapsedRange == - EditorBase::HowToHandleCollapsedRange::ExtendBackward - ? HTMLEditUtils::GetPreviousContent( - *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, editingHost) - : HTMLEditUtils::GetNextContent( - *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, - BlockInlineCheck::Unused, editingHost); - } - if (!editableContent) { - continue; - } + *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, + BlockInlineCheck::Unused, &aEditingHost); + } + if (!editableContent) { + return NS_OK; + } - if (!editableContent->IsText()) { - IgnoredErrorResult ignoredError; - range->SelectNode(*editableContent, ignoredError); - NS_WARNING_ASSERTION(!ignoredError.Failed(), - "nsRange::SelectNode() failed"); - continue; - } + if (!editableContent->IsText()) { + IgnoredErrorResult ignoredError; + aRangeToDelete.SelectNode(*editableContent, ignoredError); + NS_WARNING_ASSERTION(!ignoredError.Failed(), + "nsRange::SelectNode() failed, but ignored"); + return NS_OK; + } - if (howToHandleCollapsedRange == - EditorBase::HowToHandleCollapsedRange::ExtendBackward) { - extendRangeToSelectCharacterBackward( - range, EditorRawDOMPointInText::AtEndOf(*editableContent->AsText())); - continue; - } - extendRangeToSelectCharacterForward( - range, EditorRawDOMPointInText(editableContent->AsText(), 0)); + if (howToHandleCollapsedRange == + EditorBase::HowToHandleCollapsedRange::ExtendBackward) { + ExtendRangeToSelectCharacterBackward( + aRangeToDelete, + EditorRawDOMPointInText::AtEndOf(*editableContent->AsText())); + return NS_OK; } + ExtendRangeToSelectCharacterForward( + aRangeToDelete, EditorRawDOMPointInText(editableContent->AsText(), 0)); return NS_OK; } @@ -4893,10 +4973,9 @@ Result<bool, nsresult> HTMLEditor::AutoDeleteRangesHandler:: } nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: - AutoInclusiveAncestorBlockElementsJoiner::ComputeRangesToDelete( + AutoInclusiveAncestorBlockElementsJoiner::ComputeRangeToDelete( const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint, - AutoRangeArray& aRangesToDelete) const { - MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); + nsRange& aRangeToDelete) const { MOZ_ASSERT(mLeftBlockElement); MOZ_ASSERT(mRightBlockElement); @@ -4904,8 +4983,8 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: if (!aCaretPoint.IsSet()) { return NS_OK; // The ranges are not collapsed, keep them as-is. } - nsresult rv = aRangesToDelete.Collapse(aCaretPoint); - NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::Collapse() failed"); + nsresult rv = aRangeToDelete.CollapseTo(aCaretPoint.ToRawRangeBoundary()); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::CollapseTo() failed"); return rv; } @@ -4927,8 +5006,7 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: if (!aCaretPoint.IsSet()) { // Don't shrink the original range. bool noNeedToChangeStart = false; - const auto atStart = - aRangesToDelete.GetFirstRangeStartPoint<EditorDOMPoint>(); + const EditorDOMPoint atStart(aRangeToDelete.StartRef()); if (atStart.IsBefore(range.StartRef())) { // If the range starts from end of a container, and computed block // boundaries range starts from an invisible `<br>` element, we @@ -4948,22 +5026,19 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: : nullptr; if (!nextContent || nextContent != range.StartRef().GetChild()) { noNeedToChangeStart = true; - range.SetStart( - aRangesToDelete.GetFirstRangeStartPoint<EditorDOMPoint>()); + range.SetStart(EditorRawDOMPoint(aRangeToDelete.StartRef())); } } - if (range.EndRef().IsBefore( - aRangesToDelete.GetFirstRangeEndPoint<EditorRawDOMPoint>())) { + if (range.EndRef().IsBefore(EditorRawDOMPoint(aRangeToDelete.EndRef()))) { if (noNeedToChangeStart) { return NS_OK; // We don't need to modify the range. } - range.SetEnd(aRangesToDelete.GetFirstRangeEndPoint<EditorDOMPoint>()); + range.SetEnd(EditorRawDOMPoint(aRangeToDelete.EndRef())); } } - // XXX Oddly, we join blocks only at the first range. - nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd( - range.StartRef().ToRawRangeBoundary(), - range.EndRef().ToRawRangeBoundary()); + nsresult rv = + aRangeToDelete.SetStartAndEnd(range.StartRef().ToRawRangeBoundary(), + range.EndRef().ToRawRangeBoundary()); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::SetStartAndEnd() failed"); return rv; @@ -6666,23 +6741,31 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete( return Err(NS_ERROR_FAILURE); } - // Look for the common ancestor's block element. It's fine that we get - // non-editable block element which is ancestor of inline editing host - // because the following code checks editing host too. - const Element* const maybeNonEditableBlockElement = - HTMLEditUtils::GetInclusiveAncestorElement( - *commonAncestor, HTMLEditUtils::ClosestBlockElement, - BlockInlineCheck::UseComputedDisplayOutsideStyle); - if (NS_WARN_IF(!maybeNonEditableBlockElement)) { + // Editing host may be nested and outer one could have focus. Let's use + // the closest editing host instead. + const RefPtr<Element> closestEditingHost = + aHTMLEditor.ComputeEditingHost(*commonAncestor, LimitInBodyElement::No); + if (NS_WARN_IF(!closestEditingHost)) { return Err(NS_ERROR_FAILURE); } - // Set up for loops and cache our root element - RefPtr<Element> editingHost = aHTMLEditor.ComputeEditingHost(); - if (NS_WARN_IF(!editingHost)) { - return Err(NS_ERROR_FAILURE); - } + // Look for the common ancestor's block element in the editing host. It's + // fine that we get non-editable block element which is ancestor of inline + // editing host because the following code checks editing host too. + const RefPtr<Element> closestBlockAncestorOrInlineEditingHost = [&]() { + // Note that if non-closest editing host has focus, found block may be + // non-editable. + if (Element* const maybeEditableBlockElement = + HTMLEditUtils::GetInclusiveAncestorElement( + *commonAncestor, HTMLEditUtils::ClosestBlockElement, + BlockInlineCheck::UseComputedDisplayOutsideStyle, + closestEditingHost)) { + return maybeEditableBlockElement; + } + return closestEditingHost.get(); + }(); + // Set up for loops and cache our root element // If only one list element is selected, and if the list element is empty, // we should delete only the list element. Or if the list element is not // empty, we should make the list has only one empty list item element. @@ -6712,18 +6795,18 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete( // Find previous visible things before start of selection EditorRawDOMRange rangeToDelete(aRangeToDelete); - if (rangeToDelete.StartRef().GetContainer() != maybeNonEditableBlockElement && - rangeToDelete.StartRef().GetContainer() != editingHost) { + if (rangeToDelete.StartRef().GetContainer() != + closestBlockAncestorOrInlineEditingHost) { for (;;) { WSScanResult backwardScanFromStartResult = WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( - editingHost, rangeToDelete.StartRef(), + closestEditingHost, rangeToDelete.StartRef(), BlockInlineCheck::UseComputedDisplayOutsideStyle); if (!backwardScanFromStartResult.ReachedCurrentBlockBoundary()) { break; } MOZ_ASSERT(backwardScanFromStartResult.GetContent() == - WSRunScanner(editingHost, rangeToDelete.StartRef(), + WSRunScanner(closestEditingHost, rangeToDelete.StartRef(), BlockInlineCheck::UseComputedDisplayOutsideStyle) .GetStartReasonContent()); // We want to keep looking up. But stop if we are crossing table @@ -6731,8 +6814,8 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete( if (HTMLEditUtils::IsAnyTableElement( backwardScanFromStartResult.GetContent()) || backwardScanFromStartResult.GetContent() == - maybeNonEditableBlockElement || - backwardScanFromStartResult.GetContent() == editingHost) { + closestBlockAncestorOrInlineEditingHost || + backwardScanFromStartResult.GetContent() == closestEditingHost) { break; } // Don't cross list element boundary because we don't want to delete list @@ -6767,11 +6850,11 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete( // Find next visible things after end of selection EditorDOMPoint atFirstInvisibleBRElement; - if (rangeToDelete.EndRef().GetContainer() != maybeNonEditableBlockElement && - rangeToDelete.EndRef().GetContainer() != editingHost) { + if (rangeToDelete.EndRef().GetContainer() != + closestBlockAncestorOrInlineEditingHost) { for (;;) { WSRunScanner wsScannerAtEnd( - editingHost, rangeToDelete.EndRef(), + closestEditingHost, rangeToDelete.EndRef(), BlockInlineCheck::UseComputedDisplayOutsideStyle); WSScanResult forwardScanFromEndResult = wsScannerAtEnd.ScanNextVisibleNodeOrBlockBoundaryFrom( @@ -6806,8 +6889,7 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete( if (HTMLEditUtils::IsAnyTableElement( forwardScanFromEndResult.GetContent()) || forwardScanFromEndResult.GetContent() == - maybeNonEditableBlockElement || - forwardScanFromEndResult.GetContent() == editingHost) { + closestBlockAncestorOrInlineEditingHost) { break; } // Don't cross flex-item/grid-item boundary to make new content inserted diff --git a/editor/libeditor/TextEditor.cpp b/editor/libeditor/TextEditor.cpp index 2403dd1f55..9f6fb86eb3 100644 --- a/editor/libeditor/TextEditor.cpp +++ b/editor/libeditor/TextEditor.cpp @@ -40,7 +40,6 @@ #include "nsCaret.h" #include "nsCharTraits.h" #include "nsComponentManagerUtils.h" -#include "nsContentCID.h" #include "nsContentList.h" #include "nsDebug.h" #include "nsDependentSubstring.h" diff --git a/editor/libeditor/tests/browserscope/test_richtext.html b/editor/libeditor/tests/browserscope/test_richtext.html index c07f0a366a..3322522929 100644 --- a/editor/libeditor/tests/browserscope/test_richtext.html +++ b/editor/libeditor/tests/browserscope/test_richtext.html @@ -23,7 +23,7 @@ SimpleTest.waitForExplicitFinish(); // Running all of the tests can take a long time, try to account for it SimpleTest.requestLongerTimeout(5); -function sendScore(results, continueParams) { +function sendScore(results) { ok(results.length > 1, "At least one test should have been run"); for (var i = 1; i < results.length; ++i) { var result = results[i]; diff --git a/editor/libeditor/tests/mochitest.toml b/editor/libeditor/tests/mochitest.toml index 5af13503f7..15a6d4c379 100644 --- a/editor/libeditor/tests/mochitest.toml +++ b/editor/libeditor/tests/mochitest.toml @@ -418,6 +418,8 @@ skip-if = ["os == 'android'"] #Bug 1575739 ["test_cmd_paragraphState.html"] +["test_command_state_when_readonly.html"] + ["test_composition_event_created_in_chrome.html"] ["test_composition_with_highlight_in_texteditor.html"] diff --git a/editor/libeditor/tests/test_bug1053048.html b/editor/libeditor/tests/test_bug1053048.html index 4f9df5e602..416d6442d6 100644 --- a/editor/libeditor/tests/test_bug1053048.html +++ b/editor/libeditor/tests/test_bug1053048.html @@ -37,7 +37,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1053048 // synthesizing the key press. So, we don't need to check whether a // notification actually comes here. let selectionListener = { - notifySelectionChanged(aDocument, aSelection, aReason, aAmount) { + notifySelectionChanged() { ok(true, "selectionStart: " + textarea.selectionStart); ok(true, "selectionEnd: " + textarea.selectionEnd); }, diff --git a/editor/libeditor/tests/test_bug1649005.html b/editor/libeditor/tests/test_bug1649005.html index fbd8e16ef8..5e08e16493 100644 --- a/editor/libeditor/tests/test_bug1649005.html +++ b/editor/libeditor/tests/test_bug1649005.html @@ -11,7 +11,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1649005 <script src="/tests/SimpleTest/EventUtils.js"></script> <script> /** Test for bug 1649005, bug 1779343 **/ - window.addEventListener("DOMContentLoaded", (event) => { + window.addEventListener("DOMContentLoaded", () => { SimpleTest.waitForExplicitFinish(); SimpleTest.waitForFocus(function() { document.body.textContent = ""; // It would be \n\n otherwise... diff --git a/editor/libeditor/tests/test_bug1659276.html b/editor/libeditor/tests/test_bug1659276.html index 6789db2e77..6286e58327 100644 --- a/editor/libeditor/tests/test_bug1659276.html +++ b/editor/libeditor/tests/test_bug1659276.html @@ -63,7 +63,7 @@ SimpleTest.waitForExplicitFinish(); SimpleTest.waitForFocus(async () => {
function waitForTickOfRefeshDriver() {
function awaitOneRefresh() {
- return new Promise(function(aResolve, aReject) {
+ return new Promise(function(aResolve) {
requestAnimationFrame(aResolve);
});
}
diff --git a/editor/libeditor/tests/test_bug502673.html b/editor/libeditor/tests/test_bug502673.html index 0850cf3de0..fcfb1f329e 100644 --- a/editor/libeditor/tests/test_bug502673.html +++ b/editor/libeditor/tests/test_bug502673.html @@ -37,7 +37,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=502673 editor.removeDocumentStateListener(this); }, - NotifyDocumentStateChanged(aNowDirty) { + NotifyDocumentStateChanged() { var editor = SpecialPowers.wrap(this.input).editor; editor.removeDocumentStateListener(this); }, diff --git a/editor/libeditor/tests/test_bug525389.html b/editor/libeditor/tests/test_bug525389.html index 500720d92a..25c6e2b8e3 100644 --- a/editor/libeditor/tests/test_bug525389.html +++ b/editor/libeditor/tests/test_bug525389.html @@ -17,7 +17,7 @@ function getLoadContext() { async function runTest() { var pasteCount = 0; - var pasteFunc = function(event) { pasteCount++; }; + var pasteFunc = function() { pasteCount++; }; function verifyContent(s) { var e = document.getElementById("i1"); diff --git a/editor/libeditor/tests/test_bug569988.html b/editor/libeditor/tests/test_bug569988.html index c4d4b040ba..6181668374 100644 --- a/editor/libeditor/tests/test_bug569988.html +++ b/editor/libeditor/tests/test_bug569988.html @@ -33,7 +33,7 @@ function runTest() { os.addObserver(onPromptLoad, "common-dialog-loaded"); os.addObserver(onPromptLoad, "tabmodal-dialog-loaded"); - function onPromptLoad(subject, topic, data) { + function onPromptLoad(subject) { let ui = subject.Dialog ? subject.Dialog.ui : undefined; if (!ui) { // subject is an tab prompt, find the elements ourselves @@ -54,19 +54,15 @@ function runTest() { sendAsyncMessage("ok", [true, "onPromptFocus is called"]); gPromptInput.removeEventListener("focus", onPromptFocus); - var listenerService = Services.els; - var listener = { handleEvent: function _hv(aEvent) { var isPrevented = aEvent.defaultPrevented; sendAsyncMessage("ok", [!isPrevented, "ESC key event is prevented by editor"]); - listenerService.removeSystemEventListener(gPromptInput, "keypress", - listener, false); + gPromptInput.removeEventListener("keypress", listener, { mozSystemGroup: true }); }, }; - listenerService.addSystemEventListener(gPromptInput, "keypress", - listener, false); + gPromptInput.addEventListener("keypress", listener, { mozSystemGroup: true }); sendAsyncMessage("info", "sending key"); var EventUtils = {}; diff --git a/editor/libeditor/tests/test_bug607584.xhtml b/editor/libeditor/tests/test_bug607584.xhtml index f610de544b..37bb3dc5a4 100644 --- a/editor/libeditor/tests/test_bug607584.xhtml +++ b/editor/libeditor/tests/test_bug607584.xhtml @@ -44,7 +44,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=607584 QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"]), - onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) + onStateChange(aWebProgress, aRequest, aStateFlags) { if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { @@ -71,25 +71,23 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=607584 }, - onProgressChange(aWebProgress, aRequest, - aCurSelfProgress, aMaxSelfProgress, - aCurTotalProgress, aMaxTotalProgress) + onProgressChange() { }, - onLocationChange(aWebProgress, aRequest, aLocation, aFlags) + onLocationChange() { }, - onStatusChange(aWebProgress, aRequest, aStatus, aMessage) + onStatusChange() { }, - onSecurityChange(aWebProgress, aRequest, aState) + onSecurityChange() { }, - onContentBlockingEvent(aWebProgress, aRequest, aEvent) + onContentBlockingEvent() { }, diff --git a/editor/libeditor/tests/test_bug611182.html b/editor/libeditor/tests/test_bug611182.html index 7843156fda..125dcacb62 100644 --- a/editor/libeditor/tests/test_bug611182.html +++ b/editor/libeditor/tests/test_bug611182.html @@ -92,7 +92,7 @@ SimpleTest.waitForFocus(function() { // start running all tests. var myXHR = new XMLHttpRequest(); myXHR.open("GET", "file_bug611182.sjs?queryTotalTests"); - myXHR.onload = function(e) { + myXHR.onload = function() { totalTests = myXHR.responseText; runAllTests(); }; diff --git a/editor/libeditor/tests/test_bug616590.xhtml b/editor/libeditor/tests/test_bug616590.xhtml index 1f6cb3d0f8..4a4bad7224 100644 --- a/editor/libeditor/tests/test_bug616590.xhtml +++ b/editor/libeditor/tests/test_bug616590.xhtml @@ -43,7 +43,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=616590 QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"]), - onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) + onStateChange(aWebProgress, aRequest, aStateFlags) { if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { @@ -60,25 +60,23 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=616590 }, - onProgressChange(aWebProgress, aRequest, - aCurSelfProgress, aMaxSelfProgress, - aCurTotalProgress, aMaxTotalProgress) + onProgressChange() { }, - onLocationChange(aWebProgress, aRequest, aLocation, aFlags) + onLocationChange() { }, - onStatusChange(aWebProgress, aRequest, aStatus, aMessage) + onStatusChange() { }, - onSecurityChange(aWebProgress, aRequest, aState) + onSecurityChange() { }, - onContentBlockingEvent(aWebProgress, aRequest, aEvent) + onContentBlockingEvent() { }, diff --git a/editor/libeditor/tests/test_bug674770-1.html b/editor/libeditor/tests/test_bug674770-1.html index 0b6089e0ef..d8e792ccd2 100644 --- a/editor/libeditor/tests/test_bug674770-1.html +++ b/editor/libeditor/tests/test_bug674770-1.html @@ -55,7 +55,7 @@ function startTests() { SimpleTest.executeSoon(runNextTest); }, false); - SpecialPowers.addSystemEventListener(window, "auxclick", function(aEvent) { + SpecialPowers.wrap(window).addEventListener("auxclick", function(aEvent) { // When the click event should cause default action, e.g., opening the link, // the event shouldn't have been consumed except the link handler. // However, in e10s mode, it's not consumed during propagating the event but @@ -74,7 +74,7 @@ function startTests() { // In this case, "storage" event won't be fired. SimpleTest.executeSoon(runNextTest); } - }, false); + }, { mozSystemGroup: true }); SimpleTest.executeSoon(runNextTest); } diff --git a/editor/libeditor/tests/test_bug674770-2.html b/editor/libeditor/tests/test_bug674770-2.html index 9b05277052..ab2fbca68b 100644 --- a/editor/libeditor/tests/test_bug674770-2.html +++ b/editor/libeditor/tests/test_bug674770-2.html @@ -320,7 +320,7 @@ function initForBodyEditableDocumentTests() { frameDocument.getElementById("editor2").removeEventListener("click", clickEventHandler); iframe.onload = - function(aEvent) { SimpleTest.executeSoon(runBodyEditableDocumentTests1); }; + function() { SimpleTest.executeSoon(runBodyEditableDocumentTests1); }; iframe.srcdoc = "<body contenteditable>body:</body>"; } diff --git a/editor/libeditor/tests/test_bug780908.xhtml b/editor/libeditor/tests/test_bug780908.xhtml index 590316ef46..6bfa306a71 100644 --- a/editor/libeditor/tests/test_bug780908.xhtml +++ b/editor/libeditor/tests/test_bug780908.xhtml @@ -46,7 +46,7 @@ adapted from test_bug607584.xhtml by Kent James <kent@caspia.com> QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"]), - onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) + onStateChange(aWebProgress, aRequest, aStateFlags) { if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { @@ -69,25 +69,23 @@ adapted from test_bug607584.xhtml by Kent James <kent@caspia.com> }, - onProgressChange(aWebProgress, aRequest, - aCurSelfProgress, aMaxSelfProgress, - aCurTotalProgress, aMaxTotalProgress) + onProgressChange() { }, - onLocationChange(aWebProgress, aRequest, aLocation, aFlags) + onLocationChange() { }, - onStatusChange(aWebProgress, aRequest, aStatus, aMessage) + onStatusChange() { }, - onSecurityChange(aWebProgress, aRequest, aState) + onSecurityChange() { }, - onContentBlockingEvent(aWebProgress, aRequest, aEvent) + onContentBlockingEvent() { }, diff --git a/editor/libeditor/tests/test_command_state_when_readonly.html b/editor/libeditor/tests/test_command_state_when_readonly.html new file mode 100644 index 0000000000..7b4daaa1b3 --- /dev/null +++ b/editor/libeditor/tests/test_command_state_when_readonly.html @@ -0,0 +1,149 @@ +<!doctype html> +<title>Test for nsIEditor.isCommandEnabled for normal and read-only editors</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="/tests/SimpleTest/EventUtils.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"> +<div contenteditable></div> +<script> + let node = document.querySelector("div"); + node.focus(); + let htmlEditor = + SpecialPowers.wrap(window).docShell.editingSession.getEditorForWindow(window); + + // Supported environments for each command. Supported values for each + // environment property: + // content: "empty", "non-empty", "cleared" + // selected: true, false + // readonly: true, false + // + // If an environment definition does not state a certain property, the command + // supports all possible values for that property. The following definition: + // "cmd_copy": [{content: "non-empty", selected: true}], + // is equivalent to: + // "cmd_copy": [ + // {content: "non-empty", selected: true, readonly: true}, + // {content: "non-empty", selected: true, readonly: false}, + // ], + const TEST_COMMANDS = { + "cmd_selectAll": [{content: "non-empty"}], + + "cmd_copy": [{content: "non-empty", selected: true}], + + "cmd_cut": [{content: "non-empty", selected: true, readonly: false}], + "cmd_delete": [{content: "non-empty", selected: true, readonly: false}], + "cmd_removeList":[{content: "non-empty", selected: true, readonly: false}], + + "cmd_undo": [{content: "cleared", readonly: false}], + "cmd_redo": [{content: "cleared", readonly: false}], + + "cmd_switchTextDirection": [{readonly: false}], + "cmd_bold": [{readonly: false}], + "cmd_italic": [{readonly: false}], + "cmd_underline": [{readonly: false}], + "cmd_em": [{readonly: false}], + "cmd_strong": [{readonly: false}], + "cmd_strikethrough": [{readonly: false}], + "cmd_superscript": [{readonly: false}], + "cmd_subscript": [{readonly: false}], + "cmd_indent": [{readonly: false}], + "cmd_outdent": [{readonly: false}], + "cmd_formatBlock": [{readonly: false}], + "cmd_paragraphState": [{readonly: false}], + "cmd_fontFace": [{readonly: false}], + "cmd_fontSize": [{readonly: false}], + "cmd_fontColor": [{readonly: false}], + "cmd_backgroundColor": [{readonly: false}], + "cmd_highlight": [{readonly: false}], + "cmd_align": [{readonly: false}], + "cmd_removeStyles": [{readonly: false}], + "cmd_increaseFont": [{readonly: false}], + "cmd_decreaseFont": [{readonly: false}], + "cmd_insertHR": [{readonly: false}], + "cmd_insertHTML": [{readonly: false}], + "cmd_insertText": [{readonly: false}], + "cmd_insertParagraph": [{readonly: false}], + "cmd_insertLineBreak": [{readonly: false}], + "cmd_tt":[{readonly: false}], + "cmd_nobreak":[{readonly: false}], + "cmd_cite":[{readonly: false}], + "cmd_abbr":[{readonly: false}], + "cmd_acronym":[{readonly: false}], + "cmd_code":[{readonly: false}], + "cmd_samp":[{readonly: false}], + "cmd_var":[{readonly: false}], + "cmd_removeLinks":[{readonly: false}], + "cmd_ol":[{readonly: false}], + "cmd_ul":[{readonly: false}], + "cmd_dt":[{readonly: false}], + "cmd_dd":[{readonly: false}], + + // InsertTagCommand + "cmd_insertImageNoUI": [{readonly: false}], + "cmd_insertLinkNoUI": [{readonly: false}], + }; + + function testCommands(content) { + for (let readonly of [true, false]){ + if (readonly) { + htmlEditor.flags |= SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask; + } else { + htmlEditor.flags &= ~SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask; + } + + for (let selected of [true, false]) { + let selection = window.getSelection(); + selection.collapse(node); + + if (selected) { + if (content == "non-empty") { + // The command cmd_removeList needs selected text inside a list. It + // does not matter for all other commands, so lets just select that. + let range = document.createRange(); + let li = document.querySelector("li"); + range.selectNodeContents(li); + selection.removeAllRanges(); + selection.addRange(range); + } else { + document.execCommand("selectAll"); + } + } + + for (let [cmd, supports] of Object.entries(TEST_COMMANDS)) { + // Check if the command should support this environment. + let expected = supports.some(supported => + content == (supported?.content ?? content) && + readonly == (supported?.readonly ?? readonly) && + selected == (supported?.selected ?? selected) + ) + is( + SpecialPowers.isCommandEnabled(window, cmd), + expected, + `Enabled state of command ${cmd} should be ${ + expected ? "TRUE" : "FALSE" + } for ${JSON.stringify({content, selected, readonly})}` + ); + } + } + } + } + + testCommands("empty"); + + // The cmd_removeList command needs a list. + node.innerHTML = "<ul><li><span>abcd</span></li></ul>"; + testCommands("non-empty"); + + // Make some content modifications to enable undo and redo. + node.innerText = "ABC"; + is(node.innerText.trim(), "ABC", "phase 1"); + document.execCommand("selectAll"); + synthesizeKey("KEY_Backspace"); + is(node.innerText.trim(), "", "phase 2"); + synthesizeKey("3"); + is(node.innerText.trim(), "3", "phase 3"); + SpecialPowers.doCommand(window, "cmd_undo"); + is(node.innerText.trim(), "", "phase 4"); + + node.innerHTML = ""; + testCommands("cleared"); +</script> diff --git a/editor/libeditor/tests/test_composition_with_highlight_in_texteditor.html b/editor/libeditor/tests/test_composition_with_highlight_in_texteditor.html index d6bb703231..0990d87adb 100644 --- a/editor/libeditor/tests/test_composition_with_highlight_in_texteditor.html +++ b/editor/libeditor/tests/test_composition_with_highlight_in_texteditor.html @@ -23,9 +23,9 @@ SimpleTest.waitForFocus(() => { ); const editActionListener = { QueryInterface: SpecialPowers.ChromeUtils.generateQI(["nsIEditActionListener"]), - WillDeleteText: (textNode, offset, length) => {}, - DidInsertText: (textNode, offset, aString) => {}, - WillDeleteRanges: (rangesToDelete) => {}, + WillDeleteText: () => {}, + DidInsertText: () => {}, + WillDeleteRanges: () => {}, }; // Highlight "a" findSelection.setBaseAndExtent( diff --git a/editor/libeditor/tests/test_contenteditable_text_input_handling.html b/editor/libeditor/tests/test_contenteditable_text_input_handling.html index c00056a0f0..13a00a65b8 100644 --- a/editor/libeditor/tests/test_contenteditable_text_input_handling.html +++ b/editor/libeditor/tests/test_contenteditable_text_input_handling.html @@ -34,8 +34,7 @@ function runTests() { aEvent.preventDefault(); // prevent the browser default behavior }, }; - var els = Services.els; - els.addSystemEventListener(window, "keypress", listener, false); + SpecialPowers.wrap(window).addEventListener("keypress", listener, { mozSystemGroup: true }); var staticContent = document.getElementById("static"); staticContent._defaultValue = getTextValue(staticContent); @@ -306,7 +305,7 @@ function runTests() { testTextInput(inputInEditor); testTextInput(textareaInEditor); - els.removeSystemEventListener(window, "keypress", listener, false); + SpecialPowers.wrap(window).removeEventListener("keypress", listener, { mozSystemGroup: true }); SimpleTest.finish(); } diff --git a/editor/libeditor/tests/test_dragdrop.html b/editor/libeditor/tests/test_dragdrop.html index 6295661faa..5c7d940928 100644 --- a/editor/libeditor/tests/test_dragdrop.html +++ b/editor/libeditor/tests/test_dragdrop.html @@ -3101,7 +3101,7 @@ async function doTest() { beforeinputEvents = []; inputEvents = []; dragEvents = []; - const onDragStart = aEvent => { + const onDragStart = () => { input.style.display = "none"; document.documentElement.scrollTop; input.style.display = ""; @@ -3147,7 +3147,7 @@ async function doTest() { beforeinputEvents = []; inputEvents = []; dragEvents = []; - const onDragStart = aEvent => { + const onDragStart = () => { textarea.style.display = "none"; document.documentElement.scrollTop; textarea.style.display = ""; @@ -3193,13 +3193,13 @@ async function doTest() { beforeinputEvents = []; inputEvents = []; dragEvents = []; - const onMouseMove = aEvent => { + const onMouseMove = () => { input.style.display = "none"; document.documentElement.scrollTop; input.style.display = ""; document.documentElement.scrollTop; }; - const onMouseDown = aEvent => { + const onMouseDown = () => { document.addEventListener("mousemove", onMouseMove, {once: true}); } const onDrop = aEvent => { @@ -3243,13 +3243,13 @@ async function doTest() { beforeinputEvents = []; inputEvents = []; dragEvents = []; - const onMouseMove = aEvent => { + const onMouseMove = () => { textarea.style.display = "none"; document.documentElement.scrollTop; textarea.style.display = ""; document.documentElement.scrollTop; }; - const onMouseDown = aEvent => { + const onMouseDown = () => { document.addEventListener("mousemove", onMouseMove, {once: true}); } const onDrop = aEvent => { diff --git a/editor/libeditor/tests/test_execCommandPaste_noTarget.html b/editor/libeditor/tests/test_execCommandPaste_noTarget.html index 6586ca768d..70de112a78 100644 --- a/editor/libeditor/tests/test_execCommandPaste_noTarget.html +++ b/editor/libeditor/tests/test_execCommandPaste_noTarget.html @@ -32,7 +32,7 @@ // Check that reading text from the clipboard in non-privileged contexts // still doesn't work. - function onpstfail(e) { + function onpstfail() { ok(false, "Should not see paste event triggered by non-privileged call"); } document.addEventListener("paste", onpstfail); diff --git a/editor/libeditor/tests/test_htmleditor_keyevent_handling.html b/editor/libeditor/tests/test_htmleditor_keyevent_handling.html index 58666beb35..6fb79212e2 100644 --- a/editor/libeditor/tests/test_htmleditor_keyevent_handling.html +++ b/editor/libeditor/tests/test_htmleditor_keyevent_handling.html @@ -79,8 +79,8 @@ async function runTests() { getDesciption(aPreventedOnBubbling) + "prevented on bubbling phase"); } - SpecialPowers.addSystemEventListener(window, "keypress", listener, true); - SpecialPowers.addSystemEventListener(window, "keypress", listener, false); + SpecialPowers.wrap(window).addEventListener("keypress", listener, { capture: true, mozSystemGroup: true }); + SpecialPowers.wrap(window).addEventListener("keypress", listener, { capture: false, mozSystemGroup: true }); // eslint-disable-next-line complexity async function doTest( @@ -754,8 +754,8 @@ async function runTests() { await doTest(htmlEditor, "readonly and non-tabbable HTML editor but plaintext mode", true, false, true); - SpecialPowers.removeSystemEventListener(window, "keypress", listener, true); - SpecialPowers.removeSystemEventListener(window, "keypress", listener, false); + SpecialPowers.wrap(window).removeEventListener("keypress", listener, { capture: true, mozSystemGroup: true }); + SpecialPowers.wrap(window).removeEventListener("keypress", listener, { capture: false, mozSystemGroup: true }); SimpleTest.finish(); } diff --git a/editor/libeditor/tests/test_middle_click_paste.html b/editor/libeditor/tests/test_middle_click_paste.html index eaa918c194..d34411e54b 100644 --- a/editor/libeditor/tests/test_middle_click_paste.html +++ b/editor/libeditor/tests/test_middle_click_paste.html @@ -224,7 +224,7 @@ async function doTextareaTests(aTextarea) { aTextarea.value = ""; let pasteEventCount = 0; - function pasteEventLogger(event) { + function pasteEventLogger() { pasteEventCount++; } aTextarea.addEventListener("paste", pasteEventLogger); @@ -381,7 +381,7 @@ async function doContenteditableTests(aEditableDiv) { aEditableDiv.innerHTML = ""; let pasteEventCount = 0; - function pasteEventLogger(event) { + function pasteEventLogger() { pasteEventCount++; } aEditableDiv.addEventListener("paste", pasteEventLogger); diff --git a/editor/libeditor/tests/test_nsIEditor_documentIsEmpty.html b/editor/libeditor/tests/test_nsIEditor_documentIsEmpty.html index 49c1db78a9..1e60e58191 100644 --- a/editor/libeditor/tests/test_nsIEditor_documentIsEmpty.html +++ b/editor/libeditor/tests/test_nsIEditor_documentIsEmpty.html @@ -80,7 +80,7 @@ ok(true, "nsIEditor.documentIsEmpty should throw an exception when no editing host has focus"); } document.querySelector("div[contenteditable]").focus(); - todo_is(getHTMLEditor().documentIsEmpty, true, + is(getHTMLEditor().documentIsEmpty, true, "nsIEditor.documentIsEmpty should be true when editing host does not have contents"); document.body.innerHTML = "<div contenteditable><br></div>"; diff --git a/editor/libeditor/tests/test_paste_redirect_focus_in_paste_event_listener.html b/editor/libeditor/tests/test_paste_redirect_focus_in_paste_event_listener.html index b82938158e..89d7531fa5 100644 --- a/editor/libeditor/tests/test_paste_redirect_focus_in_paste_event_listener.html +++ b/editor/libeditor/tests/test_paste_redirect_focus_in_paste_event_listener.html @@ -57,7 +57,7 @@ SimpleTest.waitForFocus(async () => { const editableElementDesc = `<${ editableElement.tagName.toLocaleLowerCase() }${editableElement.hasAttribute("contenteditable") ? " contenteditable" : ""}>`; - (test_from_editableElement_to_input => { + (() => { const input = document.querySelector("#dest > input"); editableElement.focus(); editableElement.addEventListener( @@ -84,7 +84,7 @@ SimpleTest.waitForFocus(async () => { input.value = ""; })(); - (test_from_editableElement_to_contenteditable => { + (() => { const contentEditable = document.querySelector("#dest > div[contenteditable]"); editableElement.focus(); editableElement.addEventListener( @@ -111,7 +111,7 @@ SimpleTest.waitForFocus(async () => { contentEditable.innerHTML = "<br>"; })(); - (test_from_editableElement_to_non_editable => { + (() => { const button = document.querySelector("#dest > button"); editableElement.focus(); editableElement.addEventListener( diff --git a/editor/libeditor/tests/test_texteditor_keyevent_handling.html b/editor/libeditor/tests/test_texteditor_keyevent_handling.html index 2c80181b3c..6b3d5f4608 100644 --- a/editor/libeditor/tests/test_texteditor_keyevent_handling.html +++ b/editor/libeditor/tests/test_texteditor_keyevent_handling.html @@ -80,10 +80,8 @@ async function runTests() { } var parentElement = document.getElementById("display"); - SpecialPowers.addSystemEventListener(parentElement, "keypress", listener, - true); - SpecialPowers.addSystemEventListener(parentElement, "keypress", listener, - false); + SpecialPowers.wrap(parentElement).addEventListener("keypress", listener, { capture: true, mozSystemGroup: true }); + SpecialPowers.wrap(parentElement).addEventListener("keypress", listener, { capture: false, mozSystemGroup: true }); async function doTest(aElement, aDescription, aIsSingleLine, aIsReadonly) { function reset(aText) { @@ -457,10 +455,8 @@ async function runTests() { textarea.setAttribute("readonly", "readonly"); await doTest(textarea, "<textarea readonly>", false, true); - SpecialPowers.removeSystemEventListener(parentElement, "keypress", listener, - true); - SpecialPowers.removeSystemEventListener(parentElement, "keypress", listener, - false); + SpecialPowers.wrap(parentElement).removeEventListener("keypress", listener, { capture: true, mozSystemGroup: true }); + SpecialPowers.wrap(parentElement).removeEventListener("keypress", listener, { capture: false, mozSystemGroup: true }); SimpleTest.finish(); } diff --git a/editor/libeditor/tests/test_texteditor_wrapping_long_line.html b/editor/libeditor/tests/test_texteditor_wrapping_long_line.html index 14a445bbb0..013e563a27 100644 --- a/editor/libeditor/tests/test_texteditor_wrapping_long_line.html +++ b/editor/libeditor/tests/test_texteditor_wrapping_long_line.html @@ -11,7 +11,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1733878 <script src="/tests/SimpleTest/EventUtils.js"></script> <script> /** Test for bug 1733878 **/ - window.addEventListener("DOMContentLoaded", (event) => { + window.addEventListener("DOMContentLoaded", () => { SimpleTest.waitForExplicitFinish(); SimpleTest.waitForFocus(function() { document.body.textContent = ""; // It would be \n\n otherwise... |