void CreateLinkCommand::doApply(EditingState* editingState) {
  if (endingSelection().isNone())
    return;

  HTMLAnchorElement* anchorElement = HTMLAnchorElement::create(document());
  anchorElement->setHref(AtomicString(m_url));

  if (endingSelection().isRange()) {
    applyStyledElement(anchorElement, editingState);
    if (editingState->isAborted())
      return;
  } else {
    insertNodeAt(anchorElement, endingSelection().start(), editingState);
    if (editingState->isAborted())
      return;
    Text* textNode = Text::create(document(), m_url);
    appendNode(textNode, anchorElement, editingState);
    if (editingState->isAborted())
      return;
    document().updateStyleAndLayoutIgnorePendingStylesheets();
    setEndingSelection(createVisibleSelection(
        Position::inParentBeforeNode(*anchorElement),
        Position::inParentAfterNode(*anchorElement), TextAffinity::Downstream,
        endingSelection().isDirectional()));
  }
}
Beispiel #2
0
// TODO(yosin): We will make |SelectionInDOMTree| version of
// |setEndingSelection()| as primary function instead of wrapper, once
// |EditCommand| holds other than |VisibleSelection|.
void EditCommand::setEndingSelection(const SelectionInDOMTree& selection) {
  // TODO(editing-dev): The use of
  // updateStyleAndLayoutIgnorePendingStylesheets
  // needs to be audited.  See http://crbug.com/590369 for more details.
  document().updateStyleAndLayoutIgnorePendingStylesheets();
  setEndingVisibleSelection(createVisibleSelection(selection));
}
VisibleSelection CharacterGranularityStrategy::updateExtent(
    const IntPoint& extentPoint,
    LocalFrame* frame) {
  const VisiblePosition& extentPosition =
      visiblePositionForContentsPoint(extentPoint, frame);
  const VisibleSelection& selection = frame->selection().selection();
  if (selection.visibleBase().deepEquivalent() ==
      extentPosition.deepEquivalent())
    return selection;
  return createVisibleSelection(selection.visibleBase(), extentPosition);
}
Beispiel #4
0
// TODO(yosin): We should make |adjustSelectionInDOMTree()| to return
// |VisibleSelection| once |VisibleSelection| constructor doesn't call
// |validate()|.
void SelectionAdjuster::adjustSelectionInDOMTree(
    VisibleSelection* selection,
    const VisibleSelectionInFlatTree& selectionInFlatTree) {
  if (selectionInFlatTree.isNone()) {
    *selection = VisibleSelection();
    return;
  }

  const Position& base = toPositionInDOMTree(selectionInFlatTree.base());
  const Position& extent = toPositionInDOMTree(selectionInFlatTree.extent());

  if (isCrossingShadowBoundaries(selectionInFlatTree)) {
    DCHECK(base.document());

    // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
    // needs to be audited.  See http://crbug.com/590369 for more details.
    // This layout update call cannot be hoisted out of the |if|, otherwise it's
    // going to cause performance regression (http://crbug.com/652301).
    // TODO(yosin): Implement and apply lazy visible selection validation so
    // that we don't need to update layout here.
    base.document()->updateStyleAndLayoutIgnorePendingStylesheets();

    *selection = createVisibleSelection(
        SelectionInDOMTree::Builder().setBaseAndExtent(base, extent).build());
    return;
  }

  const Position& position1 = toPositionInDOMTree(selectionInFlatTree.start());
  const Position& position2 = toPositionInDOMTree(selectionInFlatTree.end());
  selection->m_base = base;
  selection->m_extent = extent;
  selection->m_affinity = selectionInFlatTree.m_affinity;
  selection->m_isDirectional = selectionInFlatTree.m_isDirectional;
  selection->m_granularity = selectionInFlatTree.m_granularity;
  selection->m_hasTrailingWhitespace =
      selectionInFlatTree.m_hasTrailingWhitespace;
  selection->m_baseIsFirst = base.isNull() || base.compareTo(extent) <= 0;
  if (position1.compareTo(position2) <= 0) {
    selection->m_start = position1;
    selection->m_end = position2;
  } else {
    selection->m_start = position2;
    selection->m_end = position1;
  }
  selection->updateSelectionType();
}
Beispiel #5
0
void DOMSelection::setBaseAndExtent(Node* baseNode,
                                    int baseOffset,
                                    Node* extentNode,
                                    int extentOffset,
                                    ExceptionState& exceptionState) {
    if (!isAvailable())
        return;

    if (baseOffset < 0) {
        exceptionState.throwDOMException(
            IndexSizeError,
            String::number(baseOffset) + " is not a valid base offset.");
        return;
    }

    if (extentOffset < 0) {
        exceptionState.throwDOMException(
            IndexSizeError,
            String::number(extentOffset) + " is not a valid extent offset.");
        return;
    }

    if (!baseNode || !extentNode)
        UseCounter::count(frame(), UseCounter::SelectionSetBaseAndExtentNull);

    if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
        return;

    Position base = createPosition(baseNode, baseOffset);
    Position extent = createPosition(extentNode, extentOffset);
    const bool selectionHasDirection = true;

    // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
    // needs to be audited.  See http://crbug.com/590369 for more details.
    // In the long term, we should change FrameSelection::setSelection to take a
    // parameter that does not require clean layout, so that modifying selection
    // no longer performs synchronous layout by itself.
    frame()->document()->updateStyleAndLayoutIgnorePendingStylesheets();

    frame()->selection().setSelection(createVisibleSelection(
                                          base, extent, SelDefaultAffinity, selectionHasDirection));
}
Beispiel #6
0
void DOMSelection::extend(Node* node,
                          int offset,
                          ExceptionState& exceptionState) {
    DCHECK(node);

    if (!isAvailable())
        return;

    if (offset < 0) {
        exceptionState.throwDOMException(
            IndexSizeError, String::number(offset) + " is not a valid offset.");
        return;
    }
    if (static_cast<unsigned>(offset) > node->lengthOfContents()) {
        exceptionState.throwDOMException(
            IndexSizeError,
            String::number(offset) + " is larger than the given node's length.");
        return;
    }

    if (!isValidForPosition(node))
        return;

    const Position& base = frame()->selection().base();
    const Position& extent = createPosition(node, offset);
    const bool selectionHasDirection = true;

    // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
    // needs to be audited.  See http://crbug.com/590369 for more details.
    // In the long term, we should change FrameSelection::setSelection to take a
    // parameter that does not require clean layout, so that modifying selection
    // no longer performs synchronous layout by itself.
    frame()->document()->updateStyleAndLayoutIgnorePendingStylesheets();

    const VisibleSelection newSelection = createVisibleSelection(
            base, extent, TextAffinity::Downstream, selectionHasDirection);
    frame()->selection().setSelection(newSelection);
}
Beispiel #7
0
void DOMSelection::collapseToStart(ExceptionState& exceptionState) {
    if (!isAvailable())
        return;

    const VisibleSelection& selection = frame()->selection().selection();

    if (selection.isNone()) {
        exceptionState.throwDOMException(InvalidStateError,
                                         "there is no selection.");
        return;
    }

    // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
    // needs to be audited.  See http://crbug.com/590369 for more details.
    // In the long term, we should change FrameSelection::setSelection to take a
    // parameter that does not require clean layout, so that modifying selection
    // no longer performs synchronous layout by itself.
    frame()->document()->updateStyleAndLayoutIgnorePendingStylesheets();

    SelectionInDOMTree::Builder builder;
    builder.collapse(selection.start());
    frame()->selection().setSelection(createVisibleSelection(builder.build()));
}
VisibleSelection DirectionGranularityStrategy::updateExtent(
    const IntPoint& extentPoint,
    LocalFrame* frame) {
  const VisibleSelection& selection = frame->selection().selection();

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

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

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

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

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

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

  const VisiblePosition base = selection.visibleBase();

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

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

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

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

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

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

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

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

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

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

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

  m_diffExtentPointFromExtentPosition =
      extentPoint + IntSize(m_offset, 0) - positionLocation(newSelectionExtent);
  VisibleSelection newSelection = selection;
  newSelection.setExtent(newSelectionExtent);
  return newSelection;
}
Beispiel #9
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;
}
Beispiel #10
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;
}