예제 #1
0
static int indexForPosition(HTMLElement* innerEditor, const Position& passedPosition)
{
    if (!innerEditor || !innerEditor->contains(passedPosition.anchorNode()) || passedPosition.isNull())
        return 0;

    if (positionBeforeNode(innerEditor) == passedPosition)
        return 0;

    int index = 0;
    Node* startNode = passedPosition.computeNodeBeforePosition();
    if (!startNode)
        startNode = passedPosition.computeContainerNode();
    ASSERT(startNode);
    ASSERT(innerEditor->contains(startNode));

    for (Node* node = startNode; node; node = NodeTraversal::previous(*node, innerEditor)) {
        if (node->isTextNode()) {
            int length = toText(*node).length();
            if (node == passedPosition.computeContainerNode())
                index += std::min(length, passedPosition.offsetInContainerNode());
            else
                index += length;
        } else if (node->hasTagName(brTag)) {
            ++index;
        }
    }

    ASSERT(index >= 0);
    return index;
}
예제 #2
0
static Position positionForIndex(HTMLElement* innerEditor, int index)
{
    ASSERT(index >= 0);
    if (index == 0) {
        Node* node = NodeTraversal::next(*innerEditor, innerEditor);
        if (node && node->isTextNode())
            return Position(node, 0);
        return Position(innerEditor, 0);
    }
    int remainingCharactersToMoveForward = index;
    Node* lastBrOrText = innerEditor;
    for (Node& node : NodeTraversal::descendantsOf(*innerEditor)) {
        ASSERT(remainingCharactersToMoveForward >= 0);
        if (node.hasTagName(brTag)) {
            if (remainingCharactersToMoveForward == 0)
                return positionBeforeNode(&node);
            --remainingCharactersToMoveForward;
            lastBrOrText = &node;
            continue;
        }

        if (node.isTextNode()) {
            Text& text = toText(node);
            if (remainingCharactersToMoveForward < static_cast<int>(text.length()))
                return Position(&text, remainingCharactersToMoveForward);
            remainingCharactersToMoveForward -= text.length();
            lastBrOrText = &node;
            continue;
        }

        ASSERT_NOT_REACHED();
    }
    return lastPositionInOrAfterNode(lastBrOrText);
}
예제 #3
0
static Position adjustPositionForStart(const Position& currentPosition, Node* endContainerNode)
{
    TreeScope& treeScope = endContainerNode->treeScope();

    ASSERT(&currentPosition.containerNode()->treeScope() != &treeScope);
    
    if (Node* ancestor = treeScope.ancestorInThisScope(currentPosition.containerNode())) {
        if (ancestor->contains(endContainerNode))
            return positionBeforeNode(ancestor);
        return positionAfterNode(ancestor);
    }

    if (Node* firstChild = treeScope.rootNode().firstChild())
        return positionBeforeNode(firstChild);

    return Position();
}
static Position adjustPositionForEnd(const Position& currentPosition, Node* startContainerNode)
{
    TreeScope& treeScope = startContainerNode->treeScope();

    ASSERT(currentPosition.containerNode()->treeScope() != treeScope);

    if (Node* ancestor = treeScope.ancestorInThisScope(currentPosition.containerNode())) {
        if (ancestor->contains(startContainerNode))
            return positionAfterNode(ancestor);
        return positionBeforeNode(ancestor);
    }

    if (Node* lastChild = treeScope.rootNode().lastChild())
        return positionAfterNode(lastChild);

    return Position();
}
예제 #5
0
void CreateLinkCommand::doApply()
{
    if (endingSelection().isNone())
        return;
        
    RefPtr<HTMLAnchorElement> anchorElement = new HTMLAnchorElement(document());
    anchorElement->setHref(m_url);
    
    if (endingSelection().isRange()) {
        pushPartiallySelectedAnchorElementsDown();
        applyStyledElement(anchorElement.get());
    } else {
        insertNodeAt(anchorElement.get(), endingSelection().start());
        RefPtr<Text> textNode = new Text(document(), m_url);
        appendNode(textNode.get(), anchorElement.get());
        setEndingSelection(Selection(positionBeforeNode(anchorElement.get()), positionAfterNode(anchorElement.get()), DOWNSTREAM));
    }
}
static Position positionForIndex(TextControlInnerTextElement* innerText, unsigned index)
{
    unsigned remainingCharactersToMoveForward = index;
    Node* lastBrOrText = innerText;
    for (Node* node = innerText; node; node = NodeTraversal::next(*node, innerText)) {
        if (node->hasTagName(brTag)) {
            if (!remainingCharactersToMoveForward)
                return positionBeforeNode(node);
            remainingCharactersToMoveForward--;
            lastBrOrText = node;
        } else if (is<Text>(*node)) {
            Text& text = downcast<Text>(*node);
            if (remainingCharactersToMoveForward < text.length())
                return Position(&text, remainingCharactersToMoveForward);
            remainingCharactersToMoveForward -= text.length();
            lastBrOrText = node;
        }
    }
    return lastPositionInOrAfterNode(lastBrOrText);
}
void VisibleSelection::adjustSelectionToAvoidCrossingShadowBoundaries()
{
    if (m_base.isNull() || m_start.isNull() || m_end.isNull())
        return;

    Node* startRootNode = m_start.anchorNode()->shadowTreeRootNode();
    Node* endRootNode = m_end.anchorNode()->shadowTreeRootNode();

    if (!startRootNode && !endRootNode)
        return;

    if (startRootNode == endRootNode)
        return;

    if (m_baseIsFirst) {
        m_extent = startRootNode ? lastPositionInNode(startRootNode) : positionBeforeNode(endRootNode->shadowHost());
        m_end = m_extent;
    } else {
        m_extent = endRootNode ? firstPositionInNode(endRootNode) : positionAfterNode(startRootNode->shadowHost());
        m_start = m_extent;
    }
}
unsigned HTMLTextFormControlElement::indexForPosition(const Position& passedPosition) const
{
    TextControlInnerTextElement* innerText = innerTextElement();
    if (!innerText || !innerText->contains(passedPosition.anchorNode()) || passedPosition.isNull())
        return 0;

    if (positionBeforeNode(innerText) == passedPosition)
        return 0;

    unsigned index = 0;
    Node* startNode = passedPosition.computeNodeBeforePosition();
    if (!startNode)
        startNode = passedPosition.containerNode();
    ASSERT(startNode);
    ASSERT(innerText->contains(startNode));

    for (Node* node = startNode; node; node = NodeTraversal::previous(*node, innerText)) {
        if (is<Text>(*node)) {
            unsigned length = downcast<Text>(*node).length();
            if (node == passedPosition.containerNode())
                index += std::min<unsigned>(length, passedPosition.offsetInContainerNode());
            else
                index += length;
        } else if (is<HTMLBRElement>(*node))
            ++index;
    }

    unsigned length = innerTextValue().length();
    index = std::min(index, length); // FIXME: We shouldn't have to call innerTextValue() just to ignore the last LF. See finishText.
#ifndef ASSERT_DISABLED
    VisiblePosition visiblePosition = passedPosition;
    unsigned indexComputedByVisiblePosition = 0;
    if (visiblePosition.isNotNull())
        indexComputedByVisiblePosition = WebCore::indexForVisiblePosition(innerText, visiblePosition, false /* forSelectionPreservation */);
    ASSERT(index == indexComputedByVisiblePosition);
#endif
    return index;
}
예제 #9
0
Position toPositionInDOMTree(const PositionInComposedTree& position)
{
    if (position.isNull())
        return Position();

    Node* anchorNode = position.anchorNode();

    switch (position.anchorType()) {
    case PositionAnchorType::AfterChildren:
        // FIXME: When anchorNode is <img>, assertion fails in the constructor.
        return Position(anchorNode, PositionAnchorType::AfterChildren);
    case PositionAnchorType::AfterAnchor:
        return positionAfterNode(anchorNode);
    case PositionAnchorType::BeforeChildren:
        return Position(anchorNode, PositionAnchorType::BeforeChildren);
    case PositionAnchorType::BeforeAnchor:
        return positionBeforeNode(anchorNode);
    case PositionAnchorType::OffsetInAnchor: {
        int offset = position.offsetInContainerNode();
        if (anchorNode->offsetInCharacters())
            return Position(anchorNode, offset);
        Node* child = ComposedTreeTraversal::childAt(*anchorNode, offset);
        if (child)
            return Position(child->parentNode(), child->nodeIndex());
        if (!position.offsetInContainerNode())
            return Position(anchorNode, PositionAnchorType::BeforeChildren);

        // |child| is null when the position is at the end of the children.
        // <div>foo|</div>
        return Position(anchorNode, PositionAnchorType::AfterChildren);
    }
    default:
        ASSERT_NOT_REACHED();
        return Position();
    }
}
예제 #10
0
void VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries()
{
    if (m_base.isNull() || m_start.isNull() || m_end.isNull())
        return;

    // Early return in the caret case (the state hasn't actually been set yet, so we can't use isCaret()) to avoid the
    // expense of computing highestEditableRoot.
    if (m_base == m_start && m_base == m_end)
        return;

    Node* baseRoot = highestEditableRoot(m_base);
    Node* startRoot = highestEditableRoot(m_start);
    Node* endRoot = highestEditableRoot(m_end);
    
    Node* baseEditableAncestor = lowestEditableAncestor(m_base.containerNode());
    
    // 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())
                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.containerNode());
        if (endRoot || endEditableAncestor != baseEditableAncestor) {
            
            Position p = previousVisuallyDistinctCandidate(m_end);
            Node* shadowAncestor = endRoot ? endRoot->shadowHost() : 0;
            if (p.isNull() && shadowAncestor)
                p = positionAfterNode(shadowAncestor);
            while (p.isNotNull() && !(lowestEditableAncestor(p.containerNode()) == baseEditableAncestor && !isEditablePosition(p))) {
                Node* root = editableRootForPosition(p);
                shadowAncestor = root ? root->shadowHost() : 0;
                p = isAtomicNode(p.containerNode()) ? positionInParentBeforeNode(p.containerNode()) : previousVisuallyDistinctCandidate(p);
                if (p.isNull() && shadowAncestor)
                    p = positionAfterNode(shadowAncestor);
            }
            VisiblePosition previous(p);

            if (previous.isNull()) {
                // The selection crosses an Editing boundary.  This is a
                // programmer error in the editing code.  Happy debugging!
                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.containerNode());      
        if (startRoot || startEditableAncestor != baseEditableAncestor) {
            Position p = nextVisuallyDistinctCandidate(m_start);
            Node* shadowAncestor = startRoot ? startRoot->shadowHost() : 0;
            if (p.isNull() && shadowAncestor)
                p = positionBeforeNode(shadowAncestor);
            while (p.isNotNull() && !(lowestEditableAncestor(p.containerNode()) == baseEditableAncestor && !isEditablePosition(p))) {
                Node* root = editableRootForPosition(p);
                shadowAncestor = root ? root->shadowHost() : 0;
                p = isAtomicNode(p.containerNode()) ? positionInParentAfterNode(p.containerNode()) : nextVisuallyDistinctCandidate(p);
                if (p.isNull() && shadowAncestor)
                    p = positionBeforeNode(shadowAncestor);
            }
            VisiblePosition next(p);
            
            if (next.isNull()) {
                // The selection crosses an Editing boundary.  This is a
                // programmer error in the editing code.  Happy debugging!
                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.containerNode()))
        m_extent = m_baseIsFirst ? m_end : m_start;
}
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()
        // 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() && isRenderedTableElement(canonicalPos.deprecatedNode()))) {
        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 = nullptr;
    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<HTMLElement> extraBlock = createDefaultParagraphElement(document());
                appendNode(extraBlock, startBlock);
            }
            appendNode(blockToInsert, startBlock);
        } else {
            // 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* siblingElement = startBlock.get();
            insertNodeAfter(blockToInsert, siblingElement);
        }

        // 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);

        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 = 0;
        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->hasChildren());
            refNode = startBlock->firstChild();
        }
        else if (insertionPosition.deprecatedNode() == startBlock && nestNewBlock) {
            refNode = NodeTraversal::childAt(*startBlock, 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();

        if (refNode)
            insertNodeBefore(blockToInsert, refNode);

        // Recreate the same structure in the new paragraph.

        Vector<RefPtr<Element> > ancestors;
        getAncestorsInsideBlock(positionAvoidingSpecialElementBoundary(positionOutsideTabSpan(insertionPosition)).deprecatedNode(), startBlock.get(), ancestors);

        // In this case, we need to set the new ending selection.
        setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM, endingSelection().isDirectional()));
        return;
    }

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

    // 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 = leadingWhitespacePosition(insertionPosition, 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());
            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();

    // 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()));
}
예제 #12
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;
}
예제 #13
0
static void adjustPositionForUserSelectAll(VisiblePosition& pos, bool isForward)
{
    if (Node* rootUserSelectAll = EditingStrategy::rootUserSelectAllForNode(pos.deepEquivalent().anchorNode()))
        pos = createVisiblePosition(isForward ? mostForwardCaretPosition(positionAfterNode(rootUserSelectAll), CanCrossEditingBoundary) : mostBackwardCaretPosition(positionBeforeNode(rootUserSelectAll), CanCrossEditingBoundary));
}
예제 #14
0
void InsertTextCommand::input(const String& originalText, bool selectInsertedText)
{
    String text = originalText;
    
    ASSERT(text.find('\n') == -1);

    if (endingSelection().isNone())
        return;
        
    if (RenderObject* renderer = endingSelection().start().node()->renderer())
        if (renderer->style()->collapseWhiteSpace())
            // Turn all spaces into non breaking spaces, to make sure that they are treated
            // literally, and aren't collapsed after insertion. They will be rebalanced 
            // (turned into a sequence of regular and non breaking spaces) below.
            text.replace(' ', noBreakSpace);
    
    // Delete the current selection.
    // FIXME: This delete operation blows away the typing style.
    if (endingSelection().isRange()) {
        if (performTrivialReplace(text, selectInsertedText))
            return;
        deleteSelection(false, true, true, false);
    }
    
    // Insert the character at the leftmost candidate.
    Position startPosition = endingSelection().start().upstream();
    // It is possible for the node that contains startPosition to contain only unrendered whitespace,
    // and so deleteInsignificantText could remove it.  Save the position before the node in case that happens.
    Position positionBeforeStartNode(positionBeforeNode(startPosition.node()));
    deleteInsignificantText(startPosition.upstream(), startPosition.downstream());
    if (!startPosition.node()->inDocument())
        startPosition = positionBeforeStartNode;
    if (!startPosition.isCandidate())
        startPosition = startPosition.downstream();
    
    startPosition = positionAvoidingSpecialElementBoundary(startPosition);
    
    Position endPosition;
    
    if (text == "\t") {
        endPosition = insertTab(startPosition);
        startPosition = endPosition.previous();
        removePlaceholderAt(VisiblePosition(startPosition));
        m_charactersAdded += 1;
    } else {
        // Make sure the document is set up to receive text
        startPosition = prepareForTextInsertion(startPosition);
        removePlaceholderAt(VisiblePosition(startPosition));
        Text *textNode = static_cast<Text *>(startPosition.node());
        int offset = startPosition.offset();

        insertTextIntoNode(textNode, offset, text);
        endPosition = Position(textNode, offset + text.length());

        // The insertion may require adjusting adjacent whitespace, if it is present.
        rebalanceWhitespaceAt(endPosition);
        // Rebalancing on both sides isn't necessary if we've inserted a space.
        if (originalText != " ") 
            rebalanceWhitespaceAt(startPosition);
            
        m_charactersAdded += text.length();
    }

    // We could have inserted a part of composed character sequence,
    // so we are basically treating ending selection as a range to avoid validation.
    // <http://bugs.webkit.org/show_bug.cgi?id=15781>
    Selection forcedEndingSelection;
    forcedEndingSelection.setWithoutValidation(startPosition, endPosition);
    setEndingSelection(forcedEndingSelection);

    // Handle the case where there is a typing style.
    CSSMutableStyleDeclaration* typingStyle = document()->frame()->typingStyle();
    RefPtr<CSSComputedStyleDeclaration> endingStyle = endPosition.computedStyle();
    RefPtr<CSSValue> unicodeBidi;
    RefPtr<CSSValue> direction;
    if (typingStyle) {
        unicodeBidi = typingStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
        direction = typingStyle->getPropertyCSSValue(CSSPropertyDirection);
    }
    endingStyle->diff(typingStyle);
    if (typingStyle && unicodeBidi) {
        ASSERT(unicodeBidi->isPrimitiveValue());
        typingStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent());
        if (direction) {
            ASSERT(direction->isPrimitiveValue());
            typingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
        }
    }

    if (typingStyle && typingStyle->length())
        applyStyle(typingStyle);

    if (!selectInsertedText)
        setEndingSelection(Selection(endingSelection().end(), endingSelection().affinity()));
}