Exemple #1
0
static char* webkitAccessibleTextGetWordForBoundary(AtkText* text, int offset, AtkTextBoundary boundaryType, GetTextRelativePosition textPosition, int* startOffset, int* endOffset)
{
    AccessibilityObject* coreObject = core(text);
    Document* document = coreObject->document();
    if (!document)
        return emptyTextSelectionAtOffset(0, startOffset, endOffset);

    Node* node = getNodeForAccessibilityObject(coreObject);
    if (!node)
        return emptyTextSelectionAtOffset(0, startOffset, endOffset);

    int actualOffset = atkOffsetToWebCoreOffset(text, offset);

    // Besides of the usual conversion from ATK offsets to WebCore offsets,
    // we need to consider the potential embedded objects that might have been
    // inserted in the text exposed through AtkText when calculating the offset.
    actualOffset -= numberOfReplacedElementsBeforeOffset(text, actualOffset);

    VisiblePosition caretPosition = coreObject->visiblePositionForIndex(actualOffset);
    VisibleSelection currentWord = wordAtPositionForAtkBoundary(coreObject, caretPosition, boundaryType);

    // Take into account other relative positions, if needed, by
    // calculating the new position that we would need to consider.
    VisiblePosition newPosition = caretPosition;
    switch (textPosition) {
    case GetTextPositionAt:
        break;

    case GetTextPositionBefore:
        // Early return if asking for the previous word while already at the beginning.
        if (isFirstVisiblePositionInNode(currentWord.visibleStart(), node))
            return emptyTextSelectionAtOffset(0, startOffset, endOffset);

        if (isStartOfLine(currentWord.end()))
            newPosition = currentWord.visibleStart().previous();
        else
            newPosition = startOfWord(currentWord.start(), LeftWordIfOnBoundary);
        break;

    case GetTextPositionAfter:
        // Early return if asking for the following word while already at the end.
        if (isLastVisiblePositionInNode(currentWord.visibleEnd(), node))
            return emptyTextSelectionAtOffset(accessibilityObjectLength(coreObject), startOffset, endOffset);

        if (isEndOfLine(currentWord.end()))
            newPosition = currentWord.visibleEnd().next();
        else
            newPosition = endOfWord(currentWord.end(), RightWordIfOnBoundary);
        break;

    default:
        ASSERT_NOT_REACHED();
    }

    // Determine the relevant word we are actually interested in
    // and calculate the ATK offsets for it, then return everything.
    VisibleSelection selectedWord = newPosition != caretPosition ? wordAtPositionForAtkBoundary(coreObject, newPosition, boundaryType) : currentWord;
    getSelectionOffsetsForObject(coreObject, selectedWord, *startOffset, *endOffset);
    return webkitAccessibleTextGetText(text, *startOffset, *endOffset);
}
Exemple #2
0
VisibleSelection visibleSelectionForClosestActualWordStart(const VisibleSelection& selection)
{
    // VisibleSelection validation has a special case when the caret is at the end of a paragraph where
    // it selects the paragraph marker. As well, if the position is at the end of a word, it will select
    // only the space between words. We want to select an actual word so we move the selection to
    // the start of the leftmost word if the character after the selection point is whitespace.

    if (selection.selectionType() != VisibleSelection::RangeSelection) {
        int leftDistance = 0;
        int rightDistance = 0;

        VisibleSelection leftSelection(previousWordPosition(selection.start()));
        bool leftSelectionIsOnWord = !isWhitespace(leftSelection.visibleStart().characterAfter()) && leftSelection.start().containerNode() == selection.start().containerNode();
        if (leftSelectionIsOnWord) {
            VisibleSelection rangeSelection(endOfWord(leftSelection.start()), selection.visibleStart());
            leftDistance = TextIterator::rangeLength(rangeSelection.toNormalizedRange().get());
        }

        VisibleSelection rightSelection = previousWordPosition(nextWordPosition(selection.start()));
        bool rightSelectionIsOnWord = !isWhitespace(rightSelection.visibleStart().characterAfter()) && rightSelection.start().containerNode() == selection.start().containerNode();
        if (rightSelectionIsOnWord) {
            VisibleSelection rangeSelection = VisibleSelection(rightSelection.visibleStart(), selection.visibleStart());
            rightDistance = TextIterator::rangeLength(rangeSelection.toNormalizedRange().get());
        }

        // Make sure we found an actual word. If not, return the original selection.
        if (!leftSelectionIsOnWord && !rightSelectionIsOnWord)
            return selection;

        if (!rightSelectionIsOnWord || (leftSelectionIsOnWord && leftDistance <= rightDistance)) {
            // Left is closer or right is invalid.
            return leftSelection;
        }

        // Right is closer or equal, or left was invalid.
        return rightSelection;
    }

    // No adjustment required.
    return selection;
}
Exemple #3
0
void SpellChecker::spellCheckOldSelection(const VisibleSelection& oldSelection, const VisibleSelection& newAdjacentWords)
{
    VisiblePosition oldStart(oldSelection.visibleStart());
    VisibleSelection oldAdjacentWords = VisibleSelection(startOfWord(oldStart, LeftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary));
    if (oldAdjacentWords  != newAdjacentWords) {
        if (isContinuousSpellCheckingEnabled() && isGrammarCheckingEnabled()) {
            VisibleSelection selectedSentence = VisibleSelection(startOfSentence(oldStart), endOfSentence(oldStart));
            markMisspellingsAndBadGrammar(oldAdjacentWords, true, selectedSentence);
        } else {
            markMisspellingsAndBadGrammar(oldAdjacentWords, false, oldAdjacentWords);
        }
    }
}
Exemple #4
0
void SpellChecker::respondToChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions options)
{
    bool closeTyping = options & FrameSelection::CloseTyping;
    bool isContinuousSpellCheckingEnabled = this->isContinuousSpellCheckingEnabled();
    bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabled && isGrammarCheckingEnabled();
    if (isContinuousSpellCheckingEnabled) {
        VisibleSelection newAdjacentWords;
        VisibleSelection newSelectedSentence;
        const VisibleSelection newSelection = m_frame.selection().selection();
        VisiblePosition newStart(newSelection.visibleStart());
        newAdjacentWords = VisibleSelection(startOfWord(newStart, LeftWordIfOnBoundary), endOfWord(newStart, RightWordIfOnBoundary));
        if (isContinuousGrammarCheckingEnabled)
            newSelectedSentence = VisibleSelection(startOfSentence(newStart), endOfSentence(newStart));

        // Don't check spelling and grammar if the change of selection is triggered by spelling correction itself.
        bool shouldCheckSpellingAndGrammar = !(options & FrameSelection::SpellCorrectionTriggered);

        // When typing we check spelling elsewhere, so don't redo it here.
        // If this is a change in selection resulting from a delete operation,
        // oldSelection may no longer be in the document.
        // FIXME(http://crbug.com/382809): if oldSelection is on a textarea
        // element, we cause synchronous layout.
        if (shouldCheckSpellingAndGrammar
            && closeTyping
            && !isSelectionInTextField(oldSelection)
            && (isSelectionInTextArea(oldSelection) || oldSelection.isContentEditable())
            && oldSelection.start().inDocument()) {
            spellCheckOldSelection(oldSelection, newAdjacentWords);
        }

        // FIXME(http://crbug.com/382809):
        // shouldEraseMarkersAfterChangeSelection is true, we cause synchronous
        // layout.
        if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTypeSpelling)) {
            if (RefPtr<Range> wordRange = newAdjacentWords.toNormalizedRange())
                m_frame.document()->markers().removeMarkers(wordRange.get(), DocumentMarker::Spelling);
        }
        if (textChecker().shouldEraseMarkersAfterChangeSelection(TextCheckingTypeGrammar)) {
            if (RefPtr<Range> sentenceRange = newSelectedSentence.toNormalizedRange())
                m_frame.document()->markers().removeMarkers(sentenceRange.get(), DocumentMarker::Grammar);
        }
    }

    // When continuous spell checking is off, existing markers disappear after the selection changes.
    if (!isContinuousSpellCheckingEnabled)
        m_frame.document()->markers().removeMarkers(DocumentMarker::Spelling);
    if (!isContinuousGrammarCheckingEnabled)
        m_frame.document()->markers().removeMarkers(DocumentMarker::Grammar);
}
void IndentOutdentCommand::indentRegion()
{
    VisibleSelection selection = selectionForParagraphIteration(endingSelection());
    VisiblePosition startOfSelection = selection.visibleStart();
    VisiblePosition endOfSelection = selection.visibleEnd();
    int startIndex = indexForVisiblePosition(startOfSelection);
    int endIndex = indexForVisiblePosition(endOfSelection);

    ASSERT(!startOfSelection.isNull());
    ASSERT(!endOfSelection.isNull());

    // Special case empty unsplittable elements because there's nothing to split
    // and there's nothing to move.
    Position start = startOfSelection.deepEquivalent().downstream();
    if (isAtUnsplittableElement(start)) {
        RefPtr<Element> blockquote = createIndentBlockquoteElement(document());
        insertNodeAt(blockquote, start);
        RefPtr<Element> placeholder = createBreakElement(document());
        appendNode(placeholder, blockquote);
        setEndingSelection(VisibleSelection(Position(placeholder.get(), 0), DOWNSTREAM));
        return;
    }

    RefPtr<Element> blockquoteForNextIndent;
    VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection);
    VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next());
    while (endOfCurrentParagraph != endAfterSelection) {
        // Iterate across the selected paragraphs...
        VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
        if (tryIndentingAsListItem(endOfCurrentParagraph))
            blockquoteForNextIndent = 0;
        else
            indentIntoBlockquote(endOfCurrentParagraph, endOfNextParagraph, blockquoteForNextIndent);
        // blockquoteForNextIndent maybe updated
        // this is due to the way prepareBlockquoteLevelForInsertion was designed.
        // Sanity check: Make sure our moveParagraph calls didn't remove endOfNextParagraph.deepEquivalent().node()
        // If somehow we did, return to prevent crashes.
        if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().node()->inDocument()) {
            ASSERT_NOT_REACHED();
            return;
        }
        endOfCurrentParagraph = endOfNextParagraph;
    }

    RefPtr<Range> startRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, 0, true);
    RefPtr<Range> endRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endIndex, 0, true);
    if (startRange && endRange)
        setEndingSelection(VisibleSelection(startRange->startPosition(), endRange->startPosition(), DOWNSTREAM));
}
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)) {
        VisibleSelection newSelection(visibleStart, visibleEnd.previous(CannotCrossEditingBoundary), endingSelection().isDirectional());
        if (newSelection.isNone())
            return;
        setEndingSelection(newSelection);
    }

    VisibleSelection selection = selectionForParagraphIteration(endingSelection());
    VisiblePosition startOfSelection = selection.visibleStart();
    VisiblePosition endOfSelection = selection.visibleEnd();
    ASSERT(!startOfSelection.isNull());
    ASSERT(!endOfSelection.isNull());
    RefPtrWillBeRawPtr<ContainerNode> startScope = nullptr;
    int startIndex = indexForVisiblePosition(startOfSelection, startScope);
    RefPtrWillBeRawPtr<ContainerNode> endScope = nullptr;
    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()));
    }
}
bool InsertListCommand::selectionHasListOfType(const VisibleSelection& selection, const HTMLQualifiedName& listTag)
{
    VisiblePosition start = selection.visibleStart();

    if (!enclosingList(start.deepEquivalent().deprecatedNode()))
        return false;

    VisiblePosition end = startOfParagraph(selection.visibleEnd());
    while (start.isNotNull() && start != end) {
        HTMLElement* listElement = enclosingList(start.deepEquivalent().deprecatedNode());
        if (!listElement || !listElement->hasTagName(listTag))
            return false;
        start = startOfNextParagraph(start);
    }

    return true;
}
bool InsertListCommand::modifyRange()
{
    VisibleSelection selection = selectionForParagraphIteration(endingSelection());
    ASSERT(selection.isRange());
    VisiblePosition startOfSelection = selection.visibleStart();
    VisiblePosition endOfSelection = selection.visibleEnd();
    VisiblePosition startOfLastParagraph = startOfParagraph(endOfSelection);
    
    if (startOfParagraph(startOfSelection) == startOfLastParagraph)
        return false;

    Node* startList = enclosingList(startOfSelection.deepEquivalent().node());
    Node* endList = enclosingList(endOfSelection.deepEquivalent().node());
    if (!startList || startList != endList)
        m_forceCreateList = true;

    setEndingSelection(startOfSelection);
    doApply();
    // Fetch the start of the selection after moving the first paragraph,
    // because moving the paragraph will invalidate the original start.  
    // We'll use the new start to restore the original selection after 
    // we modified all selected paragraphs.
    startOfSelection = endingSelection().visibleStart();
    VisiblePosition startOfCurrentParagraph = startOfNextParagraph(startOfSelection);
    while (startOfCurrentParagraph != startOfLastParagraph) {
        // 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().node()->inDocument())
            return true;
        setEndingSelection(startOfCurrentParagraph);
        doApply();
        startOfCurrentParagraph = startOfNextParagraph(endingSelection().visibleStart());
    }
    setEndingSelection(endOfSelection);
    doApply();
    // Fetch the end of the selection, for the reason mentioned above.
    endOfSelection = endingSelection().visibleEnd();
    setEndingSelection(VisibleSelection(startOfSelection, endOfSelection));
    m_forceCreateList = false;
    return true;
}
void IndentOutdentCommand::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 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(true)));

    VisibleSelection selection = selectionForParagraphIteration(endingSelection());
    VisiblePosition startOfSelection = selection.visibleStart();
    VisiblePosition endOfSelection = selection.visibleEnd();
    
    int startIndex = indexForVisiblePosition(startOfSelection);
    int endIndex = indexForVisiblePosition(endOfSelection);
    
    ASSERT(!startOfSelection.isNull());
    ASSERT(!endOfSelection.isNull());
    
    if (m_typeOfAction == Indent)
        indentRegion(startOfSelection, endOfSelection);
    else
        outdentRegion(startOfSelection, endOfSelection);

    updateLayout();
    
    RefPtr<Range> startRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, 0, true);
    RefPtr<Range> endRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endIndex, 0, true);
    if (startRange && endRange)
        setEndingSelection(VisibleSelection(startRange->startPosition(), endRange->startPosition(), DOWNSTREAM));
}
Exemple #10
0
void SpellChecker::markMisspellingsAfterLineBreak(const VisibleSelection& wordSelection)
{
    if (unifiedTextCheckerEnabled()) {
        TextCheckingTypeMask textCheckingOptions = 0;

        if (isContinuousSpellCheckingEnabled())
            textCheckingOptions |= TextCheckingTypeSpelling;

        if (isGrammarCheckingEnabled())
            textCheckingOptions |= TextCheckingTypeGrammar;

        VisibleSelection wholeParagraph(
            startOfParagraph(wordSelection.visibleStart()),
            endOfParagraph(wordSelection.visibleEnd()));

        markAllMisspellingsAndBadGrammarInRanges(
            textCheckingOptions, wordSelection.toNormalizedRange().get(),
            wholeParagraph.toNormalizedRange().get());
    } else {
        RefPtr<Range> misspellingRange = wordSelection.firstRange();
        markMisspellings(wordSelection, misspellingRange);
    }
}
bool InsertListCommand::selectionHasListOfType(
    const VisibleSelection& selection,
    const HTMLQualifiedName& listTag) {
    DCHECK(!document().needsLayoutTreeUpdate());
    DocumentLifecycle::DisallowTransitionScope disallowTransition(
        document().lifecycle());

    VisiblePosition start = selection.visibleStart();

    if (!enclosingList(start.deepEquivalent().anchorNode()))
        return false;

    VisiblePosition end = startOfParagraph(selection.visibleEnd());
    while (start.isNotNull() && start.deepEquivalent() != end.deepEquivalent()) {
        HTMLElement* listElement =
            enclosingList(start.deepEquivalent().anchorNode());
        if (!listElement || !listElement->hasTagName(listTag))
            return false;
        start = startOfNextParagraph(start);
    }

    return true;
}
Exemple #12
0
void Editor::transpose()
{
    if (!canEdit())
        return;

    VisibleSelection selection = m_frame.selection().selection();
    if (!selection.isCaret())
        return;

    // Make a selection that goes back one character and forward two characters.
    VisiblePosition caret = selection.visibleStart();
    VisiblePosition next = isEndOfParagraph(caret) ? caret : caret.next();
    VisiblePosition previous = next.previous();
    if (next == previous)
        return;
    previous = previous.previous();
    if (!inSameParagraph(next, previous))
        return;
    RefPtr<Range> range = makeRange(previous, next);
    if (!range)
        return;
    VisibleSelection newSelection(range.get(), DOWNSTREAM);

    // Transpose the two characters.
    String text = plainText(range.get());
    if (text.length() != 2)
        return;
    String transposed = text.right(1) + text.left(1);

    // Select the two characters.
    if (newSelection != m_frame.selection().selection())
        m_frame.selection().setSelection(newSelection);

    // Insert the transposed characters.
    replaceSelectionWithText(transposed, false, false);
}
void InsertLineBreakCommand::doApply()
{
    deleteSelection();
    VisibleSelection selection = endingSelection();
    if (!selection.isNonOrphanedCaretOrRange())
        return;

    VisiblePosition caret(selection.visibleStart());
    // FIXME: If the node is hidden, we should still be able to insert text.
    // For now, we return to avoid a crash.  https://bugs.webkit.org/show_bug.cgi?id=40342
    if (caret.isNull())
        return;

    Position pos(caret.deepEquivalent());

    pos = positionAvoidingSpecialElementBoundary(pos);

    pos = positionOutsideTabSpan(pos);

    RefPtrWillBeRawPtr<Node> nodeToInsert = nullptr;
    if (shouldUseBreakElement(pos))
        nodeToInsert = HTMLBRElement::create(document());
    else
        nodeToInsert = document().createTextNode("\n");

    // FIXME: Need to merge text nodes when inserting just after or before text.

    if (isEndOfParagraph(caret) && !lineBreakExistsAtVisiblePosition(caret)) {
        bool needExtraLineBreak = !isHTMLHRElement(*pos.anchorNode()) && !isHTMLTableElement(*pos.anchorNode());

        insertNodeAt(nodeToInsert.get(), pos);

        if (needExtraLineBreak)
            insertNodeBefore(nodeToInsert->cloneNode(false), nodeToInsert);

        VisiblePosition endingPosition = createVisiblePosition(positionBeforeNode(nodeToInsert.get()));
        setEndingSelection(VisibleSelection(endingPosition, endingSelection().isDirectional()));
    } else if (pos.computeEditingOffset() <= caretMinOffset(pos.anchorNode())) {
        insertNodeAt(nodeToInsert.get(), pos);

        // Insert an extra br or '\n' if the just inserted one collapsed.
        if (!isStartOfParagraph(createVisiblePosition(positionBeforeNode(nodeToInsert.get()))))
            insertNodeBefore(nodeToInsert->cloneNode(false).get(), nodeToInsert.get());

        setEndingSelection(VisibleSelection(positionInParentAfterNode(*nodeToInsert), TextAffinity::Downstream, endingSelection().isDirectional()));
    // If we're inserting after all of the rendered text in a text node, or into a non-text node,
    // a simple insertion is sufficient.
    } else if (!pos.anchorNode()->isTextNode() || pos.computeOffsetInContainerNode() >= caretMaxOffset(pos.anchorNode())) {
        insertNodeAt(nodeToInsert.get(), pos);
        setEndingSelection(VisibleSelection(positionInParentAfterNode(*nodeToInsert), TextAffinity::Downstream, endingSelection().isDirectional()));
    } else if (pos.anchorNode()->isTextNode()) {
        // Split a text node
        Text* textNode = toText(pos.anchorNode());
        splitTextNode(textNode, pos.computeOffsetInContainerNode());
        insertNodeBefore(nodeToInsert, textNode);
        Position endingPosition = firstPositionInNode(textNode);

        // Handle whitespace that occurs after the split
        document().updateLayoutIgnorePendingStylesheets();
        // TODO(yosin) |isRenderedCharacter()| should be removed, and we should
        // use |VisiblePosition::characterAfter()|.
        if (!isRenderedCharacter(endingPosition)) {
            Position positionBeforeTextNode(positionInParentBeforeNode(*textNode));
            // Clear out all whitespace and insert one non-breaking space
            deleteInsignificantTextDownstream(endingPosition);
            ASSERT(!textNode->layoutObject() || textNode->layoutObject()->style()->collapseWhiteSpace());
            // Deleting insignificant whitespace will remove textNode if it contains nothing but insignificant whitespace.
            if (textNode->inDocument()) {
                insertTextIntoNode(textNode, 0, nonBreakingSpaceString());
            } else {
                RefPtrWillBeRawPtr<Text> nbspNode = document().createTextNode(nonBreakingSpaceString());
                insertNodeAt(nbspNode.get(), positionBeforeTextNode);
                endingPosition = firstPositionInNode(nbspNode.get());
            }
        }

        setEndingSelection(VisibleSelection(endingPosition, TextAffinity::Downstream, endingSelection().isDirectional()));
    }

    // Handle the case where there is a typing style.

    RefPtrWillBeRawPtr<EditingStyle> typingStyle = document().frame()->selection().typingStyle();

    if (typingStyle && !typingStyle->isEmpty()) {
        // Apply the typing style to the inserted line break, so that if the selection
        // leaves and then comes back, new input will have the right style.
        // FIXME: We shouldn't always apply the typing style to the line break here,
        // see <rdar://problem/5794462>.
        applyStyle(typingStyle.get(), firstPositionInOrBeforeNode(nodeToInsert.get()), lastPositionInOrAfterNode(nodeToInsert.get()));
        // Even though this applyStyle operates on a Range, it still sets an endingSelection().
        // It tries to set a VisibleSelection around the content it operated on. So, that VisibleSelection
        // will either (a) select the line break we inserted, or it will (b) be a caret just
        // before the line break (if the line break is at the end of a block it isn't selectable).
        // So, this next call sets the endingSelection() to a caret just after the line break
        // that we inserted, or just before it if it's at the end of a block.
        setEndingSelection(endingSelection().visibleEnd());
    }

    rebalanceWhitespace();
}
void InsertLineBreakCommand::doApply(EditingState* editingState) {
  deleteSelection(editingState);
  if (editingState->isAborted())
    return;

  document().updateStyleAndLayoutIgnorePendingStylesheets();

  VisibleSelection selection = endingSelection();
  if (!selection.isNonOrphanedCaretOrRange())
    return;

  // TODO(xiaochengh): Stop storing VisiblePositions through mutations.
  VisiblePosition caret(selection.visibleStart());
  // FIXME: If the node is hidden, we should still be able to insert text. For
  // now, we return to avoid a crash.
  // https://bugs.webkit.org/show_bug.cgi?id=40342
  if (caret.isNull())
    return;

  Position pos(caret.deepEquivalent());

  pos = positionAvoidingSpecialElementBoundary(pos, editingState);
  if (editingState->isAborted())
    return;

  pos = positionOutsideTabSpan(pos);

  Node* nodeToInsert = nullptr;
  if (shouldUseBreakElement(pos))
    nodeToInsert = HTMLBRElement::create(document());
  else
    nodeToInsert = document().createTextNode("\n");

  document().updateStyleAndLayoutIgnorePendingStylesheets();

  // FIXME: Need to merge text nodes when inserting just after or before text.

  if (isEndOfParagraph(createVisiblePosition(caret.toPositionWithAffinity())) &&
      !lineBreakExistsAtVisiblePosition(caret)) {
    bool needExtraLineBreak = !isHTMLHRElement(*pos.anchorNode()) &&
                              !isHTMLTableElement(*pos.anchorNode());

    insertNodeAt(nodeToInsert, pos, editingState);
    if (editingState->isAborted())
      return;

    if (needExtraLineBreak) {
      Node* extraNode;
      // TODO(tkent): Can we remove HTMLTextFormControlElement dependency?
      if (HTMLTextFormControlElement* textControl =
              enclosingTextFormControl(nodeToInsert)) {
        extraNode = textControl->createPlaceholderBreakElement();
        // The placeholder BR should be the last child.  There might be
        // empty Text nodes at |pos|.
        appendNode(extraNode, nodeToInsert->parentNode(), editingState);
      } else {
        extraNode = nodeToInsert->cloneNode(false);
        insertNodeAfter(extraNode, nodeToInsert, editingState);
      }
      if (editingState->isAborted())
        return;
      nodeToInsert = extraNode;
    }

    setEndingSelection(SelectionInDOMTree::Builder()
                           .collapse(Position::beforeNode(nodeToInsert))
                           .setIsDirectional(endingSelection().isDirectional())
                           .build());
  } else if (pos.computeEditingOffset() <= caretMinOffset(pos.anchorNode())) {
    insertNodeAt(nodeToInsert, pos, editingState);
    if (editingState->isAborted())
      return;
    document().updateStyleAndLayoutIgnorePendingStylesheets();

    // Insert an extra br or '\n' if the just inserted one collapsed.
    if (!isStartOfParagraph(VisiblePosition::beforeNode(nodeToInsert))) {
      insertNodeBefore(nodeToInsert->cloneNode(false), nodeToInsert,
                       editingState);
      if (editingState->isAborted())
        return;
    }

    setEndingSelection(SelectionInDOMTree::Builder()
                           .collapse(Position::inParentAfterNode(*nodeToInsert))
                           .setIsDirectional(endingSelection().isDirectional())
                           .build());
    // If we're inserting after all of the rendered text in a text node, or into
    // a non-text node, a simple insertion is sufficient.
  } else if (!pos.anchorNode()->isTextNode() ||
             pos.computeOffsetInContainerNode() >=
                 caretMaxOffset(pos.anchorNode())) {
    insertNodeAt(nodeToInsert, pos, editingState);
    if (editingState->isAborted())
      return;
    setEndingSelection(SelectionInDOMTree::Builder()
                           .collapse(Position::inParentAfterNode(*nodeToInsert))
                           .setIsDirectional(endingSelection().isDirectional())
                           .build());
  } else if (pos.anchorNode()->isTextNode()) {
    // Split a text node
    Text* textNode = toText(pos.anchorNode());
    splitTextNode(textNode, pos.computeOffsetInContainerNode());
    insertNodeBefore(nodeToInsert, textNode, editingState);
    if (editingState->isAborted())
      return;
    Position endingPosition = Position::firstPositionInNode(textNode);

    // Handle whitespace that occurs after the split
    document().updateStyleAndLayoutIgnorePendingStylesheets();
    // TODO(yosin) |isRenderedCharacter()| should be removed, and we should
    // use |VisiblePosition::characterAfter()|.
    if (!isRenderedCharacter(endingPosition)) {
      Position positionBeforeTextNode(Position::inParentBeforeNode(*textNode));
      // Clear out all whitespace and insert one non-breaking space
      deleteInsignificantTextDownstream(endingPosition);
      DCHECK(!textNode->layoutObject() ||
             textNode->layoutObject()->style()->collapseWhiteSpace());
      // Deleting insignificant whitespace will remove textNode if it contains
      // nothing but insignificant whitespace.
      if (textNode->isConnected()) {
        insertTextIntoNode(textNode, 0, nonBreakingSpaceString());
      } else {
        Text* nbspNode = document().createTextNode(nonBreakingSpaceString());
        insertNodeAt(nbspNode, positionBeforeTextNode, editingState);
        if (editingState->isAborted())
          return;
        endingPosition = Position::firstPositionInNode(nbspNode);
      }
    }

    setEndingSelection(SelectionInDOMTree::Builder()
                           .collapse(endingPosition)
                           .setIsDirectional(endingSelection().isDirectional())
                           .build());
  }

  // Handle the case where there is a typing style.

  EditingStyle* typingStyle = document().frame()->selection().typingStyle();

  if (typingStyle && !typingStyle->isEmpty()) {
    // Apply the typing style to the inserted line break, so that if the
    // selection leaves and then comes back, new input will have the right
    // style.
    // FIXME: We shouldn't always apply the typing style to the line break here,
    // see <rdar://problem/5794462>.
    applyStyle(typingStyle, firstPositionInOrBeforeNode(nodeToInsert),
               lastPositionInOrAfterNode(nodeToInsert), editingState);
    if (editingState->isAborted())
      return;
    // Even though this applyStyle operates on a Range, it still sets an
    // endingSelection(). It tries to set a VisibleSelection around the content
    // it operated on. So, that VisibleSelection will either
    //   (a) select the line break we inserted, or it will
    //   (b) be a caret just before the line break (if the line break is at the
    //       end of a block it isn't selectable).
    // So, this next call sets the endingSelection() to a caret just after the
    // line break that we inserted, or just before it if it's at the end of a
    // block.
    setEndingSelection(SelectionInDOMTree::Builder()
                           .collapse(endingSelection().end())
                           .build());
  }

  rebalanceWhitespace();
}
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 != visibleStart && isStartOfParagraph(visibleEnd, CanSkipOverEditingBoundary)) {
        setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(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 = endingSelection().firstRange();
        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) != startOfLastParagraph) {
            forceListCreation = !selectionHasListOfType(selection, listTag);

            VisiblePosition startOfCurrentParagraph = startOfSelection;
            while (startOfCurrentParagraph.isNotNull() && !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.
                doApplyForSingleParagraph(forceListCreation, listTag, *currentSelection);
                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() || !endOfSelection.rootEditableElement())
                        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(endingSelection().firstRange());
    doApplyForSingleParagraph(false, listTag, *endingSelection().firstRange());
}
Exemple #16
0
static int32_t insertionPointFromCurrentSelection(const VisibleSelection& currentSelection)
{
    VisiblePosition selectionStart = currentSelection.visibleStart();
    VisiblePosition paragraphStart = startOfParagraph(selectionStart);
    return TextIterator::rangeLength(makeRange(paragraphStart, selectionStart).get());
}
void InsertListCommand::doApply(EditingState* editingState) {
    // Only entry points are Editor::Command::execute and
    // IndentOutdentCommand::outdentParagraph, both of which ensure clean layout.
    DCHECK(!document().needsLayoutTreeUpdate());

    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(createVisibleSelection(
                               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());
        DCHECK(selection.isRange());

        VisiblePosition visibleStartOfSelection = selection.visibleStart();
        VisiblePosition visibleEndOfSelection = selection.visibleEnd();
        PositionWithAffinity startOfSelection =
            visibleStartOfSelection.toPositionWithAffinity();
        PositionWithAffinity endOfSelection =
            visibleEndOfSelection.toPositionWithAffinity();
        Position startOfLastParagraph =
            startOfParagraph(visibleEndOfSelection, CanSkipOverEditingBoundary)
            .deepEquivalent();

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

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

            VisiblePosition startOfCurrentParagraph = visibleStartOfSelection;
            while (inSameTreeAndOrdered(startOfCurrentParagraph.deepEquivalent(),
                                        startOfLastParagraph) &&
                    !inSameParagraph(startOfCurrentParagraph,
                                     createVisiblePosition(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 visibleEndOfSelection
                // and use it as the end of the new selection.
                if (!startOfLastParagraph.isConnected())
                    return;
                setEndingSelection(startOfCurrentParagraph);

                // Save and restore visibleEndOfSelection and startOfLastParagraph when
                // necessary since moveParagraph and movePragraphWithClones can remove
                // nodes.
                bool singleParagraphResult = doApplyForSingleParagraph(
                                                 forceListCreation, listTag, *currentSelection, editingState);
                if (editingState->isAborted())
                    return;
                if (!singleParagraphResult)
                    break;

                document().updateStyleAndLayoutIgnorePendingStylesheets();

                // Make |visibleEndOfSelection| valid again.
                if (!endOfSelection.isConnected() ||
                        !startOfLastParagraph.isConnected()) {
                    visibleEndOfSelection = visiblePositionForIndex(
                                                indexForEndOfSelection, scopeForEndOfSelection);
                    endOfSelection = visibleEndOfSelection.toPositionWithAffinity();
                    // If visibleEndOfSelection 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.
                    DCHECK(visibleEndOfSelection.isNotNull());
                    if (visibleEndOfSelection.isNull() ||
                            !rootEditableElementOf(visibleEndOfSelection))
                        return;
                    startOfLastParagraph = startOfParagraph(visibleEndOfSelection,
                                                            CanSkipOverEditingBoundary)
                                           .deepEquivalent();
                } else {
                    visibleEndOfSelection = createVisiblePosition(endOfSelection);
                }

                startOfCurrentParagraph =
                    startOfNextParagraph(endingSelection().visibleStart());
            }
            setEndingSelection(visibleEndOfSelection);
        }
        doApplyForSingleParagraph(forceListCreation, listTag, *currentSelection,
                                  editingState);
        if (editingState->isAborted())
            return;

        document().updateStyleAndLayoutIgnorePendingStylesheets();

        // Fetch the end of the selection, for the reason mentioned above.
        if (!endOfSelection.isConnected()) {
            visibleEndOfSelection = visiblePositionForIndex(indexForEndOfSelection,
                                    scopeForEndOfSelection);
            if (visibleEndOfSelection.isNull())
                return;
        } else {
            visibleEndOfSelection = createVisiblePosition(endOfSelection);
        }

        if (!startOfSelection.isConnected()) {
            visibleStartOfSelection = visiblePositionForIndex(
                                          indexForStartOfSelection, scopeForStartOfSelection);
            if (visibleStartOfSelection.isNull())
                return;
        } else {
            visibleStartOfSelection = createVisiblePosition(startOfSelection);
        }

        setEndingSelection(
            createVisibleSelection(visibleStartOfSelection, visibleEndOfSelection,
                                   endingSelection().isDirectional()));
        return;
    }

    DCHECK(firstRangeOf(endingSelection()));
    doApplyForSingleParagraph(false, listTag, *firstRangeOf(endingSelection()),
                              editingState);
}