Source/core/editing/DeleteSelectionCommand.cpp
// If a selection starts in one block and ends in another, we have to merge to bring content before the
// start together with content after the end.
If a selection starts in one block and ends in another, we have to merge to bring content before the
start together with content after the end.
If a selection starts in one block and ends in another we have to merge to bring content before the start together with content after the end .
void DeleteSelectionCommand::mergeParagraphs()
{
    if (!m_mergeBlocksAfterDelete) {
        if (m_pruneStartBlockIfNecessary) {
            prune(m_startBlock);
            m_needPlaceholder = false;
        }
        return;
    }
    ASSERT(!m_pruneStartBlockIfNecessary);
    if (!m_downstreamEnd.inDocument() || !m_upstreamStart.inDocument())
         return;
    if (comparePositions(m_upstreamStart, m_downstreamEnd) > 0)
        return;
    if (m_upstreamStart == m_downstreamEnd)
        return;
    VisiblePosition startOfParagraphToMove(m_downstreamEnd);
    VisiblePosition mergeDestination(m_upstreamStart);
    Element* endBlock = enclosingBlock(m_downstreamEnd.deprecatedNode());
    if (!endBlock || !endBlock->contains(startOfParagraphToMove.deepEquivalent().deprecatedNode()) || !startOfParagraphToMove.deepEquivalent().deprecatedNode()) {
        removeNode(enclosingBlock(m_downstreamEnd.deprecatedNode()));
        return;
    }
    if (!mergeDestination.deepEquivalent().deprecatedNode() || (!mergeDestination.deepEquivalent().deprecatedNode()->isDescendantOf(enclosingBlock(m_upstreamStart.containerNode())) && (!mergeDestination.deepEquivalent().anchorNode()->firstChild() || !m_upstreamStart.containerNode()->firstChild())) || (m_startsAtEmptyLine && mergeDestination != startOfParagraphToMove)) {
        insertNodeAt(createBreakElement(document()).get(), m_upstreamStart);
        mergeDestination = VisiblePosition(m_upstreamStart);
    }
    if (mergeDestination == startOfParagraphToMove)
        return;
    VisiblePosition endOfParagraphToMove = endOfParagraph(startOfParagraphToMove, CanSkipOverEditingBoundary);
    if (mergeDestination == endOfParagraphToMove)
        return;
    Node* listItemInFirstParagraph = enclosingNodeOfType(m_upstreamStart, isListItem);
    Node* listItemInSecondParagraph = enclosingNodeOfType(m_downstreamEnd, isListItem);
    if (listItemInFirstParagraph && listItemInSecondParagraph
        && listItemInFirstParagraph->parentElement() != listItemInSecondParagraph->parentElement()
        && canMergeLists(listItemInFirstParagraph->parentElement(), listItemInSecondParagraph->parentElement())) {
        mergeIdenticalElements(listItemInFirstParagraph->parentElement(), listItemInSecondParagraph->parentElement());
        m_endingPosition = mergeDestination.deepEquivalent();
        return;
    }
    if (!m_startsAtEmptyLine && isStartOfParagraph(mergeDestination) && startOfParagraphToMove.absoluteCaretBounds().x() > mergeDestination.absoluteCaretBounds().x()) {
        if (isHTMLBRElement(*mergeDestination.deepEquivalent().downstream().deprecatedNode())) {
            removeNodeAndPruneAncestors(mergeDestination.deepEquivalent().downstream().deprecatedNode());
            m_endingPosition = startOfParagraphToMove.deepEquivalent();
            return;
        }
    }
    if (isRenderedAsNonInlineTableImageOrHR(startOfParagraphToMove.deepEquivalent().deprecatedNode()) && !isStartOfParagraph(mergeDestination)) {
        m_endingPosition = m_upstreamStart;
        return;
    }
    bool needPlaceholder = m_needPlaceholder;
    bool paragraphToMergeIsEmpty = (startOfParagraphToMove == endOfParagraphToMove);
    moveParagraph(startOfParagraphToMove, endOfParagraphToMove, mergeDestination, false, !paragraphToMergeIsEmpty);
    m_needPlaceholder = needPlaceholder;
    m_endingPosition = endingSelection().start();
}
void DeleteSelectionCommand :: mergeParagraphs ( ) { if ( ! m_mergeBlocksAfterDelete ) { if ( m_pruneStartBlockIfNecessary ) { prune ( m_startBlock ) ; m_needPlaceholder = false ; } return ; } ASSERT ( ! m_pruneStartBlockIfNecessary ) ; if ( ! m_downstreamEnd . inDocument ( ) || ! m_upstreamStart . inDocument ( ) ) return ; if ( comparePositions ( m_upstreamStart , m_downstreamEnd ) > 0 ) return ; if ( m_upstreamStart == m_downstreamEnd ) return ; VisiblePosition startOfParagraphToMove ( m_downstreamEnd ) ; VisiblePosition mergeDestination ( m_upstreamStart ) ; Element * endBlock = enclosingBlock ( m_downstreamEnd . deprecatedNode ( ) ) ; if ( ! endBlock || ! endBlock -> contains ( startOfParagraphToMove . deepEquivalent ( ) . deprecatedNode ( ) ) || ! startOfParagraphToMove . deepEquivalent ( ) . deprecatedNode ( ) ) { removeNode ( enclosingBlock ( m_downstreamEnd . deprecatedNode ( ) ) ) ; return ; } if ( ! mergeDestination . deepEquivalent ( ) . deprecatedNode ( ) || ( ! mergeDestination . deepEquivalent ( ) . deprecatedNode ( ) -> isDescendantOf ( enclosingBlock ( m_upstreamStart . containerNode ( ) ) ) && ( ! mergeDestination . deepEquivalent ( ) . anchorNode ( ) -> firstChild ( ) || ! m_upstreamStart . containerNode ( ) -> firstChild ( ) ) ) || ( m_startsAtEmptyLine && mergeDestination != startOfParagraphToMove ) ) { insertNodeAt ( createBreakElement ( document ( ) ) . get ( ) , m_upstreamStart ) ; mergeDestination = VisiblePosition ( m_upstreamStart ) ; } if ( mergeDestination == startOfParagraphToMove ) return ; VisiblePosition endOfParagraphToMove = endOfParagraph ( startOfParagraphToMove , CanSkipOverEditingBoundary ) ; if ( mergeDestination == endOfParagraphToMove ) return ; Node * listItemInFirstParagraph = enclosingNodeOfType ( m_upstreamStart , isListItem ) ; Node * listItemInSecondParagraph = enclosingNodeOfType ( m_downstreamEnd , isListItem ) ; if ( listItemInFirstParagraph && listItemInSecondParagraph && listItemInFirstParagraph -> parentElement ( ) != listItemInSecondParagraph -> parentElement ( ) && canMergeLists ( listItemInFirstParagraph -> parentElement ( ) , listItemInSecondParagraph -> parentElement ( ) ) ) { mergeIdenticalElements ( listItemInFirstParagraph -> parentElement ( ) , listItemInSecondParagraph -> parentElement ( ) ) ; m_endingPosition = mergeDestination . deepEquivalent ( ) ; return ; } if ( ! m_startsAtEmptyLine && isStartOfParagraph ( mergeDestination ) && startOfParagraphToMove . absoluteCaretBounds ( ) . x ( ) > mergeDestination . absoluteCaretBounds ( ) . x ( ) ) { if ( isHTMLBRElement ( * mergeDestination . deepEquivalent ( ) . downstream ( ) . deprecatedNode ( ) ) ) { removeNodeAndPruneAncestors ( mergeDestination . deepEquivalent ( ) . downstream ( ) . deprecatedNode ( ) ) ; m_endingPosition = startOfParagraphToMove . deepEquivalent ( ) ; return ; } } if ( isRenderedAsNonInlineTableImageOrHR ( startOfParagraphToMove . deepEquivalent ( ) . deprecatedNode ( ) ) && ! isStartOfParagraph ( mergeDestination ) ) { m_endingPosition = m_upstreamStart ; return ; } bool needPlaceholder = m_needPlaceholder ; bool paragraphToMergeIsEmpty = ( startOfParagraphToMove == endOfParagraphToMove ) ; moveParagraph ( startOfParagraphToMove , endOfParagraphToMove , mergeDestination , false , ! paragraphToMergeIsEmpty ) ; m_needPlaceholder = needPlaceholder ; m_endingPosition = endingSelection ( ) . start ( ) ; }
If a selection starts in one block and ends in another, we have to merge to bring content before the
start together with content after the end.
If a selection starts in one block and ends in another we have to merge to bring content before the start together with content after the end .
// We aren't going to merge into the start block, so remove it if it's empty. // Removing the start block during a deletion is usually an indication that we need // a placeholder, but not in this case. // It shouldn't have been asked to both try and merge content into the start block and prune it. // FIXME: Deletion should adjust selection endpoints as it removes nodes so that we never get into this state (4099839). // FIXME: The deletion algorithm shouldn't let this happen. // There's nothing to merge. // m_downstreamEnd's block has been emptied out by deletion.  There is no content inside of it to // move, so just remove it. // We need to merge into m_upstreamStart's block, but it's been emptied out and collapsed by deletion. // If the merge destination and source to be moved are both list items of different lists, merge them into single list. // The rule for merging into an empty block is: only do so if its farther to the right. // FIXME: Consider RTL. // Block images, tables and horizontal rules cannot be made inline with content at mergeDestination.  If there is // any (!isStartOfParagraph(mergeDestination)), don't merge, just move the caret to just before the selection we deleted. // See https://bugs.webkit.org/show_bug.cgi?id=25439 // moveParagraphs will insert placeholders if it removes blocks that would require their use, don't let block // removals that it does cause the insertion of *another* placeholder. // The endingPosition was likely clobbered by the move, so recompute it (moveParagraph selects the moved paragraph).