/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 sw=2 et tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
usingnamespace dom; using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; using InvisibleWhiteSpaces = HTMLEditUtils::InvisibleWhiteSpaces; using LeafNodeType = HTMLEditUtils::LeafNodeType; using ScanLineBreak = HTMLEditUtils::ScanLineBreak; using TableBoundary = HTMLEditUtils::TableBoundary; using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
/** * Deletes content in or around aRangesToDelete. * NOTE: This method creates SelectionBatcher. Therefore, each caller * needs to check if the editor is still available even if this returns * NS_OK.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run(
HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
nsIEditor::EStripWrappers aStripWrappers,
AutoClonedSelectionRangeArray& aRangesToDelete, const Element& aEditingHost);
/** * HandleDeleteAroundCollapsedRanges() handles deletion with collapsed * ranges. Callers must guarantee that this is called only when * aRangesToDelete.IsCollapsed() returns true. * * @param aDirectionAndAmount Direction of the deletion. * @param aStripWrappers Must be eStrip or eNoStrip. * @param aRangesToDelete Ranges to delete. This `IsCollapsed()` must * return true. * @param aWSRunScannerAtCaret Scanner instance which scanned from * caret point. * @param aScanFromCaretPointResult Scan result of aWSRunScannerAtCaret * toward aDirectionAndAmount. * @param aEditingHost The editing host.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
HandleDeleteAroundCollapsedRanges(
HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
nsIEditor::EStripWrappers aStripWrappers,
AutoClonedSelectionRangeArray& aRangesToDelete, const WSRunScanner& aWSRunScannerAtCaret, const WSScanResult& aScanFromCaretPointResult, const Element& aEditingHost);
nsresult ComputeRangesToDeleteAroundCollapsedRanges( const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
AutoClonedSelectionRangeArray& aRangesToDelete, const WSRunScanner& aWSRunScannerAtCaret, const WSScanResult& aScanFromCaretPointResult, const Element& aEditingHost) const;
/** * HandleDeleteNonCollapsedRanges() handles deletion with non-collapsed * ranges. Callers must guarantee that this is called only when * aRangesToDelete.IsCollapsed() returns false. * * @param aDirectionAndAmount Direction of the deletion. * @param aStripWrappers Must be eStrip or eNoStrip. * @param aRangesToDelete The ranges to delete. * @param aSelectionWasCollapsed If the caller extended `Selection` * from collapsed, set this to `Yes`. * Otherwise, i.e., `Selection` is not * collapsed from the beginning, set * this to `No`. * @param aEditingHost The editing host.
*/ enumclass SelectionWasCollapsed { Yes, No };
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
HandleDeleteNonCollapsedRanges(HTMLEditor& aHTMLEditor,
nsIEditor::EDirection aDirectionAndAmount,
nsIEditor::EStripWrappers aStripWrappers,
AutoClonedSelectionRangeArray& aRangesToDelete,
SelectionWasCollapsed aSelectionWasCollapsed, const Element& aEditingHost);
nsresult ComputeRangesToDeleteNonCollapsedRanges( const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
AutoClonedSelectionRangeArray& aRangesToDelete,
SelectionWasCollapsed aSelectionWasCollapsed, const Element& aEditingHost) const;
/** * Handle deletion of collapsed ranges in a text node. * * @param aDirectionAndAmount Must be eNext or ePrevious. * @param aCaretPosition The position where caret is. This container * must be a text node. * @param aEditingHost The editing host.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
HandleDeleteTextAroundCollapsedRanges(
HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
AutoClonedSelectionRangeArray& aRangesToDelete, const Element& aEditingHost);
nsresult ComputeRangesToDeleteTextAroundCollapsedRanges(
nsIEditor::EDirection aDirectionAndAmount,
AutoClonedSelectionRangeArray& aRangesToDelete) const;
/** * Handles deletion of collapsed selection at white-spaces in a text node. * * @param aDirectionAndAmount Direction of the deletion. * @param aPointToDelete The point to delete. I.e., typically, caret * position. * @param aEditingHost The editing host.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
HandleDeleteCollapsedSelectionAtWhiteSpaces(
HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, const EditorDOMPoint& aPointToDelete, const Element& aEditingHost);
/** * Handle deletion of collapsed selection in a text node. * * @param aDirectionAndAmount Direction of the deletion. * @param aRangesToDelete Computed selection ranges to delete. * @param aPointAtDeletingChar The visible char position which you want to * delete. * @param aEditingHost The editing host.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
HandleDeleteCollapsedSelectionAtVisibleChar(
HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
AutoClonedSelectionRangeArray& aRangesToDelete, const EditorDOMPoint& aPointAtDeletingChar, const Element& aEditingHost);
/** * Handle deletion of atomic elements like <br>, <hr>, <img>, <input>, etc and * data nodes except text node (e.g., comment node). Note that don't call this * directly with `<hr>` element. * * @param aAtomicContent The atomic content to be deleted. * @param aCaretPoint The caret point (i.e., selection start or * end). * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized * with the caret point. * @param aEditingHost The editing host.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
HandleDeleteAtomicContent(HTMLEditor& aHTMLEditor, nsIContent& aAtomicContent, const EditorDOMPoint& aCaretPoint, const WSRunScanner& aWSRunScannerAtCaret, const Element& aEditingHost);
nsresult ComputeRangesToDeleteAtomicContent( const nsIContent& aAtomicContent,
AutoClonedSelectionRangeArray& aRangesToDelete) const;
/** * GetAtomicContnetToDelete() returns better content that is deletion of * atomic element. If aScanFromCaretPointResult is special, since this * point may not be editable, we look for better point to remove atomic * content. * * @param aDirectionAndAmount Direction of the deletion. * @param aWSRunScannerAtCaret WSRunScanner instance which was * initialized with the caret point. * @param aScanFromCaretPointResult Scan result of aWSRunScannerAtCaret * toward aDirectionAndAmount.
*/ static nsIContent* GetAtomicContentToDelete(
nsIEditor::EDirection aDirectionAndAmount, const WSRunScanner& aWSRunScannerAtCaret, const WSScanResult& aScanFromCaretPointResult) MOZ_NONNULL_RETURN;
/** * HandleDeleteAtOtherBlockBoundary() handles deletion at other block boundary * (i.e., immediately before or after a block). If this does not join blocks, * `Run()` may be called recursively with creating another instance. * * @param aDirectionAndAmount Direction of the deletion. * @param aStripWrappers Must be eStrip or eNoStrip. * @param aOtherBlockElement The block element which follows the caret or * is followed by caret. * @param aCaretPoint The caret point (i.e., selection start or * end). * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized * with the caret point. * @param aRangesToDelete Ranges to delete of the caller. This should * be collapsed and the point should match with * aCaretPoint. * @param aEditingHost The editing host.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
HandleDeleteAtOtherBlockBoundary(
HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
nsIEditor::EStripWrappers aStripWrappers, Element& aOtherBlockElement, const EditorDOMPoint& aCaretPoint, WSRunScanner& aWSRunScannerAtCaret,
AutoClonedSelectionRangeArray& aRangesToDelete, const Element& aEditingHost);
/** * ExtendOrShrinkRangeToDelete() extends aRangeToDelete if there are * an invisible <br> element and/or some parent empty elements. * * @param aLimitersAndCaretData The frame selection data. * @param aRangeToDelete The range to be extended for deletion. This * must not be collapsed, must be positioned.
*/ template <typename EditorDOMRangeType>
Result<EditorRawDOMRange, nsresult> ExtendOrShrinkRangeToDelete( const HTMLEditor& aHTMLEditor, const LimitersAndCaretData& aLimitersAndCaretData, const EditorDOMRangeType& aRangeToDelete) const;
/** * Extend the start boundary of aRangeToDelete to contain ancestor inline * elements which will be empty once the content in aRangeToDelete is removed * from the tree. * * NOTE: This is designed for deleting inline elements which become empty if * aRangeToDelete which crosses a block boundary of right block child. * Therefore, you may need to improve this method if you want to use this in * the other cases. * * @param aRangeToDelete [in/out] The range to delete. This start * boundary may be modified. * @param aEditingHost The editing host. * @return true if aRangeToDelete is modified. * false if aRangeToDelete is not modified. * error if aRangeToDelete gets unexpected * situation.
*/ static Result<bool, nsresult>
ExtendRangeToContainAncestorInlineElementsAtStart(
nsRange& aRangeToDelete, const Element& aEditingHost);
/** * A helper method for ExtendOrShrinkRangeToDelete(). This returns shrunken * range if aRangeToDelete selects all over list elements which have some list * item elements to avoid to delete all list items from the list element.
*/
MOZ_NEVER_INLINE_DEBUG static EditorRawDOMRange
GetRangeToAvoidDeletingAllListItemsIfSelectingAllOverListElements( const EditorRawDOMRange& aRangeToDelete);
/** * If aContent is a text node that contains only collapsed white-space or * empty and editable.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
DeleteNodeIfInvisibleAndEditableTextNode(HTMLEditor& aHTMLEditor,
nsIContent& aContent);
/** * DeleteParentBlocksIfEmpty() removes parent block elements if they * don't have visible contents. Note that due performance issue of * WhiteSpaceVisibilityKeeper, this call may be expensive. And also note that * this removes a empty block with a transaction. So, please make sure that * you've already created `AutoPlaceholderBatch`. * * @param aPoint The point whether this method climbing up the DOM * tree to remove empty parent blocks. * @return NS_OK if one or more empty block parents are deleted. * NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND if the point is * not in empty block. * Or NS_ERROR_* if something unexpected occurs.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
DeleteParentBlocksWithTransactionIfEmpty(HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint);
/** * Compute target range(s) which will be called by * `EditorBase::DeleteRangeWithTransaction()` or * `HTMLEditor::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,
AutoClonedSelectionRangeArray& 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;
}
/** * PrepareToDeleteAtCurrentBlockBoundary() considers left content and right * content which are joined for handling deletion at current block boundary * (i.e., at start or end of the current block). * * @param aHTMLEditor The HTML editor. * @param aDirectionAndAmount Direction of the deletion. * @param aCurrentBlockElement The current block element. * @param aCaretPoint The caret point (i.e., selection start * or end). * @param aEditingHost The editing host. * @return true if can continue to handle the * deletion.
*/ bool PrepareToDeleteAtCurrentBlockBoundary( const HTMLEditor& aHTMLEditor,
nsIEditor::EDirection aDirectionAndAmount,
Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint, const Element& aEditingHost);
/** * PrepareToDeleteAtOtherBlockBoundary() considers left content and right * content which are joined for handling deletion at other block boundary * (i.e., immediately before or after a block). * * @param aHTMLEditor The HTML editor. * @param aDirectionAndAmount Direction of the deletion. * @param aOtherBlockElement The block element which follows the * caret or is followed by caret. * @param aCaretPoint The caret point (i.e., selection start * or end). * @param aWSRunScannerAtCaret WSRunScanner instance which was * initialized with the caret point. * @return true if can continue to handle the * deletion.
*/ bool PrepareToDeleteAtOtherBlockBoundary( const HTMLEditor& aHTMLEditor,
nsIEditor::EDirection aDirectionAndAmount, Element& aOtherBlockElement, const EditorDOMPoint& aCaretPoint, const WSRunScanner& aWSRunScannerAtCaret);
/** * PrepareToDeleteNonCollapsedRange() considers left block element and * right block element which are inclusive ancestor block element of * start and end container of aRangeToDelete * * @param aHTMLEditor The HTML editor. * @param aRangeToDelete The range to delete. Must not be * collapsed. * @param aEditingHost The editing host. * @return true if can continue to handle the * deletion.
*/ bool PrepareToDeleteNonCollapsedRange(const HTMLEditor& aHTMLEditor, const nsRange& aRangeToDelete, const Element& aEditingHost);
/** * Run() executes the joining. * * @param aHTMLEditor The HTML editor. * @param aDirectionAndAmount Direction of the deletion. * @param aStripWrappers Must be eStrip or eNoStrip. * @param aCaretPoint The caret point (i.e., selection start * or end). * @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, nsRange& aRangeToDelete, const Element& aEditingHost) { switch (mMode) { case Mode::JoinCurrentBlock: {
Result<EditActionResult, nsresult> result =
HandleDeleteAtCurrentBlockBoundary(
aHTMLEditor, aDirectionAndAmount, aCaretPoint, aEditingHost);
NS_WARNING_ASSERTION(result.isOk(), "AutoBlockElementsJoiner::" "HandleDeleteAtCurrentBlockBoundary() failed"); return result;
} case Mode::JoinOtherBlock: {
Result<EditActionResult, nsresult> result =
HandleDeleteAtOtherBlockBoundary(aHTMLEditor, aDirectionAndAmount,
aStripWrappers, aCaretPoint,
aRangeToDelete, aEditingHost);
NS_WARNING_ASSERTION(result.isOk(), "AutoBlockElementsJoiner::" "HandleDeleteAtOtherBlockBoundary() failed"); return result;
} case Mode::DeleteBRElement: case Mode::DeletePrecedingBRElementOfBlock: case Mode::DeletePrecedingPreformattedLineBreak: {
Result<EditActionResult, nsresult> result = HandleDeleteLineBreak(
aHTMLEditor, aDirectionAndAmount, aCaretPoint, aEditingHost);
NS_WARNING_ASSERTION(
result.isOk(), "AutoBlockElementsJoiner::HandleDeleteLineBreak() failed"); return result;
} case Mode::JoinBlocksInSameParent: case Mode::DeleteContentInRange: case Mode::DeleteNonCollapsedRange: case Mode::DeletePrecedingLinesAndContentInRange:
MOZ_ASSERT_UNREACHABLE( "This mode should be handled in the other Run()"); return Err(NS_ERROR_UNEXPECTED); case Mode::NotInitialized: return EditActionResult::IgnoredResult();
} return Err(NS_ERROR_NOT_INITIALIZED);
}
nsresult ComputeRangeToDelete(const HTMLEditor& aHTMLEditor,
nsIEditor::EDirection aDirectionAndAmount, const EditorDOMPoint& aCaretPoint,
nsRange& aRangeToDelete, const Element& aEditingHost) const { switch (mMode) { case Mode::JoinCurrentBlock: {
nsresult rv = ComputeRangeToDeleteAtCurrentBlockBoundary(
aHTMLEditor, aCaretPoint, aRangeToDelete, aEditingHost);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "AutoBlockElementsJoiner::" "ComputeRangeToDeleteAtCurrentBlockBoundary() failed"); return rv;
} case Mode::JoinOtherBlock: {
nsresult rv = ComputeRangeToDeleteAtOtherBlockBoundary(
aHTMLEditor, aDirectionAndAmount, aCaretPoint, aRangeToDelete,
aEditingHost);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "AutoBlockElementsJoiner::" "ComputeRangeToDeleteAtOtherBlockBoundary() failed"); return rv;
} case Mode::DeleteBRElement: case Mode::DeletePrecedingBRElementOfBlock: case Mode::DeletePrecedingPreformattedLineBreak: {
nsresult rv = ComputeRangeToDeleteLineBreak(
aHTMLEditor, aRangeToDelete, aEditingHost,
ComputeRangeFor::GetTargetRanges);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoBlockElementsJoiner::" "ComputeRangeToDeleteLineBreak() failed"); return rv;
} case Mode::JoinBlocksInSameParent: case Mode::DeleteContentInRange: case Mode::DeleteNonCollapsedRange: case Mode::DeletePrecedingLinesAndContentInRange:
MOZ_ASSERT_UNREACHABLE( "This mode should be handled in the other " "ComputeRangesToDelete()"); return NS_ERROR_UNEXPECTED; case Mode::NotInitialized: return NS_OK;
} return NS_ERROR_NOT_IMPLEMENTED;
}
/** * Run() executes the joining. * * @param aHTMLEditor The HTML editor. * @param aLimitersAndCaretData The data copied from nsFrameSelection. * @param aDirectionAndAmount Direction of the deletion. * @param aStripWrappers Whether delete or keep new empty * ancestor elements. * @param aRangeToDelete The range to delete. Must not be * collapsed. * @param aSelectionWasCollapsed Whether selection was or was not * collapsed when starting to handle * deletion.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run(
HTMLEditor& aHTMLEditor, const LimitersAndCaretData& aLimitersAndCaretData,
nsIEditor::EDirection aDirectionAndAmount,
nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete,
AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, const Element& aEditingHost) { switch (mMode) { case Mode::JoinCurrentBlock: case Mode::JoinOtherBlock: case Mode::DeleteBRElement: case Mode::DeletePrecedingBRElementOfBlock: case Mode::DeletePrecedingPreformattedLineBreak:
MOZ_ASSERT_UNREACHABLE( "This mode should be handled in the other Run()"); return Err(NS_ERROR_UNEXPECTED); case Mode::JoinBlocksInSameParent: {
Result<EditActionResult, nsresult> result =
JoinBlockElementsInSameParent(
aHTMLEditor, aLimitersAndCaretData, aDirectionAndAmount,
aStripWrappers, aRangeToDelete, aSelectionWasCollapsed,
aEditingHost);
NS_WARNING_ASSERTION(result.isOk(), "AutoBlockElementsJoiner::" "JoinBlockElementsInSameParent() failed"); return result;
} case Mode::DeleteContentInRange: {
Result<EditActionResult, nsresult> result = DeleteContentInRange(
aHTMLEditor, aLimitersAndCaretData, aDirectionAndAmount,
aStripWrappers, aRangeToDelete, aEditingHost);
NS_WARNING_ASSERTION(
result.isOk(), "AutoBlockElementsJoiner::DeleteContentInRange() failed"); return result;
} case Mode::DeleteNonCollapsedRange: case Mode::DeletePrecedingLinesAndContentInRange: {
Result<EditActionResult, nsresult> result =
HandleDeleteNonCollapsedRange(
aHTMLEditor, aDirectionAndAmount, aStripWrappers,
aRangeToDelete, aSelectionWasCollapsed, aEditingHost);
NS_WARNING_ASSERTION(result.isOk(), "AutoBlockElementsJoiner::" "HandleDeleteNonCollapsedRange() failed"); return result;
} case Mode::NotInitialized:
MOZ_ASSERT_UNREACHABLE( "Call Run() after calling a preparation method"); return EditActionResult::IgnoredResult();
} return Err(NS_ERROR_NOT_INITIALIZED);
}
nsresult ComputeRangeToDelete( const HTMLEditor& aHTMLEditor, const AutoClonedSelectionRangeArray& aRangesToDelete,
nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete,
AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, const Element& aEditingHost) const { switch (mMode) { case Mode::JoinCurrentBlock: case Mode::JoinOtherBlock: case Mode::DeleteBRElement: case Mode::DeletePrecedingBRElementOfBlock: case Mode::DeletePrecedingPreformattedLineBreak:
MOZ_ASSERT_UNREACHABLE( "This mode should be handled in the other " "ComputeRangesToDelete()"); return NS_ERROR_UNEXPECTED; case Mode::JoinBlocksInSameParent: {
nsresult rv = ComputeRangeToJoinBlockElementsInSameParent(
aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aEditingHost);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "AutoBlockElementsJoiner::" "ComputeRangesToJoinBlockElementsInSameParent() failed"); return rv;
} case Mode::DeleteContentInRange: {
nsresult rv = ComputeRangeToDeleteContentInRange(
aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aEditingHost);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoBlockElementsJoiner::" "ComputeRangesToDeleteContentInRanges() failed"); return rv;
} case Mode::DeleteNonCollapsedRange: case Mode::DeletePrecedingLinesAndContentInRange: {
nsresult rv = ComputeRangeToDeleteNonCollapsedRange(
aHTMLEditor, aDirectionAndAmount, aRangeToDelete,
aSelectionWasCollapsed, aEditingHost);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "AutoBlockElementsJoiner::" "ComputeRangesToDeleteNonCollapsedRanges() failed"); return rv;
} case Mode::NotInitialized:
MOZ_ASSERT_UNREACHABLE( "Call ComputeRangesToDelete() after calling a preparation " "method"); return NS_ERROR_NOT_INITIALIZED;
} return NS_ERROR_NOT_INITIALIZED;
}
/** * JoinNodesDeepWithTransaction() joins aLeftNode and aRightNode "deeply". * First, they are joined simply, then, new right node is assumed as the * child at length of the left node before joined and new left node is * assumed as its previous sibling. Then, they will be joined again. * And then, these steps are repeated. * * @param aLeftContent The node which will be removed form the tree. * @param aRightContent The node which will be inserted the contents of * aRightContent. * @return The point of the first child of the last right * node. The result is always set if this succeeded.
*/
MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
JoinNodesDeepWithTransaction(HTMLEditor& aHTMLEditor,
nsIContent& aLeftContent,
nsIContent& aRightContent);
/** * DeleteNodesEntirelyInRangeButKeepTableStructure() removes each node in * aArrayOfContent. However, if some nodes are part of a table, removes all * children of them instead. I.e., this does not make damage to table * structure at the range, but may remove table entirely if it's in the * range.
*/
MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult>
DeleteNodesEntirelyInRangeButKeepTableStructure(
HTMLEditor& aHTMLEditor, const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContent,
PutCaretTo aPutCaretTo); bool NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure( const HTMLEditor& aHTMLEditor, const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed) const;
Result<bool, nsresult>
ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure( const HTMLEditor& aHTMLEditor, nsRange& aRange,
AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed) const;
/** * DeleteContentButKeepTableStructure() removes aContent if it's an element * which is part of a table structure. If it's a part of table structure, * removes its all children recursively. I.e., this may delete all of a * table, but won't break table structure partially. * * @param aContent The content which or whose all children should * be removed.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult>
DeleteContentButKeepTableStructure(HTMLEditor& aHTMLEditor,
nsIContent& aContent);
/** * DeleteTextAtStartAndEndOfRange() removes text if start and/or end of * aRange is in a text node.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult>
DeleteTextAtStartAndEndOfRange(HTMLEditor& aHTMLEditor, nsRange& aRange,
PutCaretTo aPutCaretTo);
/** * Return a block element which is an inclusive ancestor of the container of * aPoint if aPoint is start of ancestor blocks. For example, if `<div * id=div1>abc<div id=div2><div id=div3>[]def</div></div></div>`, return * #div2.
*/ template <typename EditorDOMPointType> static Result<Element*, nsresult>
GetMostDistantBlockAncestorIfPointIsStartAtBlock( const EditorDOMPointType& aPoint, const Element& aEditingHost, const Element* aAncestorLimiter = nullptr);
/** * Extend aRangeToDelete to contain new empty inline ancestors and contain * an invisible <br> element before right child block which causes an empty * line but the range starts after it.
*/ void ExtendRangeToDeleteNonCollapsedRange( const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete, const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const;
/** * Prepare for joining inclusive ancestor block elements. When this * returns false, the deletion should be canceled.
*/
Result<bool, nsresult> Prepare(const HTMLEditor& aHTMLEditor, const Element& aEditingHost);
/** * When this returns true, this can join the blocks with `Run()`.
*/ bool CanJoinBlocks() const { return mCanJoinBlocks; }
/** * When this returns true, `Run()` must return "ignored" so that * caller can skip calling `Run()`. This is available only when * `CanJoinBlocks()` returns `true`. * TODO: This should be merged into `CanJoinBlocks()` in the future.
*/ bool ShouldDeleteLeafContentInstead() const {
MOZ_ASSERT(CanJoinBlocks()); return mFallbackToDeleteLeafContent;
}
/** * ComputeRangesToDelete() extends aRangeToDelete includes the element * boundaries between joining blocks. If they won't be joined, this * collapses the range to aCaretPoint.
*/
nsresult ComputeRangeToDelete(const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint,
nsRange& aRangeToDelete) const;
/** * Join inclusive ancestor block elements which are found by preceding * 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 * list items together into one). * If the elements are not the same type, or one is a descendant of the * other, we instead destroy the right block placing its children into * left block.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult> Run(
HTMLEditor& aHTMLEditor, const Element& aEditingHost);
private: /** * This method returns true when * `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`, * `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()` and * `MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` handle it * with the `if` block of their main blocks.
*/ bool CanMergeLeftAndRightBlockElements() const { if (!IsSet()) { returnfalse;
} // `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()` if (mPointContainingTheOtherBlockElement.GetContainer() ==
mRightBlockElement) { return mNewListElementTagNameOfRightListElement.isSome();
} // `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()` if (mPointContainingTheOtherBlockElement.GetContainer() ==
mLeftBlockElement) { return mNewListElementTagNameOfRightListElement.isSome() &&
!mRightBlockElement->GetChildCount();
}
MOZ_ASSERT(!mPointContainingTheOtherBlockElement.IsSet()); // `MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` return mNewListElementTagNameOfRightListElement.isSome() ||
mLeftBlockElement->NodeInfo()->NameAtom() ==
mRightBlockElement->NodeInfo()->NameAtom();
}
enumclass Mode {
NotInitialized,
JoinCurrentBlock,
JoinOtherBlock,
JoinBlocksInSameParent,
DeleteBRElement, // The instance will handle only the <br> element immediately before a // block.
DeletePrecedingBRElementOfBlock, // The instance will handle only the preceding preformatted line break // before a block.
DeletePrecedingPreformattedLineBreak,
DeleteContentInRange,
DeleteNonCollapsedRange, // The instance will handle preceding lines of the right block and content // in the range in the right block.
DeletePrecedingLinesAndContentInRange,
};
AutoDeleteRangesHandler* mDeleteRangesHandler; const AutoDeleteRangesHandler& mDeleteRangesHandlerConst;
nsCOMPtr<nsIContent> mLeftContent;
nsCOMPtr<nsIContent> mRightContent;
nsCOMPtr<nsIContent> mLeafContentInOtherBlock; // mSkippedInvisibleContents stores all content nodes which are skipped at // scanning mLeftContent and mRightContent. The content nodes should be // removed at deletion.
AutoTArray<OwningNonNull<nsIContent>, 8> mSkippedInvisibleContents;
RefPtr<dom::HTMLBRElement> mBRElement;
EditorDOMPointInText mPreformattedLineBreak;
Mode mMode = Mode::NotInitialized;
}; // HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner
class MOZ_STACK_CLASS AutoEmptyBlockAncestorDeleter final { public: /** * ScanEmptyBlockInclusiveAncestor() scans an inclusive ancestor element * which is empty and a block element. Then, stores the result and * returns the found empty block element. * * @param aHTMLEditor The HTMLEditor. * @param aStartContent Start content to look for empty ancestors.
*/
[[nodiscard]] Element* ScanEmptyBlockInclusiveAncestor( const HTMLEditor& aHTMLEditor, nsIContent& aStartContent);
/** * Deletes found empty block element by `ScanEmptyBlockInclusiveAncestor()`. * If found one is a list item element, calls * `MaybeInsertBRElementBeforeEmptyListItemElement()` before deleting * the list item element. * If found empty ancestor is not a list item element, * `GetNewCaretPosition()` will be called to determine new caret position. * Finally, removes the empty block ancestor. * * @param aHTMLEditor The HTMLEditor. * @param aDirectionAndAmount If found empty ancestor block is a list item * element, this is ignored. Otherwise: * - If eNext, eNextWord or eToEndOfLine, * collapse Selection to after found empty * ancestor. * - If ePrevious, ePreviousWord or * eToBeginningOfLine, collapse Selection to * end of previous editable node. * - Otherwise, eNone is allowed but does * nothing. * @param aEditingHost The editing host.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult> Run(
HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, const Element& aEditingHost);
private: /** * MaybeReplaceSubListWithNewListItem() replaces * mEmptyInclusiveAncestorBlockElement with new list item element * (containing <br>) if: * - mEmptyInclusiveAncestorBlockElement is a list element * - The parent of mEmptyInclusiveAncestorBlockElement is a list element * - The parent becomes empty after deletion * If this does not perform the replacement, returns "ignored".
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult>
MaybeReplaceSubListWithNewListItem(HTMLEditor& aHTMLEditor);
/** * MaybeInsertBRElementBeforeEmptyListItemElement() inserts a `<br>` element * if `mEmptyInclusiveAncestorBlockElement` is a list item element which * is first editable element in its parent, and its grand parent is not a * list element, inserts a `<br>` element before the empty list item.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateLineBreakResult, nsresult>
MaybeInsertBRElementBeforeEmptyListItemElement(HTMLEditor& aHTMLEditor);
/** * GetNewCaretPosition() returns new caret position after deleting * `mEmptyInclusiveAncestorBlockElement`.
*/
[[nodiscard]] Result<CaretPoint, nsresult> GetNewCaretPosition( const HTMLEditor& aHTMLEditor,
nsIEditor::EDirection aDirectionAndAmount) const;
// First check for table selection mode. If so, hand off to table editor.
SelectedTableCellScanner scanner(aRangesToDelete); if (scanner.IsInTableCellSelectionMode()) { // If it's in table cell selection mode, we'll delete all childen in // the all selected table cell elements, if (scanner.ElementsRef().Length() == aRangesToDelete.Ranges().Length()) { return NS_OK;
} // but will ignore all ranges which does not select a table cell.
size_t removedRanges = 0; for (size_t i = 1; i < scanner.ElementsRef().Length(); i++) { if (HTMLEditUtils::GetTableCellElementIfOnlyOneSelected(
aRangesToDelete.Ranges()[i - removedRanges]) !=
scanner.ElementsRef()[i]) { // XXX Need to manage anchor-focus range too!
aRangesToDelete.Ranges().RemoveElementAt(i - removedRanges);
removedRanges++;
}
} return NS_OK;
}
aRangesToDelete.EnsureOnlyEditableRanges(*editingHost); if (aRangesToDelete.Ranges().IsEmpty()) {
NS_WARNING( "There is no range which we can delete entire of or around the caret"); return NS_ERROR_EDITOR_NO_EDITABLE_RANGE;
}
AutoDeleteRangesHandler deleteHandler; // Should we delete target ranges which cannot delete actually?
nsresult rv = deleteHandler.ComputeRangesToDelete(
*this, aDirectionAndAmount, aRangesToDelete, *editingHost);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "AutoDeleteRangesHandler::ComputeRangesToDelete() failed"); return rv;
}
// Remember that we did a selection deletion. Used by // CreateStyleForInsertText()
TopLevelEditSubActionDataRef().mDidDeleteSelection = true;
if (MOZ_UNLIKELY(IsEmpty())) { return EditActionResult::CanceledResult();
}
// First check for table selection mode. If so, hand off to table editor. if (HTMLEditUtils::IsInTableCellSelectionMode(SelectionRef())) {
nsresult rv = DeleteTableCellContentsWithTransaction(); if (NS_WARN_IF(Destroyed())) { return Err(NS_ERROR_EDITOR_DESTROYED);
} if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::DeleteTableCellContentsWithTransaction() failed"); return Err(rv);
} return EditActionResult::HandledResult();
}
AutoClonedSelectionRangeArray rangesToDelete(SelectionRef());
rangesToDelete.EnsureOnlyEditableRanges(*editingHost); // AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() need to use // NodeIsInLimiters() to extend the range for deletion. But if focus event // doesn't receive yet, ancestor hasn't been set yet. So we need to set // ancestor limiter to editing host, <body> or something else in such case. if (!rangesToDelete.GetAncestorLimiter()) {
rangesToDelete.SetAncestorLimiter(FindSelectionRoot(*editingHost));
} if (MOZ_UNLIKELY(rangesToDelete.Ranges().IsEmpty())) {
NS_WARNING( "There is no range which we can delete entire the ranges or around the " "caret"); return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE);
}
AutoDeleteRangesHandler deleteHandler;
Result<EditActionResult, nsresult> result = deleteHandler.Run(
*this, aDirectionAndAmount, aStripWrappers, rangesToDelete, *editingHost); if (MOZ_UNLIKELY(result.isErr()) || result.inspect().Canceled()) {
NS_WARNING_ASSERTION(result.isOk(), "AutoDeleteRangesHandler::Run() failed"); return result;
} return EditActionResult::HandledResult();
}
Result<CaretPoint, nsresult> result = EditorBase::DeleteRangesWithTransaction(
aDirectionAndAmount, aStripWrappers, aRangesToDelete); if (MOZ_UNLIKELY(result.isErr())) { return result;
}
constbool isDeleteSelection =
GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent;
EditorDOMPoint pointToPutCaret = result.unwrap().UnwrapCaretPoint();
{
AutoTrackDOMPoint trackCaretPoint(RangeUpdaterRef(), &pointToPutCaret); for (constauto& range : aRangesToDelete.Ranges()) { // Refer the start boundary of the range because it should be end of the // preceding content, but the end boundary may be in an ancestor when an // ancestor element of end boundary has already been deleted. if (MOZ_UNLIKELY(!range->IsPositioned() ||
!range->GetStartContainer()->IsContent())) { continue;
}
EditorDOMPoint pointToInsertLineBreak(range->StartRef()); // Don't remove empty inline elements in the plaintext-only mode because // nobody can restore the style again. if (aStripWrappers == nsIEditor::eStrip &&
!editingHost->IsContentEditablePlainTextOnly()) { const OwningNonNull<nsIContent> maybeEmptyContent =
*pointToInsertLineBreak.ContainerAs<nsIContent>();
Result<CaretPoint, nsresult> caretPointOrError =
DeleteEmptyInclusiveAncestorInlineElements(maybeEmptyContent,
*editingHost); if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
NS_WARNING( "HTMLEditor::DeleteEmptyInclusiveAncestorInlineElements() " "failed"); return caretPointOrError.propagateErr();
} if (NS_WARN_IF(!range->IsPositioned() ||
!range->GetStartContainer()->IsContent())) { continue;
}
caretPointOrError.unwrap().MoveCaretPointTo(
pointToInsertLineBreak, {SuggestCaret::OnlyIfHasSuggestion}); if (NS_WARN_IF(!pointToInsertLineBreak.IsSetAndValidInComposedDoc())) { continue;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.