Example #1
0
bool isStartOfDocument(const VisiblePosition &p)
{
    return p.isNotNull() && p.previous().isNull();
}
VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const VisiblePosition& visiblePos) const
{
    if (visiblePos.isNull())
        return VisiblePositionRange();

    // make sure we move off of a line end
    VisiblePosition nextVisiblePos = visiblePos.next();
    if (nextVisiblePos.isNull())
        return VisiblePositionRange();

    VisiblePosition startPosition = startOfLine(nextVisiblePos);

    // fetch for a valid line start position
    if (startPosition.isNull()) {
        startPosition = visiblePos;
        nextVisiblePos = nextVisiblePos.next();
    } else
        startPosition = updateAXLineStartForVisiblePosition(startPosition);

    VisiblePosition endPosition = endOfLine(nextVisiblePos);

    // as long as the position hasn't reached the end of the doc,  keep searching for a valid line end position
    // Unless the VisiblePosition is at the very end, there should always be a valid line range.  However, endOfLine will
    // return null for position by a floating object, since floating object doesn't really belong to any line.
    // This check will reposition the marker after the floating object, to ensure we get a line end.
    while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
        nextVisiblePos = nextVisiblePos.next();
        endPosition = endOfLine(nextVisiblePos);
    }

    return VisiblePositionRange(startPosition, endPosition);
}
void InsertListCommand::doApply()
{
    if (!endingSelection().isNonOrphanedCaretOrRange())
        return;

    if (!endingSelection().rootEditableElement())
        return;

    VisiblePosition visibleEnd = endingSelection().visibleEnd();
    VisiblePosition visibleStart = endingSelection().visibleStart();
    // When a selection ends at the start of a paragraph, we rarely paint
    // the selection gap before that paragraph, because there often is no gap.
    // In a case like this, it's not obvious to the user that the selection
    // ends "inside" that paragraph, so it would be confusing if InsertUn{Ordered}List
    // operated on that paragraph.
    // FIXME: We paint the gap before some paragraphs that are indented with left
    // margin/padding, but not others.  We should make the gap painting more consistent and
    // then use a left margin/padding rule here.
    if (visibleEnd.deepEquivalent() != visibleStart.deepEquivalent() && isStartOfParagraph(visibleEnd, CanSkipOverEditingBoundary)) {
        setEndingSelection(VisibleSelection(visibleStart, previousPositionOf(visibleEnd, CannotCrossEditingBoundary), endingSelection().isDirectional()));
        if (!endingSelection().rootEditableElement())
            return;
    }

    const HTMLQualifiedName& listTag = (m_type == OrderedList) ? olTag : ulTag;
    if (endingSelection().isRange()) {
        bool forceListCreation = false;
        VisibleSelection selection = selectionForParagraphIteration(endingSelection());
        ASSERT(selection.isRange());
        VisiblePosition startOfSelection = selection.visibleStart();
        VisiblePosition endOfSelection = selection.visibleEnd();
        VisiblePosition startOfLastParagraph = startOfParagraph(endOfSelection, CanSkipOverEditingBoundary);

        RefPtrWillBeRawPtr<Range> currentSelection = firstRangeOf(endingSelection());
        RefPtrWillBeRawPtr<ContainerNode> scopeForStartOfSelection = nullptr;
        RefPtrWillBeRawPtr<ContainerNode> scopeForEndOfSelection = nullptr;
        // FIXME: This is an inefficient way to keep selection alive because
        // indexForVisiblePosition walks from the beginning of the document to the
        // endOfSelection everytime this code is executed. But not using index is hard
        // because there are so many ways we can los eselection inside doApplyForSingleParagraph.
        int indexForStartOfSelection = indexForVisiblePosition(startOfSelection, scopeForStartOfSelection);
        int indexForEndOfSelection = indexForVisiblePosition(endOfSelection, scopeForEndOfSelection);

        if (startOfParagraph(startOfSelection, CanSkipOverEditingBoundary).deepEquivalent() != startOfLastParagraph.deepEquivalent()) {
            forceListCreation = !selectionHasListOfType(selection, listTag);

            VisiblePosition startOfCurrentParagraph = startOfSelection;
            while (inSameTreeAndOrdered(startOfCurrentParagraph, startOfLastParagraph) && !inSameParagraph(startOfCurrentParagraph, startOfLastParagraph, CanCrossEditingBoundary)) {
                // doApply() may operate on and remove the last paragraph of the selection from the document
                // if it's in the same list item as startOfCurrentParagraph.  Return early to avoid an
                // infinite loop and because there is no more work to be done.
                // FIXME(<rdar://problem/5983974>): The endingSelection() may be incorrect here.  Compute
                // the new location of endOfSelection and use it as the end of the new selection.
                if (!startOfLastParagraph.deepEquivalent().inDocument())
                    return;
                setEndingSelection(startOfCurrentParagraph);

                // Save and restore endOfSelection and startOfLastParagraph when necessary
                // since moveParagraph and movePragraphWithClones can remove nodes.
                if (!doApplyForSingleParagraph(forceListCreation, listTag, *currentSelection))
                    break;
                if (endOfSelection.isNull() || endOfSelection.isOrphan() || startOfLastParagraph.isNull() || startOfLastParagraph.isOrphan()) {
                    endOfSelection = visiblePositionForIndex(indexForEndOfSelection, scopeForEndOfSelection.get());
                    // If endOfSelection is null, then some contents have been deleted from the document.
                    // This should never happen and if it did, exit early immediately because we've lost the loop invariant.
                    ASSERT(endOfSelection.isNotNull());
                    if (endOfSelection.isNull() || !rootEditableElementOf(endOfSelection))
                        return;
                    startOfLastParagraph = startOfParagraph(endOfSelection, CanSkipOverEditingBoundary);
                }

                startOfCurrentParagraph = startOfNextParagraph(endingSelection().visibleStart());
            }
            setEndingSelection(endOfSelection);
        }
        doApplyForSingleParagraph(forceListCreation, listTag, *currentSelection);
        // Fetch the end of the selection, for the reason mentioned above.
        if (endOfSelection.isNull() || endOfSelection.isOrphan()) {
            endOfSelection = visiblePositionForIndex(indexForEndOfSelection, scopeForEndOfSelection.get());
            if (endOfSelection.isNull())
                return;
        }
        if (startOfSelection.isNull() || startOfSelection.isOrphan()) {
            startOfSelection = visiblePositionForIndex(indexForStartOfSelection, scopeForStartOfSelection.get());
            if (startOfSelection.isNull())
                return;
        }
        setEndingSelection(VisibleSelection(startOfSelection, endOfSelection, endingSelection().isDirectional()));
        return;
    }

    ASSERT(firstRangeOf(endingSelection()));
    doApplyForSingleParagraph(false, listTag, *firstRangeOf(endingSelection()));
}
Example #4
0
void Selection::setBase(const VisiblePosition& visiblePosition)
{
    m_base = visiblePosition.deepEquivalent();
    validate();
}
Example #5
0
void Selection::validate()
{
    // Move the selection to rendered positions, if possible.
    bool baseAndExtentEqual = m_base == m_extent;
    if (m_base.isNotNull()) {
        m_base = VisiblePosition(m_base, m_affinity).deepEquivalent();
        if (baseAndExtentEqual)
            m_extent = m_base;
    }
    if (m_extent.isNotNull() && !baseAndExtentEqual)
        m_extent = VisiblePosition(m_extent, m_affinity).deepEquivalent();

    // Make sure we do not have a dangling base or extent.
    if (m_base.isNull() && m_extent.isNull())
        m_baseIsFirst = true;
    else if (m_base.isNull()) {
        m_base = m_extent;
        m_baseIsFirst = true;
    } else if (m_extent.isNull()) {
        m_extent = m_base;
        m_baseIsFirst = true;
    } else {
        m_baseIsFirst = comparePositions(m_base, m_extent) <= 0;
    }

    if (m_baseIsFirst) {
        m_start = m_base;
        m_end = m_extent;
    } else {
        m_start = m_extent;
        m_end = m_base;
    }

    // Expand the selection if requested.
    switch (m_granularity) {
        case CharacterGranularity:
            // Don't do any expansion.
            break;
        case WordGranularity: {
            // General case: Select the word the caret is positioned inside of, or at the start of (RightWordIfOnBoundary).
            // Edge case: If the caret is after the last word in a soft-wrapped line or the last word in
            // the document, select that last word (LeftWordIfOnBoundary).
            // Edge case: If the caret is after the last word in a paragraph, select from the the end of the
            // last word to the line break (also RightWordIfOnBoundary);
            VisiblePosition start = VisiblePosition(m_start, m_affinity);
            VisiblePosition originalEnd(m_end, m_affinity);
            EWordSide side = RightWordIfOnBoundary;
            if (isEndOfDocument(start) || (isEndOfLine(start) && !isStartOfLine(start) && !isEndOfParagraph(start)))
                side = LeftWordIfOnBoundary;
            m_start = startOfWord(start, side).deepEquivalent();
            side = RightWordIfOnBoundary;
            if (isEndOfDocument(originalEnd) || (isEndOfLine(originalEnd) && !isStartOfLine(originalEnd) && !isEndOfParagraph(originalEnd)))
                side = LeftWordIfOnBoundary;
                
            VisiblePosition wordEnd(endOfWord(originalEnd, side));
            VisiblePosition end(wordEnd);
            
            if (isEndOfParagraph(originalEnd)) {
                // Select the paragraph break (the space from the end of a paragraph to the start of 
                // the next one) to match TextEdit.
                end = wordEnd.next();
                
                if (Node* table = isFirstPositionAfterTable(end)) {
                    // The paragraph break after the last paragraph in the last cell of a block table ends
                    // at the start of the paragraph after the table.
                    if (isBlock(table))
                        end = end.next(true);
                    else
                        end = wordEnd;
                }
                
                if (end.isNull())
                    end = wordEnd;
                    
            }
                
            m_end = end.deepEquivalent();
            break;
        }
        case SentenceGranularity: {
            m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent();
            m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent();
            break;
        }
        case LineGranularity: {
            m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent();
            VisiblePosition end = endOfLine(VisiblePosition(m_end, m_affinity));
            // If the end of this line is at the end of a paragraph, include the space 
            // after the end of the line in the selection.
            if (isEndOfParagraph(end)) {
                VisiblePosition next = end.next();
                if (next.isNotNull())
                    end = next;
            }
            m_end = end.deepEquivalent();
            break;
        }
        case LineBoundary:
            m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent();
            m_end = endOfLine(VisiblePosition(m_end, m_affinity)).deepEquivalent();
            break;
        case ParagraphGranularity: {
            VisiblePosition pos(m_start, m_affinity);
            if (isStartOfLine(pos) && isEndOfDocument(pos))
                pos = pos.previous();
            m_start = startOfParagraph(pos).deepEquivalent();
            VisiblePosition visibleParagraphEnd = endOfParagraph(VisiblePosition(m_end, m_affinity));
            
            // Include the "paragraph break" (the space from the end of this paragraph to the start
            // of the next one) in the selection.
            VisiblePosition end(visibleParagraphEnd.next());
             
            if (Node* table = isFirstPositionAfterTable(end)) {
                // The paragraph break after the last paragraph in the last cell of a block table ends
                // at the start of the paragraph after the table, not at the position just after the table.
                if (isBlock(table))
                    end = end.next(true);
                // There is no parargraph break after the last paragraph in the last cell of an inline table.
                else
                    end = visibleParagraphEnd;
            }
             
            if (end.isNull())
                end = visibleParagraphEnd;
                
            m_end = end.deepEquivalent();
            break;
        }
        case DocumentBoundary:
            m_start = startOfDocument(VisiblePosition(m_start, m_affinity)).deepEquivalent();
            m_end = endOfDocument(VisiblePosition(m_end, m_affinity)).deepEquivalent();
            break;
        case ParagraphBoundary:
            m_start = startOfParagraph(VisiblePosition(m_start, m_affinity)).deepEquivalent();
            m_end = endOfParagraph(VisiblePosition(m_end, m_affinity)).deepEquivalent();
            break;
        case SentenceBoundary:
            m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent();
            m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent();
            break;
    }
    
    // Make sure we do not have a dangling start or end.
    if (m_start.isNull())
        m_start = m_end;
    if (m_end.isNull())
        m_end = m_start;
    
    adjustForEditableContent();

    // adjust the state
    if (m_start.isNull()) {
        ASSERT(m_end.isNull());
        m_state = NONE;

        // enforce downstream affinity if not caret, as affinity only
        // makes sense for caret
        m_affinity = DOWNSTREAM;
    } else if (m_start == m_end || m_start.upstream() == m_end.upstream()) {
        m_state = CARET;
    } else {
        m_state = RANGE;

        // enforce downstream affinity if not caret, as affinity only
        // makes sense for caret
        m_affinity = DOWNSTREAM;

        // "Constrain" the selection to be the smallest equivalent range of nodes.
        // This is a somewhat arbitrary choice, but experience shows that it is
        // useful to make to make the selection "canonical" (if only for
        // purposes of comparing selections). This is an ideal point of the code
        // to do this operation, since all selection changes that result in a RANGE 
        // come through here before anyone uses it.
        m_start = m_start.downstream();
        m_end = m_end.upstream();
    }
}
// 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) {
            // We aren't going to merge into the start block, so remove it if it's empty.
            prune(m_startBlock);
            // Removing the start block during a deletion is usually an indication that we need
            // a placeholder, but not in this case.
            m_needPlaceholder = false;
        }
        return;
    }
    
    // It shouldn't have been asked to both try and merge content into the start block and prune it.
    ASSERT(!m_pruneStartBlockIfNecessary);

    // FIXME: Deletion should adjust selection endpoints as it removes nodes so that we never get into this state (4099839).
    if (!m_downstreamEnd.anchorNode()->inDocument() || !m_upstreamStart.anchorNode()->inDocument())
         return;
         
    // FIXME: The deletion algorithm shouldn't let this happen.
    if (comparePositions(m_upstreamStart, m_downstreamEnd) > 0)
        return;
        
    // There's nothing to merge.
    if (m_upstreamStart == m_downstreamEnd)
        return;
        
    VisiblePosition startOfParagraphToMove(m_downstreamEnd);
    VisiblePosition mergeDestination(m_upstreamStart);
    
    // m_downstreamEnd's block has been emptied out by deletion.  There is no content inside of it to
    // move, so just remove it.
    Element* endBlock = static_cast<Element*>(enclosingBlock(m_downstreamEnd.deprecatedNode()));
    if (!endBlock || !endBlock->contains(startOfParagraphToMove.deepEquivalent().deprecatedNode()) || !startOfParagraphToMove.deepEquivalent().deprecatedNode()) {
        removeNode(enclosingBlock(m_downstreamEnd.deprecatedNode()));
        return;
    }
    
    // We need to merge into m_upstreamStart's block, but it's been emptied out and collapsed by deletion.
    if (!mergeDestination.deepEquivalent().deprecatedNode() || !mergeDestination.deepEquivalent().deprecatedNode()->isDescendantOf(enclosingBlock(m_upstreamStart.containerNode())) || m_startsAtEmptyLine) {
        insertNodeAt(createBreakElement(document()).get(), m_upstreamStart);
        mergeDestination = VisiblePosition(m_upstreamStart);
    }
    
    if (mergeDestination == startOfParagraphToMove)
        return;
        
    VisiblePosition endOfParagraphToMove = endOfParagraph(startOfParagraphToMove);
    
    if (mergeDestination == endOfParagraphToMove)
        return;
    
    // The rule for merging into an empty block is: only do so if its farther to the right.
    // FIXME: Consider RTL.
    if (!m_startsAtEmptyLine && isStartOfParagraph(mergeDestination) && startOfParagraphToMove.absoluteCaretBounds().x() > mergeDestination.absoluteCaretBounds().x()) {
        if (mergeDestination.deepEquivalent().downstream().deprecatedNode()->hasTagName(brTag)) {
            removeNodeAndPruneAncestors(mergeDestination.deepEquivalent().downstream().deprecatedNode());
            m_endingPosition = startOfParagraphToMove.deepEquivalent();
            return;
        }
    }
    
    // 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
    if (isRenderedAsNonInlineTableImageOrHR(startOfParagraphToMove.deepEquivalent().deprecatedNode()) && !isStartOfParagraph(mergeDestination)) {
        m_endingPosition = m_upstreamStart;
        return;
    }
    
    RefPtr<Range> range = Range::create(document(), startOfParagraphToMove.deepEquivalent().parentAnchoredEquivalent(), endOfParagraphToMove.deepEquivalent().parentAnchoredEquivalent());
    RefPtr<Range> rangeToBeReplaced = Range::create(document(), mergeDestination.deepEquivalent().parentAnchoredEquivalent(), mergeDestination.deepEquivalent().parentAnchoredEquivalent());
    if (!document()->frame()->editor()->client()->shouldMoveRangeAfterDelete(range.get(), rangeToBeReplaced.get()))
        return;
    
    // 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.
    bool needPlaceholder = m_needPlaceholder;
    bool paragraphToMergeIsEmpty = (startOfParagraphToMove == endOfParagraphToMove);
    moveParagraph(startOfParagraphToMove, endOfParagraphToMove, mergeDestination, false, !paragraphToMergeIsEmpty);
    m_needPlaceholder = needPlaceholder;
    // The endingPosition was likely clobbered by the move, so recompute it (moveParagraph selects the moved paragraph).
    m_endingPosition = endingSelection().start();
}
AccessibilityObject* objectFocusedAndCaretOffsetUnignored(AccessibilityObject* referenceObject, int& offset)
{
    // Indication that something bogus has transpired.
    offset = -1;

    Document* document = referenceObject->document();
    if (!document)
        return 0;

    Node* focusedNode = referenceObject->selection().end().containerNode();
    if (!focusedNode)
        return 0;

    RenderObject* focusedRenderer = focusedNode->renderer();
    if (!focusedRenderer)
        return 0;

    AccessibilityObject* focusedObject = document->axObjectCache()->getOrCreate(focusedRenderer);
    if (!focusedObject)
        return 0;

    // Look for the actual (not ignoring accessibility) selected object.
    AccessibilityObject* firstUnignoredParent = focusedObject;
    if (firstUnignoredParent->accessibilityIsIgnored())
        firstUnignoredParent = firstUnignoredParent->parentObjectUnignored();
    if (!firstUnignoredParent)
        return 0;

    // Don't ignore links if the offset is being requested for a link.
    if (!referenceObject->isLink() && firstUnignoredParent->isLink())
        firstUnignoredParent = firstUnignoredParent->parentObjectUnignored();
    if (!firstUnignoredParent)
        return 0;

    // The reference object must either coincide with the focused
    // object being considered, or be a descendant of it.
    if (referenceObject->isDescendantOfObject(firstUnignoredParent))
        referenceObject = firstUnignoredParent;

    Node* startNode = 0;
    if (firstUnignoredParent != referenceObject || firstUnignoredParent->isTextControl()) {
        // We need to use the first child's node of the reference
        // object as the start point to calculate the caret offset
        // because we want it to be relative to the object of
        // reference, not just to the focused object (which could have
        // previous siblings which should be taken into account too).
        AccessibilityObject* axFirstChild = referenceObject->firstChild();
        if (axFirstChild)
            startNode = axFirstChild->node();
    }
    if (!startNode)
        startNode = firstUnignoredParent->node();

    // Check if the node for the first parent object not ignoring
    // accessibility is null again before using it. This might happen
    // with certain kind of accessibility objects, such as the root
    // one (the scroller containing the webArea object).
    if (!startNode)
        return 0;

    VisiblePosition startPosition = VisiblePosition(positionBeforeNode(startNode), DOWNSTREAM);
    VisiblePosition endPosition = firstUnignoredParent->selection().visibleEnd();

    if (startPosition == endPosition)
        offset = 0;
    else if (!isStartOfLine(endPosition)) {
        RefPtr<Range> range = makeRange(startPosition, endPosition.previous());
        offset = TextIterator::rangeLength(range.get(), true) + 1;
    } else {
        RefPtr<Range> range = makeRange(startPosition, endPosition);
        offset = TextIterator::rangeLength(range.get(), true);
    }

    return firstUnignoredParent;
}
// FIXME: We should merge this function with ApplyBlockElementCommand::formatSelection
void IndentOutdentCommand::outdentRegion(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection)
{
    VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection);
    VisiblePosition endOfLastParagraph = endOfParagraph(endOfSelection);

    if (endOfCurrentParagraph == endOfLastParagraph) {
        outdentParagraph();
        return;
    }

    Position originalSelectionEnd = endingSelection().end();
    VisiblePosition endAfterSelection = endOfParagraph(endOfLastParagraph.next());

    while (endOfCurrentParagraph != endAfterSelection) {
        VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
        if (endOfCurrentParagraph == endOfLastParagraph)
            setEndingSelection(VisibleSelection(originalSelectionEnd, DOWNSTREAM));
        else
            setEndingSelection(endOfCurrentParagraph);

        outdentParagraph();

        // outdentParagraph could move more than one paragraph if the paragraph
        // is in a list item. As a result, endAfterSelection and endOfNextParagraph
        // could refer to positions no longer in the document.
        if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent().inDocument())
            break;

        if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().inDocument()) {
            endOfCurrentParagraph = VisiblePosition(endingSelection().end());
            endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
        }
        endOfCurrentParagraph = endOfNextParagraph;
    }
}
void ApplyBlockElementCommand::doApply()
{
    if (!endingSelection().rootEditableElement())
        return;

    VisiblePosition visibleEnd = endingSelection().visibleEnd();
    VisiblePosition visibleStart = endingSelection().visibleStart();
    if (visibleStart.isNull() || visibleStart.isOrphan() || visibleEnd.isNull() || visibleEnd.isOrphan())
        return;

    // When a selection ends at the start of a paragraph, we rarely paint
    // the selection gap before that paragraph, because there often is no gap.
    // In a case like this, it's not obvious to the user that the selection
    // ends "inside" that paragraph, so it would be confusing if Indent/Outdent
    // operated on that paragraph.
    // FIXME: We paint the gap before some paragraphs that are indented with left
    // margin/padding, but not others.  We should make the gap painting more consistent and
    // then use a left margin/padding rule here.
    if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd))
        setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(CannotCrossEditingBoundary), endingSelection().isDirectional()));

    VisibleSelection selection = selectionForParagraphIteration(endingSelection());
    VisiblePosition startOfSelection = selection.visibleStart();
    VisiblePosition endOfSelection = selection.visibleEnd();
    ASSERT(!startOfSelection.isNull());
    ASSERT(!endOfSelection.isNull());
    RefPtr<ContainerNode> startScope;
    int startIndex = indexForVisiblePosition(startOfSelection, startScope);
    RefPtr<ContainerNode> endScope;
    int endIndex = indexForVisiblePosition(endOfSelection, endScope);

    formatSelection(startOfSelection, endOfSelection);

    document()->updateLayoutIgnorePendingStylesheets();

    ASSERT(startScope == endScope);
    ASSERT(startIndex >= 0);
    ASSERT(startIndex <= endIndex);
    if (startScope == endScope && startIndex >= 0 && startIndex <= endIndex) {
        VisiblePosition start(visiblePositionForIndex(startIndex, startScope.get()));
        VisiblePosition end(visiblePositionForIndex(endIndex, endScope.get()));
        if (start.isNotNull() && end.isNotNull())
            setEndingSelection(VisibleSelection(start, end, endingSelection().isDirectional()));
    }
}
void FormatBlockCommand::doApply()
{
    if (!endingSelection().isNonOrphanedCaretOrRange())
        return;
    
    if (!endingSelection().rootEditableElement())
        return;

    VisiblePosition visibleEnd = endingSelection().visibleEnd();
    VisiblePosition visibleStart = endingSelection().visibleStart();
    // When a selection ends at the start of a paragraph, we rarely paint 
    // the selection gap before that paragraph, because there often is no gap.  
    // In a case like this, it's not obvious to the user that the selection 
    // ends "inside" that paragraph, so it would be confusing if FormatBlock
    // operated on that paragraph.
    // FIXME: We paint the gap before some paragraphs that are indented with left 
    // margin/padding, but not others.  We should make the gap painting more consistent and 
    // then use a left margin/padding rule here.
    if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd))
        setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true)));

    if (endingSelection().isRange() && modifyRange())
        return;

    ExceptionCode ec;
    String localName, prefix;
    if (!Document::parseQualifiedName(m_tagName, prefix, localName, ec))
        return;
    QualifiedName qTypeOfBlock(prefix, localName, xhtmlNamespaceURI);

    Node* refNode = enclosingBlockFlowElement(endingSelection().visibleStart());
    if (refNode->hasTagName(qTypeOfBlock))
        // We're already in a block with the format we want, so we don't have to do anything
        return;
    
    VisiblePosition paragraphStart = startOfParagraph(endingSelection().visibleStart());
    VisiblePosition paragraphEnd = endOfParagraph(endingSelection().visibleStart());
    VisiblePosition blockStart = startOfBlock(endingSelection().visibleStart());
    VisiblePosition blockEnd = endOfBlock(endingSelection().visibleStart());
    RefPtr<Element> blockNode = createHTMLElement(document(), m_tagName);
    RefPtr<Element> placeholder = createBreakElement(document());
    
    Node* root = endingSelection().start().node()->rootEditableElement();
    if (validBlockTag(refNode->nodeName().lower()) && 
        paragraphStart == blockStart && paragraphEnd == blockEnd && 
        refNode != root && !root->isDescendantOf(refNode))
        // Already in a valid block tag that only contains the current paragraph, so we can swap with the new tag
        insertNodeBefore(blockNode, refNode);
    else {
        // Avoid inserting inside inline elements that surround paragraphStart with upstream().
        // This is only to avoid creating bloated markup.
        insertNodeAt(blockNode, paragraphStart.deepEquivalent().upstream());
    }
    appendNode(placeholder, blockNode);
    
    VisiblePosition destination(Position(placeholder.get(), 0));
    if (paragraphStart == paragraphEnd && !lineBreakExistsAtVisiblePosition(paragraphStart)) {
        setEndingSelection(destination);
        return;
    }
    moveParagraph(paragraphStart, paragraphEnd, destination, true, false);
}
void IndentOutdentCommand::outdentParagraph()
{
    VisiblePosition visibleStartOfParagraph = startOfParagraph(endingSelection().visibleStart());
    VisiblePosition visibleEndOfParagraph = endOfParagraph(visibleStartOfParagraph);

    HTMLElement* enclosingElement = toHTMLElement(enclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), &isHTMLListOrBlockquoteElement));
    if (!enclosingElement || !enclosingElement->parentNode()->hasEditableStyle()) // We can't outdent if there is no place to go!
        return;

    // Use InsertListCommand to remove the selection from the list
    if (isHTMLOListElement(*enclosingElement)) {
        applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::OrderedList));
        return;
    }
    if (isHTMLUListElement(*enclosingElement)) {
        applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::UnorderedList));
        return;
    }

    // The selection is inside a blockquote i.e. enclosingNode is a blockquote
    VisiblePosition positionInEnclosingBlock = VisiblePosition(firstPositionInNode(enclosingElement));
    // If the blockquote is inline, the start of the enclosing block coincides with
    // positionInEnclosingBlock.
    VisiblePosition startOfEnclosingBlock = (enclosingElement->layoutObject() && enclosingElement->layoutObject()->isInline()) ? positionInEnclosingBlock : startOfBlock(positionInEnclosingBlock);
    VisiblePosition lastPositionInEnclosingBlock = VisiblePosition(lastPositionInNode(enclosingElement));
    VisiblePosition endOfEnclosingBlock = endOfBlock(lastPositionInEnclosingBlock);
    if (visibleStartOfParagraph == startOfEnclosingBlock &&
        visibleEndOfParagraph == endOfEnclosingBlock) {
        // The blockquote doesn't contain anything outside the paragraph, so it can be totally removed.
        Node* splitPoint = enclosingElement->nextSibling();
        removeNodePreservingChildren(enclosingElement);
        // outdentRegion() assumes it is operating on the first paragraph of an enclosing blockquote, but if there are multiply nested blockquotes and we've
        // just removed one, then this assumption isn't true. By splitting the next containing blockquote after this node, we keep this assumption true
        if (splitPoint) {
            if (Element* splitPointParent = splitPoint->parentElement()) {
                if (splitPointParent->hasTagName(blockquoteTag)
                    && !splitPoint->hasTagName(blockquoteTag)
                    && splitPointParent->parentNode()->hasEditableStyle()) // We can't outdent if there is no place to go!
                    splitElement(splitPointParent, splitPoint);
            }
        }

        document().updateLayoutIgnorePendingStylesheets();
        visibleStartOfParagraph = VisiblePosition(visibleStartOfParagraph.deepEquivalent());
        visibleEndOfParagraph = VisiblePosition(visibleEndOfParagraph.deepEquivalent());
        if (visibleStartOfParagraph.isNotNull() && !isStartOfParagraph(visibleStartOfParagraph))
            insertNodeAt(createBreakElement(document()), visibleStartOfParagraph.deepEquivalent());
        if (visibleEndOfParagraph.isNotNull() && !isEndOfParagraph(visibleEndOfParagraph))
            insertNodeAt(createBreakElement(document()), visibleEndOfParagraph.deepEquivalent());

        return;
    }
    RefPtrWillBeRawPtr<Node> splitBlockquoteNode = enclosingElement;
    if (Element* enclosingBlockFlow = enclosingBlock(visibleStartOfParagraph.deepEquivalent().deprecatedNode())) {
        if (enclosingBlockFlow != enclosingElement) {
            splitBlockquoteNode = splitTreeToNode(enclosingBlockFlow, enclosingElement, true);
        } else {
            // We split the blockquote at where we start outdenting.
            Node* highestInlineNode = highestEnclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), isInline, CannotCrossEditingBoundary, enclosingBlockFlow);
            splitElement(enclosingElement, highestInlineNode ? highestInlineNode : visibleStartOfParagraph.deepEquivalent().deprecatedNode());
        }
    }
    VisiblePosition startOfParagraphToMove(startOfParagraph(visibleStartOfParagraph));
    VisiblePosition endOfParagraphToMove(endOfParagraph(visibleEndOfParagraph));
    if (startOfParagraphToMove.isNull() || endOfParagraphToMove.isNull())
        return;
    RefPtrWillBeRawPtr<HTMLBRElement> placeholder = createBreakElement(document());
    insertNodeBefore(placeholder, splitBlockquoteNode);
    moveParagraph(startOfParagraphToMove, endOfParagraphToMove, VisiblePosition(positionBeforeNode(placeholder.get())), true);
}
Example #12
0
bool SelectionModifier::modifyWithPageGranularity(EAlteration alter,
                                                  unsigned verticalDistance,
                                                  VerticalDirection direction) {
  if (!verticalDistance)
    return false;

  DCHECK(!frame()->document()->needsLayoutTreeUpdate());
  DocumentLifecycle::DisallowTransitionScope disallowTransition(
      frame()->document()->lifecycle());

  willBeModified(alter, direction == FrameSelection::DirectionUp
                            ? DirectionBackward
                            : DirectionForward);

  VisiblePosition pos;
  LayoutUnit xPos;
  switch (alter) {
    case FrameSelection::AlterationMove:
      pos = createVisiblePosition(direction == FrameSelection::DirectionUp
                                      ? m_selection.start()
                                      : m_selection.end(),
                                  m_selection.affinity());
      xPos = lineDirectionPointForBlockDirectionNavigation(
          direction == FrameSelection::DirectionUp ? START : END);
      m_selection.setAffinity(direction == FrameSelection::DirectionUp
                                  ? TextAffinity::Upstream
                                  : TextAffinity::Downstream);
      break;
    case FrameSelection::AlterationExtend:
      pos = createVisiblePosition(m_selection.extent(), m_selection.affinity());
      xPos = lineDirectionPointForBlockDirectionNavigation(EXTENT);
      m_selection.setAffinity(TextAffinity::Downstream);
      break;
  }

  int startY;
  if (!absoluteCaretY(pos, startY))
    return false;
  if (direction == FrameSelection::DirectionUp)
    startY = -startY;
  int lastY = startY;

  VisiblePosition result;
  VisiblePosition next;
  for (VisiblePosition p = pos;; p = next) {
    if (direction == FrameSelection::DirectionUp)
      next = previousLinePosition(p, xPos);
    else
      next = nextLinePosition(p, xPos);

    if (next.isNull() || next.deepEquivalent() == p.deepEquivalent())
      break;
    int nextY;
    if (!absoluteCaretY(next, nextY))
      break;
    if (direction == FrameSelection::DirectionUp)
      nextY = -nextY;
    if (nextY - startY > static_cast<int>(verticalDistance))
      break;
    if (nextY >= lastY) {
      lastY = nextY;
      result = next;
    }
  }

  if (result.isNull())
    return false;

  switch (alter) {
    case FrameSelection::AlterationMove:
      m_selection = createVisibleSelection(
          SelectionInDOMTree::Builder()
              .collapse(result.toPositionWithAffinity())
              .setIsDirectional(m_selection.isDirectional())
              .build());
      break;
    case FrameSelection::AlterationExtend:
      m_selection.setExtent(result);
      break;
  }

  m_selection.setIsDirectional(shouldAlwaysUseDirectionalSelection(frame()) ||
                               alter == FrameSelection::AlterationExtend);

  return true;
}
Example #13
0
bool SelectionModifier::modify(EAlteration alter,
                               SelectionDirection direction,
                               TextGranularity granularity) {
  DCHECK(!frame()->document()->needsLayoutTreeUpdate());
  DocumentLifecycle::DisallowTransitionScope disallowTransition(
      frame()->document()->lifecycle());

  willBeModified(alter, direction);

  bool wasRange = m_selection.isRange();
  VisiblePosition originalStartPosition = m_selection.visibleStart();
  VisiblePosition position;
  switch (direction) {
    case DirectionRight:
      if (alter == FrameSelection::AlterationMove)
        position = modifyMovingRight(granularity);
      else
        position = modifyExtendingRight(granularity);
      break;
    case DirectionForward:
      if (alter == FrameSelection::AlterationExtend)
        position = modifyExtendingForward(granularity);
      else
        position = modifyMovingForward(granularity);
      break;
    case DirectionLeft:
      if (alter == FrameSelection::AlterationMove)
        position = modifyMovingLeft(granularity);
      else
        position = modifyExtendingLeft(granularity);
      break;
    case DirectionBackward:
      if (alter == FrameSelection::AlterationExtend)
        position = modifyExtendingBackward(granularity);
      else
        position = modifyMovingBackward(granularity);
      break;
  }

  if (position.isNull())
    return false;

  if (isSpatialNavigationEnabled(frame())) {
    if (!wasRange && alter == FrameSelection::AlterationMove &&
        position.deepEquivalent() == originalStartPosition.deepEquivalent())
      return false;
  }

  // Some of the above operations set an xPosForVerticalArrowNavigation.
  // Setting a selection will clear it, so save it to possibly restore later.
  // Note: the START position type is arbitrary because it is unused, it would
  // be the requested position type if there were no
  // xPosForVerticalArrowNavigation set.
  LayoutUnit x = lineDirectionPointForBlockDirectionNavigation(START);
  m_selection.setIsDirectional(shouldAlwaysUseDirectionalSelection(frame()) ||
                               alter == FrameSelection::AlterationExtend);

  switch (alter) {
    case FrameSelection::AlterationMove:
      m_selection = createVisibleSelection(
          SelectionInDOMTree::Builder()
              .collapse(position.toPositionWithAffinity())
              .setIsDirectional(m_selection.isDirectional())
              .build());
      break;
    case FrameSelection::AlterationExtend:

      if (!m_selection.isCaret() && (granularity == WordGranularity ||
                                     granularity == ParagraphGranularity ||
                                     granularity == LineGranularity) &&
          frame() &&
          !frame()
               ->editor()
               .behavior()
               .shouldExtendSelectionByWordOrLineAcrossCaret()) {
        // Don't let the selection go across the base position directly. Needed
        // to match mac behavior when, for instance, word-selecting backwards
        // starting with the caret in the middle of a word and then
        // word-selecting forward, leaving the caret in the same place where it
        // was, instead of directly selecting to the end of the word.
        VisibleSelection newSelection = m_selection;
        newSelection.setExtent(position);
        if (m_selection.isBaseFirst() != newSelection.isBaseFirst())
          position = m_selection.visibleBase();
      }

      // Standard Mac behavior when extending to a boundary is grow the
      // selection rather than leaving the base in place and moving the
      // extent. Matches NSTextView.
      if (!frame() ||
          !frame()
               ->editor()
               .behavior()
               .shouldAlwaysGrowSelectionWhenExtendingToBoundary() ||
          m_selection.isCaret() || !isBoundary(granularity)) {
        m_selection.setExtent(position);
      } else {
        TextDirection textDirection = directionOfEnclosingBlock();
        if (direction == DirectionForward ||
            (textDirection == LTR && direction == DirectionRight) ||
            (textDirection == RTL && direction == DirectionLeft))
          setSelectionEnd(&m_selection, position);
        else
          setSelectionStart(&m_selection, position);
      }
      break;
  }

  if (granularity == LineGranularity || granularity == ParagraphGranularity)
    m_xPosForVerticalArrowNavigation = x;

  return true;
}
Example #14
0
bool isEndOfDocument(const VisiblePosition &p)
{
    return p.isNotNull() && p.next().isNull();
}
void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing)
{
    LocalFrame* frame = document().frame();
    if (!frame)
        return;

    frame->spellChecker().updateMarkersForWordsAffectedByEditing(false);

    VisibleSelection selectionToDelete;
    VisibleSelection selectionAfterUndo;

    switch (endingSelection().selectionType()) {
    case RangeSelection:
        selectionToDelete = endingSelection();
        selectionAfterUndo = selectionToDelete;
        break;
    case CaretSelection: {
        m_smartDelete = false;

        // Handle delete at beginning-of-block case.
        // Do nothing in the case that the caret is at the start of a
        // root editable element or at the start of a document.
        OwnPtrWillBeRawPtr<FrameSelection> selection = FrameSelection::create();
        selection->setSelection(endingSelection());
        selection->modify(FrameSelection::AlterationExtend, DirectionForward, granularity);
        if (killRing && selection->isCaret() && granularity != CharacterGranularity)
            selection->modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);

        Position downstreamEnd = endingSelection().end().downstream();
        VisiblePosition visibleEnd = endingSelection().visibleEnd();
        Node* enclosingTableCell = enclosingNodeOfType(visibleEnd.deepEquivalent(), &isTableCell);
        if (enclosingTableCell && visibleEnd == VisiblePosition(lastPositionInNode(enclosingTableCell)))
            return;
        if (visibleEnd == endOfParagraph(visibleEnd))
            downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream();
        // When deleting tables: Select the table first, then perform the deletion
        if (isRenderedTableElement(downstreamEnd.containerNode()) && downstreamEnd.computeOffsetInContainerNode() <= caretMinOffset(downstreamEnd.containerNode())) {
            setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.containerNode()), DOWNSTREAM, endingSelection().isDirectional()));
            typingAddedToOpenCommand(ForwardDeleteKey);
            return;
        }

        // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any)
        if (granularity == ParagraphBoundary && selection->selection().isCaret() && isEndOfParagraph(selection->selection().visibleEnd()))
            selection->modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);

        selectionToDelete = selection->selection();
        if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
            selectionAfterUndo = selectionToDelete;
        else {
            // It's a little tricky to compute what the starting selection would have been in the original document.
            // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on
            // the current state of the document and we'll get the wrong result.
            Position extent = startingSelection().end();
            if (extent.containerNode() != selectionToDelete.end().containerNode())
                extent = selectionToDelete.extent();
            else {
                int extraCharacters;
                if (selectionToDelete.start().containerNode() == selectionToDelete.end().containerNode())
                    extraCharacters = selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode();
                else
                    extraCharacters = selectionToDelete.end().computeOffsetInContainerNode();
                extent = Position(extent.containerNode(), extent.computeOffsetInContainerNode() + extraCharacters, Position::PositionIsOffsetInAnchor);
            }
            selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent);
        }
        break;
    }
    case NoSelection:
        ASSERT_NOT_REACHED();
        break;
    }

    ASSERT(!selectionToDelete.isNone());
    if (selectionToDelete.isNone())
        return;

    if (selectionToDelete.isCaret())
        return;

    if (killRing)
        frame->editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false);
    // Make undo select what was deleted on Mac alone
    if (frame->editor().behavior().shouldUndoOfDeleteSelectText())
        setStartingSelection(selectionAfterUndo);
    CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
    setSmartDelete(false);
    typingAddedToOpenCommand(ForwardDeleteKey);
}
void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool shouldAddToKillRing)
{
    Frame& frame = this->frame();

    frame.editor().updateMarkersForWordsAffectedByEditing(false);

    VisibleSelection selectionToDelete;
    VisibleSelection selectionAfterUndo;

    switch (endingSelection().selectionType()) {
    case VisibleSelection::RangeSelection:
        selectionToDelete = endingSelection();
        selectionAfterUndo = selectionToDelete;
        break;
    case VisibleSelection::CaretSelection: {
        m_smartDelete = false;

        // Handle delete at beginning-of-block case.
        // Do nothing in the case that the caret is at the start of a
        // root editable element or at the start of a document.
        FrameSelection selection;
        selection.setSelection(endingSelection());
        selection.modify(FrameSelection::AlterationExtend, DirectionForward, granularity);
        if (shouldAddToKillRing && selection.isCaret() && granularity != CharacterGranularity)
            selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);

        Position downstreamEnd = endingSelection().end().downstream();
        VisiblePosition visibleEnd = endingSelection().visibleEnd();
        Node* enclosingTableCell = enclosingNodeOfType(visibleEnd.deepEquivalent(), &isTableCell);
        if (enclosingTableCell && visibleEnd == lastPositionInNode(enclosingTableCell))
            return;
        if (visibleEnd == endOfParagraph(visibleEnd))
            downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream();
        // When deleting tables: Select the table first, then perform the deletion
        if (downstreamEnd.containerNode() && downstreamEnd.containerNode()->renderer() && downstreamEnd.containerNode()->renderer()->isTable()
            && downstreamEnd.computeOffsetInContainerNode() <= caretMinOffset(downstreamEnd.containerNode())) {
            setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.containerNode()), DOWNSTREAM, endingSelection().isDirectional()));
            typingAddedToOpenCommand(ForwardDeleteKey);
            return;
        }

        // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any)
        if (granularity == ParagraphBoundary && selection.selection().isCaret() && isEndOfParagraph(selection.selection().visibleEnd()))
            selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);

        selectionToDelete = selection.selection();
        if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
            selectionAfterUndo = selectionToDelete;
        else {
            // It's a little tricky to compute what the starting selection would have been in the original document.
            // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on
            // the current state of the document and we'll get the wrong result.
            Position extent = startingSelection().end();
            if (extent.containerNode() != selectionToDelete.end().containerNode())
                extent = selectionToDelete.extent();
            else {
                int extraCharacters;
                if (selectionToDelete.start().containerNode() == selectionToDelete.end().containerNode())
                    extraCharacters = selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode();
                else
                    extraCharacters = selectionToDelete.end().computeOffsetInContainerNode();
                extent = Position(extent.containerNode(), extent.computeOffsetInContainerNode() + extraCharacters, Position::PositionIsOffsetInAnchor);
            }
            selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent);
        }
        break;
    }
    case VisibleSelection::NoSelection:
        ASSERT_NOT_REACHED();
        break;
    }
    
    ASSERT(!selectionToDelete.isNone());
    if (selectionToDelete.isNone()) {
#if PLATFORM(IOS)
        // Workaround for this bug:
        // <rdar://problem/4653755> UIKit text widgets should use WebKit editing API to manipulate text
        setEndingSelection(frame.selection().selection());
        closeTyping(&frame);
#endif
        return;
    }
    
    if (selectionToDelete.isCaret() || !frame.selection().shouldDeleteSelection(selectionToDelete))
        return;
        
    if (shouldAddToKillRing)
        frame.editor().addRangeToKillRing(*selectionToDelete.toNormalizedRange().get(), Editor::KillRingInsertionMode::AppendText);
    // make undo select what was deleted
    setStartingSelection(selectionAfterUndo);
    CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
    setSmartDelete(false);
    typingAddedToOpenCommand(ForwardDeleteKey);
}
void DeleteSelectionCommand::initializePositionData()
{
    Position start, end;
    initializeStartEnd(start, end);
    
    m_upstreamStart = start.upstream();
    m_downstreamStart = start.downstream();
    m_upstreamEnd = end.upstream();
    m_downstreamEnd = end.downstream();
    
    m_startRoot = editableRootForPosition(start);
    m_endRoot = editableRootForPosition(end);
    
    m_startTableRow = enclosingNodeOfType(start, &isTableRow);
    m_endTableRow = enclosingNodeOfType(end, &isTableRow);
    
    // Don't move content out of a table cell.
    // If the cell is non-editable, enclosingNodeOfType won't return it by default, so
    // tell that function that we don't care if it returns non-editable nodes.
    Node* startCell = enclosingNodeOfType(m_upstreamStart, &isTableCell, CanCrossEditingBoundary);
    Node* endCell = enclosingNodeOfType(m_downstreamEnd, &isTableCell, CanCrossEditingBoundary);
    // FIXME: This isn't right.  A borderless table with two rows and a single column would appear as two paragraphs.
    if (endCell && endCell != startCell)
        m_mergeBlocksAfterDelete = false;
    
    // Usually the start and the end of the selection to delete are pulled together as a result of the deletion.
    // Sometimes they aren't (like when no merge is requested), so we must choose one position to hold the caret 
    // and receive the placeholder after deletion.
    VisiblePosition visibleEnd(m_downstreamEnd);
    if (m_mergeBlocksAfterDelete && !isEndOfParagraph(visibleEnd))
        m_endingPosition = m_downstreamEnd;
    else
        m_endingPosition = m_downstreamStart;
    
    // We don't want to merge into a block if it will mean changing the quote level of content after deleting 
    // selections that contain a whole number paragraphs plus a line break, since it is unclear to most users 
    // that such a selection actually ends at the start of the next paragraph. This matches TextEdit behavior 
    // for indented paragraphs.
    // Only apply this rule if the endingSelection is a range selection.  If it is a caret, then other operations have created
    // the selection we're deleting (like the process of creating a selection to delete during a backspace), and the user isn't in the situation described above.
    if (numEnclosingMailBlockquotes(start) != numEnclosingMailBlockquotes(end) 
            && isStartOfParagraph(visibleEnd) && isStartOfParagraph(VisiblePosition(start)) 
            && endingSelection().isRange()) {
        m_mergeBlocksAfterDelete = false;
        m_pruneStartBlockIfNecessary = true;
    }

    // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection
    m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.affinity());
    m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);

    if (m_smartDelete) {
    
        // skip smart delete if the selection to delete already starts or ends with whitespace
        Position pos = VisiblePosition(m_upstreamStart, m_selectionToDelete.affinity()).deepEquivalent();
        bool skipSmartDelete = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
        if (!skipSmartDelete)
            skipSmartDelete = m_downstreamEnd.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();

        // extend selection upstream if there is whitespace there
        bool hasLeadingWhitespaceBeforeAdjustment = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.affinity(), true).isNotNull();
        if (!skipSmartDelete && hasLeadingWhitespaceBeforeAdjustment) {
            VisiblePosition visiblePos = VisiblePosition(m_upstreamStart, VP_DEFAULT_AFFINITY).previous();
            pos = visiblePos.deepEquivalent();
            // Expand out one character upstream for smart delete and recalculate
            // positions based on this change.
            m_upstreamStart = pos.upstream();
            m_downstreamStart = pos.downstream();
            m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(visiblePos.affinity());

            setStartingSelectionOnSmartDelete(m_upstreamStart, m_upstreamEnd);
        }
        
        // trailing whitespace is only considered for smart delete if there is no leading
        // whitespace, as in the case where you double-click the first word of a paragraph.
        if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull()) {
            // Expand out one character downstream for smart delete and recalculate
            // positions based on this change.
            pos = VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY).next().deepEquivalent();
            m_upstreamEnd = pos.upstream();
            m_downstreamEnd = pos.downstream();
            m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);

            setStartingSelectionOnSmartDelete(m_downstreamStart, m_downstreamEnd);
        }
    }
    
    // We must pass call parentAnchoredEquivalent on the positions since some editing positions
    // that appear inside their nodes aren't really inside them.  [hr, 0] is one example.
    // FIXME: parentAnchoredEquivalent should eventually be moved into enclosing element getters
    // like the one below, since editing functions should obviously accept editing positions.
    // FIXME: Passing false to enclosingNodeOfType tells it that it's OK to return a non-editable
    // node.  This was done to match existing behavior, but it seems wrong.
    m_startBlock = enclosingNodeOfType(m_downstreamStart.parentAnchoredEquivalent(), &isBlock, CanCrossEditingBoundary);
    m_endBlock = enclosingNodeOfType(m_upstreamEnd.parentAnchoredEquivalent(), &isBlock, CanCrossEditingBoundary);
}
Example #18
0
int comparePositions(const VisiblePosition& a, const VisiblePosition& b)
{
    return comparePositions(a.deepEquivalent(), b.deepEquivalent());
}
void InsertParagraphSeparatorCommand::doApply()
{
    if (!endingSelection().isNonOrphanedCaretOrRange())
        return;
    
    Position insertionPosition = endingSelection().start();
        
    EAffinity affinity = endingSelection().affinity();
        
    // Delete the current selection.
    if (endingSelection().isRange()) {
        calculateStyleBeforeInsertion(insertionPosition);
        deleteSelection(false, true);
        insertionPosition = endingSelection().start();
        affinity = endingSelection().affinity();
    }
    
    // FIXME: The parentAnchoredEquivalent conversion needs to be moved into enclosingBlock.
    RefPtr<Element> startBlock = enclosingBlock(insertionPosition.parentAnchoredEquivalent().containerNode());
    Position canonicalPos = VisiblePosition(insertionPosition).deepEquivalent();
    if (!startBlock
        || !startBlock->nonShadowBoundaryParentNode()
        || isTableCell(startBlock.get())
        || isHTMLFormElement(startBlock.get())
        // FIXME: If the node is hidden, we don't have a canonical position so we will do the wrong thing for tables and <hr>. https://bugs.webkit.org/show_bug.cgi?id=40342
        || (!canonicalPos.isNull() && canonicalPos.deprecatedNode()->renderer() && canonicalPos.deprecatedNode()->renderer()->isTable())
        || (!canonicalPos.isNull() && canonicalPos.deprecatedNode()->hasTagName(hrTag))) {
        applyCommandToComposite(InsertLineBreakCommand::create(document()));
        return;
    }
    
    // Use the leftmost candidate.
    insertionPosition = insertionPosition.upstream();
    if (!insertionPosition.isCandidate())
        insertionPosition = insertionPosition.downstream();

    // Adjust the insertion position after the delete
    insertionPosition = positionAvoidingSpecialElementBoundary(insertionPosition);
    VisiblePosition visiblePos(insertionPosition, affinity);
    calculateStyleBeforeInsertion(insertionPosition);

    //---------------------------------------------------------------------
    // Handle special case of typing return on an empty list item
    if (breakOutOfEmptyListItem())
        return;

    //---------------------------------------------------------------------
    // Prepare for more general cases.

    bool isFirstInBlock = isStartOfBlock(visiblePos);
    bool isLastInBlock = isEndOfBlock(visiblePos);
    bool nestNewBlock = false;

    // Create block to be inserted.
    RefPtr<Element> blockToInsert;
    if (startBlock->isRootEditableElement()) {
        blockToInsert = createDefaultParagraphElement(document());
        nestNewBlock = true;
    } else if (shouldUseDefaultParagraphElement(startBlock.get())) 
        blockToInsert = createDefaultParagraphElement(document());
    else
        blockToInsert = startBlock->cloneElementWithoutChildren();

    //---------------------------------------------------------------------
    // Handle case when position is in the last visible position in its block,
    // including when the block is empty. 
    if (isLastInBlock) {
        if (nestNewBlock) {
            if (isFirstInBlock && !lineBreakExistsAtVisiblePosition(visiblePos)) {
                // The block is empty.  Create an empty block to
                // represent the paragraph that we're leaving.
                RefPtr<Element> extraBlock = createDefaultParagraphElement(document());
                appendNode(extraBlock, startBlock);
                appendBlockPlaceholder(extraBlock);
            }
            appendNode(blockToInsert, startBlock);
        } else {
            // We can get here if we pasted a copied portion of a blockquote with a newline at the end and are trying to paste it
            // into an unquoted area. We then don't want the newline within the blockquote or else it will also be quoted.
            if (m_pasteBlockqutoeIntoUnquotedArea) {
                if (Node* highestBlockquote = highestEnclosingNodeOfType(canonicalPos, &isMailBlockquote))
                    startBlock = toElement(highestBlockquote);
            }

            // Most of the time we want to stay at the nesting level of the startBlock (e.g., when nesting within lists).  However,
            // for div nodes, this can result in nested div tags that are hard to break out of.
            Element* siblingNode = startBlock.get();
            if (blockToInsert->hasTagName(divTag))
                siblingNode = highestVisuallyEquivalentDivBelowRoot(startBlock.get());
            insertNodeAfter(blockToInsert, siblingNode);
        }

        // Recreate the same structure in the new paragraph.
        
        Vector<RefPtr<Element>> ancestors;
        getAncestorsInsideBlock(positionOutsideTabSpan(insertionPosition).deprecatedNode(), startBlock.get(), ancestors);      
        RefPtr<Element> parent = cloneHierarchyUnderNewBlock(ancestors, blockToInsert);
        
        appendBlockPlaceholder(parent);

        setEndingSelection(VisibleSelection(firstPositionInNode(parent.get()), DOWNSTREAM, endingSelection().isDirectional()));
        return;
    }
    

    //---------------------------------------------------------------------
    // Handle case when position is in the first visible position in its block, and
    // similar case where previous position is in another, presumeably nested, block.
    if (isFirstInBlock || !inSameBlock(visiblePos, visiblePos.previous())) {
        Node *refNode;
        
        insertionPosition = positionOutsideTabSpan(insertionPosition);

        if (isFirstInBlock && !nestNewBlock)
            refNode = startBlock.get();
        else if (isFirstInBlock && nestNewBlock) {
            // startBlock should always have children, otherwise isLastInBlock would be true and it's handled above.
            ASSERT(startBlock->firstChild());
            refNode = startBlock->firstChild();
        }
        else if (insertionPosition.deprecatedNode() == startBlock && nestNewBlock) {
            refNode = startBlock->traverseToChildAt(insertionPosition.deprecatedEditingOffset());
            ASSERT(refNode); // must be true or we'd be in the end of block case
        } else
            refNode = insertionPosition.deprecatedNode();

        // find ending selection position easily before inserting the paragraph
        insertionPosition = insertionPosition.downstream();
        
        insertNodeBefore(blockToInsert, refNode);

        // Recreate the same structure in the new paragraph.

        Vector<RefPtr<Element>> ancestors;
        getAncestorsInsideBlock(positionAvoidingSpecialElementBoundary(positionOutsideTabSpan(insertionPosition)).deprecatedNode(), startBlock.get(), ancestors);
        
        appendBlockPlaceholder(cloneHierarchyUnderNewBlock(ancestors, blockToInsert));
        
        // In this case, we need to set the new ending selection.
        setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM, endingSelection().isDirectional()));
        return;
    }

    //---------------------------------------------------------------------
    // Handle the (more complicated) general case,

    // All of the content in the current block after visiblePos is
    // about to be wrapped in a new paragraph element.  Add a br before 
    // it if visiblePos is at the start of a paragraph so that the 
    // content will move down a line.
    if (isStartOfParagraph(visiblePos)) {
        RefPtr<Element> br = createBreakElement(document());
        insertNodeAt(br.get(), insertionPosition);
        insertionPosition = positionInParentAfterNode(br.get());
        // If the insertion point is a break element, there is nothing else
        // we need to do.
        if (visiblePos.deepEquivalent().anchorNode()->renderer()->isBR()) {
            setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM, endingSelection().isDirectional()));
            return;
        }
    }
    
    // Move downstream. Typing style code will take care of carrying along the 
    // style of the upstream position.
    insertionPosition = insertionPosition.downstream();

    // At this point, the insertionPosition's node could be a container, and we want to make sure we include
    // all of the correct nodes when building the ancestor list.  So this needs to be the deepest representation of the position
    // before we walk the DOM tree.
    insertionPosition = positionOutsideTabSpan(VisiblePosition(insertionPosition).deepEquivalent());

    // If the returned position lies either at the end or at the start of an element that is ignored by editing
    // we should move to its upstream or downstream position.
    if (editingIgnoresContent(insertionPosition.deprecatedNode())) {
        if (insertionPosition.atLastEditingPositionForNode())
            insertionPosition = insertionPosition.downstream();
        else if (insertionPosition.atFirstEditingPositionForNode())
            insertionPosition = insertionPosition.upstream();
    }

    // Make sure we do not cause a rendered space to become unrendered.
    // FIXME: We need the affinity for pos, but pos.downstream() does not give it
    Position leadingWhitespace = insertionPosition.leadingWhitespacePosition(VP_DEFAULT_AFFINITY);
    // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions
    // after the preserved newline, causing the newline to be turned into a nbsp.
    if (leadingWhitespace.isNotNull() && leadingWhitespace.deprecatedNode()->isTextNode()) {
        Text* textNode = toText(leadingWhitespace.deprecatedNode());
        ASSERT(!textNode->renderer() || textNode->renderer()->style().collapseWhiteSpace());
        replaceTextInNodePreservingMarkers(textNode, leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
    }
    
    // Split at pos if in the middle of a text node.
    Position positionAfterSplit;
    if (insertionPosition.anchorType() == Position::PositionIsOffsetInAnchor && insertionPosition.containerNode()->isTextNode()) {
        RefPtr<Text> textNode = toText(insertionPosition.containerNode());
        bool atEnd = static_cast<unsigned>(insertionPosition.offsetInContainerNode()) >= textNode->length();
        if (insertionPosition.deprecatedEditingOffset() > 0 && !atEnd) {
            splitTextNode(textNode, insertionPosition.offsetInContainerNode());
            positionAfterSplit = firstPositionInNode(textNode.get());
            if (!textNode->previousSibling())
                return; // Bail out if mutation events detachd the split text node.
            insertionPosition.moveToPosition(textNode->previousSibling(), insertionPosition.offsetInContainerNode());
            visiblePos = VisiblePosition(insertionPosition);
        }
    }

    // If we got detached due to mutation events, just bail out.
    if (!startBlock->parentNode())
        return;

    // Put the added block in the tree.
    if (nestNewBlock)
        appendNode(blockToInsert.get(), startBlock);
    else
        insertNodeAfter(blockToInsert.get(), startBlock);

    document().updateLayoutIgnorePendingStylesheets();

    // If the paragraph separator was inserted at the end of a paragraph, an empty line must be
    // created.  All of the nodes, starting at visiblePos, are about to be added to the new paragraph 
    // element.  If the first node to be inserted won't be one that will hold an empty line open, add a br.
    if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtVisiblePosition(visiblePos))
        appendNode(createBreakElement(document()).get(), blockToInsert.get());

    // Move the start node and the siblings of the start node.
    if (VisiblePosition(insertionPosition) != VisiblePosition(positionBeforeNode(blockToInsert.get()))) {
        Node* n;
        if (insertionPosition.containerNode() == startBlock)
            n = insertionPosition.computeNodeAfterPosition();
        else {
            Node* splitTo = insertionPosition.containerNode();
            if (splitTo->isTextNode() && insertionPosition.offsetInContainerNode() >= caretMaxOffset(splitTo))
                splitTo = NodeTraversal::next(splitTo, startBlock.get());
            ASSERT(splitTo);
            splitTreeToNode(splitTo, startBlock.get());

            for (n = startBlock->firstChild(); n; n = n->nextSibling()) {
                VisiblePosition beforeNodePosition = positionBeforeNode(n);
                if (!beforeNodePosition.isNull() && comparePositions(VisiblePosition(insertionPosition), beforeNodePosition) <= 0)
                    break;
            }
        }

        moveRemainingSiblingsToNewParent(n, blockToInsert.get(), blockToInsert);
    }            

    // Handle whitespace that occurs after the split
    if (positionAfterSplit.isNotNull()) {
        document().updateLayoutIgnorePendingStylesheets();
        if (!positionAfterSplit.isRenderedCharacter()) {
            // Clear out all whitespace and insert one non-breaking space
            ASSERT(!positionAfterSplit.containerNode()->renderer() || positionAfterSplit.containerNode()->renderer()->style().collapseWhiteSpace());
            deleteInsignificantTextDownstream(positionAfterSplit);
            if (positionAfterSplit.deprecatedNode()->isTextNode())
                insertTextIntoNode(toText(positionAfterSplit.containerNode()), 0, nonBreakingSpaceString());
        }
    }

    setEndingSelection(VisibleSelection(firstPositionInNode(blockToInsert.get()), DOWNSTREAM, endingSelection().isDirectional()));
    applyStyleAfterInsertion(startBlock.get());
}
Example #20
0
bool lineBreakExistsAtVisiblePosition(const VisiblePosition& visiblePosition)
{
    return lineBreakExistsAtPosition(visiblePosition.deepEquivalent().downstream());
}
Example #21
0
void InsertListCommand::doApply()
{
    if (endingSelection().isNone())
        return;
    
    if (!endingSelection().rootEditableElement())
        return;
    
    VisiblePosition visibleEnd = endingSelection().visibleEnd();
    VisiblePosition visibleStart = endingSelection().visibleStart();
    // When a selection ends at the start of a paragraph, we rarely paint 
    // the selection gap before that paragraph, because there often is no gap.  
    // In a case like this, it's not obvious to the user that the selection 
    // ends "inside" that paragraph, so it would be confusing if InsertUn{Ordered}List 
    // operated on that paragraph.
    // FIXME: We paint the gap before some paragraphs that are indented with left 
    // margin/padding, but not others.  We should make the gap painting more consistent and 
    // then use a left margin/padding rule here.
    if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd))
        setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true)));

    if (endingSelection().isRange() && modifyRange())
        return;
    
    // FIXME: This will produce unexpected results for a selection that starts just before a
    // table and ends inside the first cell, selectionForParagraphIteration should probably
    // be renamed and deployed inside setEndingSelection().
    Node* selectionNode = endingSelection().start().node();
    const QualifiedName listTag = (m_type == OrderedList) ? olTag : ulTag;
    Node* listChildNode = enclosingListChild(selectionNode);
    bool switchListType = false;
    if (listChildNode) {
        // Remove the list chlild.
        HTMLElement* listNode = enclosingList(listChildNode);
        if (!listNode)
            listNode = fixOrphanedListChild(listChildNode);
        if (!listNode->hasTagName(listTag))
            // listChildNode will be removed from the list and a list of type m_type will be created.
            switchListType = true;
        Node* nextListChild;
        Node* previousListChild;
        VisiblePosition start;
        VisiblePosition end;
        if (listChildNode->hasTagName(liTag)) {
            start = firstDeepEditingPositionForNode(listChildNode);
            end = lastDeepEditingPositionForNode(listChildNode);
            nextListChild = listChildNode->nextSibling();
            previousListChild = listChildNode->previousSibling();
        } else {
            // A paragraph is visually a list item minus a list marker.  The paragraph will be moved.
            start = startOfParagraph(endingSelection().visibleStart());
            end = endOfParagraph(endingSelection().visibleEnd());
            nextListChild = enclosingListChild(end.next().deepEquivalent().node());
            ASSERT(nextListChild != listChildNode);
            if (enclosingList(nextListChild) != listNode)
                nextListChild = 0;
            previousListChild = enclosingListChild(start.previous().deepEquivalent().node());
            ASSERT(previousListChild != listChildNode);
            if (enclosingList(previousListChild) != listNode)
                previousListChild = 0;
        }
        // When removing a list, we must always create a placeholder to act as a point of insertion
        // for the list content being removed.
        RefPtr<Element> placeholder = createBreakElement(document());
        RefPtr<Element> nodeToInsert = placeholder;
        // If the content of the list item will be moved into another list, put it in a list item
        // so that we don't create an orphaned list child.
        if (enclosingList(listNode)) {
            nodeToInsert = createListItemElement(document());
            appendNode(placeholder, nodeToInsert);
        }
        
        if (nextListChild && previousListChild) {
            // We want to pull listChildNode out of listNode, and place it before nextListChild 
            // and after previousListChild, so we split listNode and insert it between the two lists.  
            // But to split listNode, we must first split ancestors of listChildNode between it and listNode,
            // if any exist.
            // FIXME: We appear to split at nextListChild as opposed to listChildNode so that when we remove
            // listChildNode below in moveParagraphs, previousListChild will be removed along with it if it is 
            // unrendered. But we ought to remove nextListChild too, if it is unrendered.
            splitElement(listNode, splitTreeToNode(nextListChild, listNode));
            insertNodeBefore(nodeToInsert, listNode);
        } else if (nextListChild || listChildNode->parentNode() != listNode) {
            // Just because listChildNode has no previousListChild doesn't mean there isn't any content
            // in listNode that comes before listChildNode, as listChildNode could have ancestors
            // between it and listNode. So, we split up to listNode before inserting the placeholder
            // where we're about to move listChildNode to.
            if (listChildNode->parentNode() != listNode)
                splitElement(listNode, splitTreeToNode(listChildNode, listNode).get());
            insertNodeBefore(nodeToInsert, listNode);
        } else
            insertNodeAfter(nodeToInsert, listNode);
        
        VisiblePosition insertionPoint = VisiblePosition(Position(placeholder.get(), 0));
        moveParagraphs(start, end, insertionPoint, true);
    }
    if (!listChildNode || switchListType || m_forceCreateList) {
        // Create list.
        VisiblePosition start = startOfParagraph(endingSelection().visibleStart());
        VisiblePosition end = endOfParagraph(endingSelection().visibleEnd());
        
        // Check for adjoining lists.
        VisiblePosition previousPosition = start.previous(true);
        VisiblePosition nextPosition = end.next(true);
        RefPtr<HTMLElement> listItemElement = createListItemElement(document());
        RefPtr<HTMLElement> placeholder = createBreakElement(document());
        appendNode(placeholder, listItemElement);
        Element* previousList = outermostEnclosingList(previousPosition.deepEquivalent().node());
        Element* nextList = outermostEnclosingList(nextPosition.deepEquivalent().node());
        Node* startNode = start.deepEquivalent().node();
        Node* previousCell = enclosingTableCell(previousPosition.deepEquivalent());
        Node* nextCell = enclosingTableCell(nextPosition.deepEquivalent());
        Node* currentCell = enclosingTableCell(start.deepEquivalent());
        if (previousList && (!previousList->hasTagName(listTag) || startNode->isDescendantOf(previousList) || previousCell != currentCell))
            previousList = 0;
        if (nextList && (!nextList->hasTagName(listTag) || startNode->isDescendantOf(nextList) || nextCell != currentCell))
            nextList = 0;
        // Place list item into adjoining lists.
        if (previousList)
            appendNode(listItemElement, previousList);
        else if (nextList)
            insertNodeAt(listItemElement, Position(nextList, 0));
        else {
            // Create the list.
            RefPtr<HTMLElement> listElement = m_type == OrderedList ? createOrderedListElement(document()) : createUnorderedListElement(document());
            m_listElement = listElement;
            appendNode(listItemElement, listElement);
            
            if (start == end && isBlock(start.deepEquivalent().node())) {
                // Inserting the list into an empty paragraph that isn't held open 
                // by a br or a '\n', will invalidate start and end.  Insert 
                // a placeholder and then recompute start and end.
                RefPtr<Node> placeholder = insertBlockPlaceholder(start.deepEquivalent());
                start = VisiblePosition(Position(placeholder.get(), 0));
                end = start;
            }
            
            // Insert the list at a position visually equivalent to start of the
            // paragraph that is being moved into the list. 
            // Try to avoid inserting it somewhere where it will be surrounded by 
            // inline ancestors of start, since it is easier for editing to produce 
            // clean markup when inline elements are pushed down as far as possible.
            Position insertionPos(start.deepEquivalent().upstream());
            // Also avoid the containing list item.
            Node* listChild = enclosingListChild(insertionPos.node());
            if (listChild && listChild->hasTagName(liTag))
                insertionPos = positionBeforeNode(listChild);
                
            insertNodeAt(listElement, insertionPos);
        }
        moveParagraph(start, end, VisiblePosition(Position(placeholder.get(), 0)), true);
        if (nextList && previousList)
            mergeIdenticalElements(static_cast<Element*>(previousList), static_cast<Element*>(nextList));
    }
}
Example #22
0
VisibleSelection DirectionGranularityStrategy::updateExtent(
    const IntPoint& extentPoint,
    LocalFrame* frame) {
  const VisibleSelection& selection = frame->selection().selection();

  if (m_state == StrategyState::Cleared)
    m_state = StrategyState::Expanding;

  VisiblePosition oldOffsetExtentPosition = selection.visibleExtent();
  IntPoint oldExtentLocation = positionLocation(oldOffsetExtentPosition);

  IntPoint oldOffsetExtentPoint =
      oldExtentLocation + m_diffExtentPointFromExtentPosition;
  IntPoint oldExtentPoint =
      IntPoint(oldOffsetExtentPoint.x() - m_offset, oldOffsetExtentPoint.y());

  // Apply the offset.
  IntPoint newOffsetExtentPoint = extentPoint;
  int dx = extentPoint.x() - oldExtentPoint.x();
  if (m_offset != 0) {
    if (m_offset > 0 && dx > 0)
      m_offset = std::max(0, m_offset - dx);
    else if (m_offset < 0 && dx < 0)
      m_offset = std::min(0, m_offset - dx);
    newOffsetExtentPoint.move(m_offset, 0);
  }

  VisiblePosition newOffsetExtentPosition =
      visiblePositionForContentsPoint(newOffsetExtentPoint, frame);
  IntPoint newOffsetLocation = positionLocation(newOffsetExtentPosition);

  // Reset the offset in case of a vertical change in the location (could be
  // due to a line change or due to an unusual layout, e.g. rotated text).
  bool verticalChange = newOffsetLocation.y() != oldExtentLocation.y();
  if (verticalChange) {
    m_offset = 0;
    m_granularity = CharacterGranularity;
    newOffsetExtentPoint = extentPoint;
    newOffsetExtentPosition =
        visiblePositionForContentsPoint(extentPoint, frame);
  }

  const VisiblePosition base = selection.visibleBase();

  // Do not allow empty selection.
  if (newOffsetExtentPosition.deepEquivalent() == base.deepEquivalent())
    return selection;

  // The direction granularity strategy, particularly the "offset" feature
  // doesn't work with non-horizontal text (e.g. when the text is rotated).
  // So revert to the behavior equivalent to the character granularity
  // strategy if we detect that the text's baseline coordinate changed
  // without a line change.
  if (verticalChange &&
      inSameLine(newOffsetExtentPosition, oldOffsetExtentPosition)) {
    return createVisibleSelection(selection.visibleBase(),
                                  newOffsetExtentPosition);
  }

  int oldExtentBaseOrder = selection.isBaseFirst() ? 1 : -1;

  int newExtentBaseOrder;
  bool thisMoveShrunkSelection;
  if (newOffsetExtentPosition.deepEquivalent() ==
      oldOffsetExtentPosition.deepEquivalent()) {
    if (m_granularity == CharacterGranularity)
      return selection;

    // If we are in Word granularity, we cannot exit here, since we may pass
    // the middle of the word without changing the position (in which case
    // the selection needs to expand).
    thisMoveShrunkSelection = false;
    newExtentBaseOrder = oldExtentBaseOrder;
  } else {
    bool selectionExpanded = arePositionsInSpecifiedOrder(
        newOffsetExtentPosition, oldOffsetExtentPosition, oldExtentBaseOrder);
    bool extentBaseOrderSwitched =
        selectionExpanded
            ? false
            : !arePositionsInSpecifiedOrder(newOffsetExtentPosition, base,
                                            oldExtentBaseOrder);
    newExtentBaseOrder =
        extentBaseOrderSwitched ? -oldExtentBaseOrder : oldExtentBaseOrder;

    // Determine the word boundary, i.e. the boundary extending beyond which
    // should change the granularity to WordGranularity.
    VisiblePosition wordBoundary;
    if (extentBaseOrderSwitched) {
      // Special case.
      // If the extent-base order was switched, then the selection is now
      // expanding in a different direction than before. Therefore we
      // calculate the word boundary in this new direction and based on
      // the |base| position.
      wordBoundary = nextWordBound(base, newExtentBaseOrder > 0
                                             ? SearchDirection::SearchForward
                                             : SearchDirection::SearchBackwards,
                                   BoundAdjust::NextBoundIfOnBound);
      m_granularity = CharacterGranularity;
    } else {
      // Calculate the word boundary based on |oldExtentWithGranularity|.
      // If selection was shrunk in the last update and the extent is now
      // exactly on the word boundary - we need to take the next bound as
      // the bound of the current word.
      wordBoundary = nextWordBound(oldOffsetExtentPosition,
                                   oldExtentBaseOrder > 0
                                       ? SearchDirection::SearchForward
                                       : SearchDirection::SearchBackwards,
                                   m_state == StrategyState::Shrinking
                                       ? BoundAdjust::NextBoundIfOnBound
                                       : BoundAdjust::CurrentPosIfOnBound);
    }

    bool expandedBeyondWordBoundary;
    if (selectionExpanded)
      expandedBeyondWordBoundary = arePositionsInSpecifiedOrder(
          newOffsetExtentPosition, wordBoundary, newExtentBaseOrder);
    else if (extentBaseOrderSwitched)
      expandedBeyondWordBoundary = arePositionsInSpecifiedOrder(
          newOffsetExtentPosition, wordBoundary, newExtentBaseOrder);
    else
      expandedBeyondWordBoundary = false;

    // The selection is shrunk if the extent changes position to be closer to
    // the base, and the extent/base order wasn't switched.
    thisMoveShrunkSelection = !extentBaseOrderSwitched && !selectionExpanded;

    if (expandedBeyondWordBoundary)
      m_granularity = WordGranularity;
    else if (thisMoveShrunkSelection)
      m_granularity = CharacterGranularity;
  }

  VisiblePosition newSelectionExtent = newOffsetExtentPosition;
  if (m_granularity == WordGranularity) {
    // Determine the bounds of the word where the extent is located.
    // Set the selection extent to one of the two bounds depending on
    // whether the extent is passed the middle of the word.
    VisiblePosition boundBeforeExtent =
        nextWordBound(newOffsetExtentPosition, SearchDirection::SearchBackwards,
                      BoundAdjust::CurrentPosIfOnBound);
    VisiblePosition boundAfterExtent =
        nextWordBound(newOffsetExtentPosition, SearchDirection::SearchForward,
                      BoundAdjust::CurrentPosIfOnBound);
    int xMiddleBetweenBounds = (positionLocation(boundAfterExtent).x() +
                                positionLocation(boundBeforeExtent).x()) /
                               2;
    bool offsetExtentBeforeMiddle =
        newOffsetExtentPoint.x() < xMiddleBetweenBounds;
    newSelectionExtent =
        offsetExtentBeforeMiddle ? boundBeforeExtent : boundAfterExtent;
    // Update the offset if selection expanded in word granularity.
    if (newSelectionExtent.deepEquivalent() !=
            selection.visibleExtent().deepEquivalent() &&
        ((newExtentBaseOrder > 0 && !offsetExtentBeforeMiddle) ||
         (newExtentBaseOrder < 0 && offsetExtentBeforeMiddle))) {
      m_offset = positionLocation(newSelectionExtent).x() - extentPoint.x();
    }
  }

  // Only update the state if the selection actually changed as a result of
  // this move.
  if (newSelectionExtent.deepEquivalent() !=
      selection.visibleExtent().deepEquivalent())
    m_state = thisMoveShrunkSelection ? StrategyState::Shrinking
                                      : StrategyState::Expanding;

  m_diffExtentPointFromExtentPosition =
      extentPoint + IntSize(m_offset, 0) - positionLocation(newSelectionExtent);
  VisibleSelection newSelection = selection;
  newSelection.setExtent(newSelectionExtent);
  return newSelection;
}
Example #23
0
void Selection::setExtent(const VisiblePosition& visiblePosition)
{
    m_extent = visiblePosition.deepEquivalent();
    validate();
}
Example #24
0
void IndentOutdentCommand::outdentParagraph()
{
    VisiblePosition visibleStartOfParagraph = startOfParagraph(endingSelection().visibleStart());
    VisiblePosition visibleEndOfParagraph = endOfParagraph(visibleStartOfParagraph);

    Node* enclosingNode = enclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), &isListOrIndentBlockquote);
    if (!enclosingNode || !enclosingNode->parentNode()->isContentEditable())  // We can't outdent if there is no place to go!
        return;

    // Use InsertListCommand to remove the selection from the list
    if (enclosingNode->hasTagName(olTag)) {
        applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::OrderedList));
        return;
    }
    if (enclosingNode->hasTagName(ulTag)) {
        applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::UnorderedList));
        return;
    }

    // The selection is inside a blockquote i.e. enclosingNode is a blockquote
    VisiblePosition positionInEnclosingBlock = VisiblePosition(firstPositionInNode(enclosingNode));
    // If the blockquote is inline, the start of the enclosing block coincides with
    // positionInEnclosingBlock.
    VisiblePosition startOfEnclosingBlock = (enclosingNode->renderer() && enclosingNode->renderer()->isInline()) ? positionInEnclosingBlock : startOfBlock(positionInEnclosingBlock);
    VisiblePosition lastPositionInEnclosingBlock = VisiblePosition(lastPositionInNode(enclosingNode));
    VisiblePosition endOfEnclosingBlock = endOfBlock(lastPositionInEnclosingBlock);
    if (visibleStartOfParagraph == startOfEnclosingBlock &&
            visibleEndOfParagraph == endOfEnclosingBlock) {
        // The blockquote doesn't contain anything outside the paragraph, so it can be totally removed.
        Node* splitPoint = enclosingNode->nextSibling();
        removeNodePreservingChildren(enclosingNode);
        // outdentRegion() assumes it is operating on the first paragraph of an enclosing blockquote, but if there are multiply nested blockquotes and we've
        // just removed one, then this assumption isn't true. By splitting the next containing blockquote after this node, we keep this assumption true
        if (splitPoint) {
            if (ContainerNode* splitPointParent = splitPoint->parentNode()) {
                if (splitPointParent->hasTagName(blockquoteTag)
                        && !splitPoint->hasTagName(blockquoteTag)
                        && splitPointParent->parentNode()->isContentEditable()) // We can't outdent if there is no place to go!
                    splitElement(static_cast<Element*>(splitPointParent), splitPoint);
            }
        }

        updateLayout();
        visibleStartOfParagraph = VisiblePosition(visibleStartOfParagraph.deepEquivalent());
        visibleEndOfParagraph = VisiblePosition(visibleEndOfParagraph.deepEquivalent());
        if (visibleStartOfParagraph.isNotNull() && !isStartOfParagraph(visibleStartOfParagraph))
            insertNodeAt(createBreakElement(document()), visibleStartOfParagraph.deepEquivalent());
        if (visibleEndOfParagraph.isNotNull() && !isEndOfParagraph(visibleEndOfParagraph))
            insertNodeAt(createBreakElement(document()), visibleEndOfParagraph.deepEquivalent());

        return;
    }
    Node* enclosingBlockFlow = enclosingBlock(visibleStartOfParagraph.deepEquivalent().node());
    RefPtr<Node> splitBlockquoteNode = enclosingNode;
    if (enclosingBlockFlow != enclosingNode)
        splitBlockquoteNode = splitTreeToNode(enclosingBlockFlow, enclosingNode, true);
    else {
        // We split the blockquote at where we start outdenting.
        splitElement(static_cast<Element*>(enclosingNode), visibleStartOfParagraph.deepEquivalent().node());
    }
    RefPtr<Node> placeholder = createBreakElement(document());
    insertNodeBefore(placeholder, splitBlockquoteNode);
    moveParagraph(startOfParagraph(visibleStartOfParagraph), endOfParagraph(visibleEndOfParagraph), positionBeforeNode(placeholder.get()), true);
}
Example #25
0
void Selection::adjustForEditableContent()
{
    if (m_base.isNull() || m_start.isNull() || m_end.isNull())
        return;

    Node* baseRoot = highestEditableRoot(m_base);
    Node* startRoot = highestEditableRoot(m_start);
    Node* endRoot = highestEditableRoot(m_end);
    
    Node* baseEditableAncestor = lowestEditableAncestor(m_base.node());
    
    // The base, start and end are all in the same region.  No adjustment necessary.
    if (baseRoot == startRoot && baseRoot == endRoot)
        return;
    
    // The selection is based in editable content.
    if (baseRoot) {
        // If the start is outside the base's editable root, cap it at the start of that root.
        // If the start is in non-editable content that is inside the base's editable root, put it
        // at the first editable position after start inside the base's editable root.
        if (startRoot != baseRoot) {
            VisiblePosition first = firstEditablePositionAfterPositionInRoot(m_start, baseRoot);
            m_start = first.deepEquivalent();
            if (m_start.isNull()) {
                ASSERT_NOT_REACHED();
                m_start = m_end;
            }
        }
        // If the end is outside the base's editable root, cap it at the end of that root.
        // If the end is in non-editable content that is inside the base's root, put it
        // at the last editable position before the end inside the base's root.
        if (endRoot != baseRoot) {
            VisiblePosition last = lastEditablePositionBeforePositionInRoot(m_end, baseRoot);
            m_end = last.deepEquivalent();
            if (m_end.isNull()) {
                ASSERT_NOT_REACHED();
                m_end = m_start;
            }
        }
    // The selection is based in non-editable content.
    } else {
        // FIXME: Non-editable pieces inside editable content should be atomic, in the same way that editable
        // pieces in non-editable content are atomic.
    
        // The selection ends in editable content or non-editable content inside a different editable ancestor, 
        // move backward until non-editable content inside the same lowest editable ancestor is reached.
        Node* endEditableAncestor = lowestEditableAncestor(m_end.node());
        if (endRoot || endEditableAncestor != baseEditableAncestor) {
            
            Position p = previousVisuallyDistinctCandidate(m_end);
            Node* shadowAncestor = endRoot ? endRoot->shadowAncestorNode() : 0;
            if (p.isNull() && endRoot && (shadowAncestor != endRoot))
                p = Position(shadowAncestor, maxDeepOffset(shadowAncestor));
            while (p.isNotNull() && !(lowestEditableAncestor(p.node()) == baseEditableAncestor && !isEditablePosition(p))) {
                Node* root = editableRootForPosition(p);
                shadowAncestor = root ? root->shadowAncestorNode() : 0;
                p = isAtomicNode(p.node()) ? positionBeforeNode(p.node()) : previousVisuallyDistinctCandidate(p);
                if (p.isNull() && (shadowAncestor != root))
                    p = Position(shadowAncestor, maxDeepOffset(shadowAncestor));
            }
            VisiblePosition previous(p);
            
            if (previous.isNull()) {
                ASSERT_NOT_REACHED();
                m_base = Position();
                m_extent = Position();
                validate();
                return;
            }
            m_end = previous.deepEquivalent();
        }

        // The selection starts in editable content or non-editable content inside a different editable ancestor, 
        // move forward until non-editable content inside the same lowest editable ancestor is reached.
        Node* startEditableAncestor = lowestEditableAncestor(m_start.node());      
        if (startRoot || startEditableAncestor != baseEditableAncestor) {
            Position p = nextVisuallyDistinctCandidate(m_start);
            Node* shadowAncestor = startRoot ? startRoot->shadowAncestorNode() : 0;
            if (p.isNull() && startRoot && (shadowAncestor != startRoot))
                p = Position(shadowAncestor, 0);
            while (p.isNotNull() && !(lowestEditableAncestor(p.node()) == baseEditableAncestor && !isEditablePosition(p))) {
                Node* root = editableRootForPosition(p);
                shadowAncestor = root ? root->shadowAncestorNode() : 0;
                p = isAtomicNode(p.node()) ? positionAfterNode(p.node()) : nextVisuallyDistinctCandidate(p);
                if (p.isNull() && (shadowAncestor != root))
                    p = Position(shadowAncestor, 0);
            }
            VisiblePosition next(p);
            
            if (next.isNull()) {
                ASSERT_NOT_REACHED();
                m_base = Position();
                m_extent = Position();
                validate();
                return;
            }
            m_start = next.deepEquivalent();
        }
    }
    
    // Correct the extent if necessary.
    if (baseEditableAncestor != lowestEditableAncestor(m_extent.node()))
        m_extent = m_baseIsFirst ? m_end : m_start;
}
Example #26
0
bool CaretBase::updateCaretRect(Document* document, const VisiblePosition& caretPosition)
{
    return updateCaretRect(document, PositionWithAffinity(caretPosition.deepEquivalent(), caretPosition.affinity()));
}
static bool inSameTreeAndOrdered(const VisiblePosition& shouldBeFormer, const VisiblePosition& shouldBeLater)
{
    const Position formerPosition = shouldBeFormer.deepEquivalent();
    const Position laterPosition = shouldBeLater.deepEquivalent();
    return Position::commonAncestorTreeScope(formerPosition, laterPosition) && comparePositions(formerPosition, laterPosition) <= 0;
}
Example #28
0
void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(TextGranularity granularity)
{
    if (m_baseIsFirst) {
        m_start = m_base;
        m_end = m_extent;
    } else {
        m_start = m_extent;
        m_end = m_base;
    }

    switch (granularity) {
        case CharacterGranularity:
            // Don't do any expansion.
            break;
        case WordGranularity: {
            // General case: Select the word the caret is positioned inside of, or at the start of (RightWordIfOnBoundary).
            // Edge case: If the caret is after the last word in a soft-wrapped line or the last word in
            // the document, select that last word (LeftWordIfOnBoundary).
            // Edge case: If the caret is after the last word in a paragraph, select from the the end of the
            // last word to the line break (also RightWordIfOnBoundary);
            VisiblePosition start = VisiblePosition(m_start, m_affinity);
            VisiblePosition originalEnd(m_end, m_affinity);
            EWordSide side = RightWordIfOnBoundary;
            if (isEndOfEditableOrNonEditableContent(start) || (isEndOfLine(start) && !isStartOfLine(start) && !isEndOfParagraph(start)))
                side = LeftWordIfOnBoundary;
            m_start = startOfWord(start, side).deepEquivalent();
            side = RightWordIfOnBoundary;
            if (isEndOfEditableOrNonEditableContent(originalEnd) || (isEndOfLine(originalEnd) && !isStartOfLine(originalEnd) && !isEndOfParagraph(originalEnd)))
                side = LeftWordIfOnBoundary;

            VisiblePosition wordEnd(endOfWord(originalEnd, side));
            VisiblePosition end(wordEnd);

            if (isEndOfParagraph(originalEnd) && !isEmptyTableCell(m_start.deprecatedNode())) {
                // Select the paragraph break (the space from the end of a paragraph to the start of
                // the next one) to match TextEdit.
                end = wordEnd.next();

                if (Element* table = isFirstPositionAfterTable(end)) {
                    // The paragraph break after the last paragraph in the last cell of a block table ends
                    // at the start of the paragraph after the table.
                    if (isBlock(table))
                        end = end.next(CannotCrossEditingBoundary);
                    else
                        end = wordEnd;
                }

                if (end.isNull())
                    end = wordEnd;

            }

            m_end = end.deepEquivalent();
            break;
        }
        case SentenceGranularity: {
            m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent();
            m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent();
            break;
        }
        case LineGranularity: {
            m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent();
            VisiblePosition end = endOfLine(VisiblePosition(m_end, m_affinity));
            // If the end of this line is at the end of a paragraph, include the space
            // after the end of the line in the selection.
            if (isEndOfParagraph(end)) {
                VisiblePosition next = end.next();
                if (next.isNotNull())
                    end = next;
            }
            m_end = end.deepEquivalent();
            break;
        }
        case LineBoundary:
            m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent();
            m_end = endOfLine(VisiblePosition(m_end, m_affinity)).deepEquivalent();
            break;
        case ParagraphGranularity: {
            VisiblePosition pos(m_start, m_affinity);
            if (isStartOfLine(pos) && isEndOfEditableOrNonEditableContent(pos))
                pos = pos.previous();
            m_start = startOfParagraph(pos).deepEquivalent();
            VisiblePosition visibleParagraphEnd = endOfParagraph(VisiblePosition(m_end, m_affinity));

            // Include the "paragraph break" (the space from the end of this paragraph to the start
            // of the next one) in the selection.
            VisiblePosition end(visibleParagraphEnd.next());

            if (Element* table = isFirstPositionAfterTable(end)) {
                // The paragraph break after the last paragraph in the last cell of a block table ends
                // at the start of the paragraph after the table, not at the position just after the table.
                if (isBlock(table))
                    end = end.next(CannotCrossEditingBoundary);
                // There is no parargraph break after the last paragraph in the last cell of an inline table.
                else
                    end = visibleParagraphEnd;
            }

            if (end.isNull())
                end = visibleParagraphEnd;

            m_end = end.deepEquivalent();
            break;
        }
        case DocumentBoundary:
            m_start = startOfDocument(VisiblePosition(m_start, m_affinity)).deepEquivalent();
            m_end = endOfDocument(VisiblePosition(m_end, m_affinity)).deepEquivalent();
            break;
        case ParagraphBoundary:
            m_start = startOfParagraph(VisiblePosition(m_start, m_affinity)).deepEquivalent();
            m_end = endOfParagraph(VisiblePosition(m_end, m_affinity)).deepEquivalent();
            break;
        case SentenceBoundary:
            m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent();
            m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent();
            break;
    }

    // Make sure we do not have a dangling start or end.
    if (m_start.isNull())
        m_start = m_end;
    if (m_end.isNull())
        m_end = m_start;
}
PassRefPtrWillBeRawPtr<HTMLElement> InsertListCommand::listifyParagraph(const VisiblePosition& originalStart, const HTMLQualifiedName& listTag)
{
    VisiblePosition start = startOfParagraph(originalStart, CanSkipOverEditingBoundary);
    VisiblePosition end = endOfParagraph(start, CanSkipOverEditingBoundary);

    if (start.isNull() || end.isNull())
        return nullptr;

    // Check for adjoining lists.
    RefPtrWillBeRawPtr<HTMLElement> listItemElement = createListItemElement(document());
    RefPtrWillBeRawPtr<HTMLBRElement> placeholder = createBreakElement(document());
    appendNode(placeholder, listItemElement);

    // Place list item into adjoining lists.
    HTMLElement* previousList = adjacentEnclosingList(start, previousPositionOf(start, CannotCrossEditingBoundary), listTag);
    HTMLElement* nextList = adjacentEnclosingList(start, nextPositionOf(end, CannotCrossEditingBoundary), listTag);
    RefPtrWillBeRawPtr<HTMLElement> listElement = nullptr;
    if (previousList) {
        appendNode(listItemElement, previousList);
    } else if (nextList) {
        insertNodeAt(listItemElement, positionBeforeNode(nextList));
    } else {
        // Create the list.
        listElement = createHTMLElement(document(), listTag);
        appendNode(listItemElement, listElement);

        if (start.deepEquivalent() == end.deepEquivalent() && isEnclosingBlock(start.deepEquivalent().anchorNode())) {
            // Inserting the list into an empty paragraph that isn't held open
            // by a br or a '\n', will invalidate start and end.  Insert
            // a placeholder and then recompute start and end.
            RefPtrWillBeRawPtr<HTMLBRElement> placeholder = insertBlockPlaceholder(start.deepEquivalent());
            start = createVisiblePosition(positionBeforeNode(placeholder.get()));
            end = start;
        }

        // Insert the list at a position visually equivalent to start of the
        // paragraph that is being moved into the list.
        // Try to avoid inserting it somewhere where it will be surrounded by
        // inline ancestors of start, since it is easier for editing to produce
        // clean markup when inline elements are pushed down as far as possible.
        Position insertionPos(mostBackwardCaretPosition(start.deepEquivalent()));
        // Also avoid the containing list item.
        Node* listChild = enclosingListChild(insertionPos.anchorNode());
        if (isHTMLLIElement(listChild))
            insertionPos = positionInParentBeforeNode(*listChild);

        insertNodeAt(listElement, insertionPos);

        // We inserted the list at the start of the content we're about to move
        // Update the start of content, so we don't try to move the list into itself.  bug 19066
        // Layout is necessary since start's node's inline layoutObjects may have been destroyed by the insertion
        // The end of the content may have changed after the insertion and layout so update it as well.
        if (insertionPos == start.deepEquivalent())
            start = originalStart;
    }

    // Inserting list element and list item list may change start of pargraph
    // to move. We calculate start of paragraph again.
    document().updateLayoutIgnorePendingStylesheets();
    start = startOfParagraph(start, CanSkipOverEditingBoundary);
    end = endOfParagraph(start, CanSkipOverEditingBoundary);
    moveParagraph(start, end, createVisiblePosition(positionBeforeNode(placeholder.get())), true);

    if (listElement)
        return mergeWithNeighboringLists(listElement);

    if (canMergeLists(previousList, nextList))
        mergeIdenticalElements(previousList, nextList);

    return listElement;
}
Example #30
0
VisiblePosition endOfDocument(const VisiblePosition &c)
{
    return endOfDocument(c.deepEquivalent().node());
}