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