Пример #1
0
// Abs x/y position of the caret ignoring transforms.
// TODO(yosin) navigation with transforms should be smarter.
static LayoutUnit lineDirectionPointForBlockDirectionNavigationOf(
    const VisiblePosition& visiblePosition) {
  if (visiblePosition.isNull())
    return LayoutUnit();

  LayoutObject* layoutObject;
  LayoutRect localRect = localCaretRectOfPosition(
      visiblePosition.toPositionWithAffinity(), layoutObject);
  if (localRect.isEmpty() || !layoutObject)
    return LayoutUnit();

  // This ignores transforms on purpose, for now. Vertical navigation is done
  // without consulting transforms, so that 'up' in transformed text is 'up'
  // relative to the text, not absolute 'up'.
  FloatPoint caretPoint =
      layoutObject->localToAbsolute(FloatPoint(localRect.location()));
  LayoutObject* containingBlock = layoutObject->containingBlock();
  if (!containingBlock) {
    // Just use ourselves to determine the writing mode if we have no containing
    // block.
    containingBlock = layoutObject;
  }
  return LayoutUnit(containingBlock->isHorizontalWritingMode()
                        ? caretPoint.x()
                        : caretPoint.y());
}
Пример #2
0
// TODO(xiaochengh): Stop storing VisiblePositions through mutations.
void InsertListCommand::moveParagraphOverPositionIntoEmptyListItem(
    const VisiblePosition& pos,
    HTMLLIElement* listItemElement,
    EditingState* editingState) {
    DCHECK(!listItemElement->hasChildren());
    HTMLBRElement* placeholder = HTMLBRElement::create(document());
    appendNode(placeholder, listItemElement, editingState);
    if (editingState->isAborted())
        return;
    // Inserting list element and list item list may change start of pargraph
    // to move. We calculate start of paragraph again.
    document().updateStyleAndLayoutIgnorePendingStylesheets();
    const VisiblePosition& validPos =
        createVisiblePosition(pos.toPositionWithAffinity());
    const VisiblePosition& start =
        startOfParagraph(validPos, CanSkipOverEditingBoundary);
    const VisiblePosition& end =
        endOfParagraph(validPos, CanSkipOverEditingBoundary);
    moveParagraph(start, end, VisiblePosition::beforeNode(placeholder),
                  editingState, PreserveSelection);
}
Пример #3
0
void InsertListCommand::unlistifyParagraph(const VisiblePosition& originalStart,
        HTMLElement* listElement,
        Node* listChildNode,
        EditingState* editingState) {
    // Since, unlistify paragraph inserts nodes into parent and removes node
    // from parent, if parent of |listElement| should be editable.
    DCHECK(hasEditableStyle(*listElement->parentNode()));
    Node* nextListChild;
    Node* previousListChild;
    VisiblePosition start;
    VisiblePosition end;
    DCHECK(listChildNode);
    if (isHTMLLIElement(*listChildNode)) {
        start = VisiblePosition::firstPositionInNode(listChildNode);
        end = VisiblePosition::lastPositionInNode(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(originalStart, CanSkipOverEditingBoundary);
        end = endOfParagraph(start, CanSkipOverEditingBoundary);
        nextListChild = enclosingListChild(
                            nextPositionOf(end).deepEquivalent().anchorNode(), listElement);
        DCHECK_NE(nextListChild, listChildNode);
        previousListChild = enclosingListChild(
                                previousPositionOf(start).deepEquivalent().anchorNode(), listElement);
        DCHECK_NE(previousListChild, listChildNode);
    }

    // Helpers for making |start| and |end| valid again after DOM changes.
    PositionWithAffinity startPosition = start.toPositionWithAffinity();
    PositionWithAffinity endPosition = end.toPositionWithAffinity();

    // When removing a list, we must always create a placeholder to act as a point
    // of insertion for the list content being removed.
    HTMLBRElement* placeholder = HTMLBRElement::create(document());
    HTMLElement* elementToInsert = 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(listElement)) {
        elementToInsert = HTMLLIElement::create(document());
        appendNode(placeholder, elementToInsert, editingState);
        if (editingState->isAborted())
            return;
    }

    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(listElement, splitTreeToNode(nextListChild, listElement));
        insertNodeBefore(elementToInsert, listElement, editingState);
    } else if (nextListChild || listChildNode->parentNode() != listElement) {
        // 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() != listElement)
            splitElement(listElement, splitTreeToNode(listChildNode, listElement));
        insertNodeBefore(elementToInsert, listElement, editingState);
    } else {
        insertNodeAfter(elementToInsert, listElement, editingState);
    }
    if (editingState->isAborted())
        return;

    document().updateStyleAndLayoutIgnorePendingStylesheets();

    // Make |start| and |end| valid again.
    start = createVisiblePosition(startPosition);
    end = createVisiblePosition(endPosition);

    VisiblePosition insertionPoint = VisiblePosition::beforeNode(placeholder);
    moveParagraphs(start, end, insertionPoint, editingState, PreserveSelection,
                   PreserveStyle, listChildNode);
}
Пример #4
0
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);
}
Пример #5
0
void CaretBase::updateCaretRect(const VisiblePosition& caretPosition) {
    updateCaretRect(caretPosition.toPositionWithAffinity());
}
VisiblePosition VisiblePosition::honorEditingBoundaryAtOrBefore(const VisiblePosition &pos) const
{
    return VisiblePosition(honorEditingBoundaryAtOrBeforeOf(pos.toPositionWithAffinity(), deepEquivalent()));
}
Пример #7
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;
}
Пример #8
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;
}
Пример #9
0
void IndentOutdentCommand::outdentParagraph(EditingState* editingState) {
  VisiblePosition visibleStartOfParagraph =
      startOfParagraph(endingSelection().visibleStart());
  VisiblePosition visibleEndOfParagraph =
      endOfParagraph(visibleStartOfParagraph);

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

  // Use InsertListCommand to remove the selection from the list
  if (isHTMLOListElement(*enclosingElement)) {
    applyCommandToComposite(
        InsertListCommand::create(document(), InsertListCommand::OrderedList),
        editingState);
    return;
  }
  if (isHTMLUListElement(*enclosingElement)) {
    applyCommandToComposite(
        InsertListCommand::create(document(), InsertListCommand::UnorderedList),
        editingState);
    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.deepEquivalent() ==
          startOfEnclosingBlock.deepEquivalent() &&
      visibleEndOfParagraph.deepEquivalent() ==
          endOfEnclosingBlock.deepEquivalent()) {
    // The blockquote doesn't contain anything outside the paragraph, so it can
    // be totally removed.
    Node* splitPoint = enclosingElement->nextSibling();
    removeNodePreservingChildren(enclosingElement, editingState);
    if (editingState->isAborted())
      return;
    // 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()) {
        // We can't outdent if there is no place to go!
        if (splitPointParent->hasTagName(blockquoteTag) &&
            !splitPoint->hasTagName(blockquoteTag) &&
            hasEditableStyle(*splitPointParent->parentNode()))
          splitElement(splitPointParent, splitPoint);
      }
    }

    document().updateStyleAndLayoutIgnorePendingStylesheets();
    visibleStartOfParagraph =
        createVisiblePosition(visibleStartOfParagraph.deepEquivalent());
    if (visibleStartOfParagraph.isNotNull() &&
        !isStartOfParagraph(visibleStartOfParagraph)) {
      insertNodeAt(HTMLBRElement::create(document()),
                   visibleStartOfParagraph.deepEquivalent(), editingState);
      if (editingState->isAborted())
        return;
    }

    document().updateStyleAndLayoutIgnorePendingStylesheets();
    visibleEndOfParagraph =
        createVisiblePosition(visibleEndOfParagraph.deepEquivalent());
    if (visibleEndOfParagraph.isNotNull() &&
        !isEndOfParagraph(visibleEndOfParagraph))
      insertNodeAt(HTMLBRElement::create(document()),
                   visibleEndOfParagraph.deepEquivalent(), editingState);
    return;
  }

  Node* splitBlockquoteNode = enclosingElement;
  if (Element* enclosingBlockFlow = enclosingBlock(
          visibleStartOfParagraph.deepEquivalent().anchorNode())) {
    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().anchorNode());
    }

    document().updateStyleAndLayoutIgnorePendingStylesheets();

    // Re-canonicalize visible{Start,End}OfParagraph, make them valid again
    // after DOM change.
    // TODO(xiaochengh): We should not store a VisiblePosition and later inspect
    // its properties when it is already invalidated.
    visibleStartOfParagraph =
        createVisiblePosition(visibleStartOfParagraph.toPositionWithAffinity());
    visibleEndOfParagraph =
        createVisiblePosition(visibleEndOfParagraph.toPositionWithAffinity());
  }

  // TODO(xiaochengh): We should not store a VisiblePosition and later inspect
  // its properties when it is already invalidated.
  VisiblePosition startOfParagraphToMove =
      startOfParagraph(visibleStartOfParagraph);
  VisiblePosition endOfParagraphToMove = endOfParagraph(visibleEndOfParagraph);
  if (startOfParagraphToMove.isNull() || endOfParagraphToMove.isNull())
    return;
  HTMLBRElement* placeholder = HTMLBRElement::create(document());
  insertNodeBefore(placeholder, splitBlockquoteNode, editingState);
  if (editingState->isAborted())
    return;

  document().updateStyleAndLayoutIgnorePendingStylesheets();
  startOfParagraphToMove =
      createVisiblePosition(startOfParagraphToMove.toPositionWithAffinity());
  endOfParagraphToMove =
      createVisiblePosition(endOfParagraphToMove.toPositionWithAffinity());
  moveParagraph(startOfParagraphToMove, endOfParagraphToMove,
                VisiblePosition::beforeNode(placeholder), editingState,
                PreserveSelection);
}