VisibleSelection SurroundingTextTest::select(int start, int end) { Element* element = document().getElementById("selection"); VisibleSelection selection; selection.setBase(Position(toText(element->firstChild()), start)); selection.setExtent(Position(toText(element->firstChild()), end)); return selection; }
int Frame::checkOverflowScroll(OverflowScrollAction action) { Position extent = selection().selection().extent(); if (extent.isNull()) return OverflowScrollNone; RenderObject* renderer = extent.deprecatedNode()->renderer(); if (!renderer) return OverflowScrollNone; FrameView* view = this->view(); if (!view) return OverflowScrollNone; RenderBlock* containingBlock = renderer->containingBlock(); if (!containingBlock || !containingBlock->hasOverflowClip()) return OverflowScrollNone; RenderLayer* layer = containingBlock->layer(); ASSERT(layer); IntRect visibleRect = IntRect(view->scrollX(), view->scrollY(), view->visibleWidth(), view->visibleHeight()); IntPoint position = m_overflowAutoScrollPos; if (visibleRect.contains(position.x(), position.y())) return OverflowScrollNone; int scrollType = 0; int deltaX = 0; int deltaY = 0; IntPoint selectionPosition; // This constant will make the selection draw a little bit beyond the edge of the visible area. // This prevents a visual glitch, in that you can fail to select a portion of a character that // is being rendered right at the edge of the visible rectangle. // FIXME: This probably needs improvement, and may need to take the font size into account. static const int scrollBoundsAdjustment = 3; // FIXME: Make a small buffer at the end of a visible rectangle so that autoscrolling works // even if the visible extends to the limits of the screen. if (position.x() < visibleRect.x()) { scrollType |= OverflowScrollLeft; if (action == PerformOverflowScroll) { deltaX -= static_cast<int>(m_overflowAutoScrollDelta); selectionPosition.setX(view->scrollX() - scrollBoundsAdjustment); } } else if (position.x() > visibleRect.maxX()) { scrollType |= OverflowScrollRight; if (action == PerformOverflowScroll) { deltaX += static_cast<int>(m_overflowAutoScrollDelta); selectionPosition.setX(view->scrollX() + view->visibleWidth() + scrollBoundsAdjustment); } } if (position.y() < visibleRect.y()) { scrollType |= OverflowScrollUp; if (action == PerformOverflowScroll) { deltaY -= static_cast<int>(m_overflowAutoScrollDelta); selectionPosition.setY(view->scrollY() - scrollBoundsAdjustment); } } else if (position.y() > visibleRect.maxY()) { scrollType |= OverflowScrollDown; if (action == PerformOverflowScroll) { deltaY += static_cast<int>(m_overflowAutoScrollDelta); selectionPosition.setY(view->scrollY() + view->visibleHeight() + scrollBoundsAdjustment); } } Ref<Frame> protectedThis(*this); if (action == PerformOverflowScroll && (deltaX || deltaY)) { layer->scrollToOffset(layer->scrollOffset() + IntSize(deltaX, deltaY)); // Handle making selection. VisiblePosition visiblePosition(renderer->positionForPoint(selectionPosition, nullptr)); if (visiblePosition.isNotNull()) { VisibleSelection visibleSelection = selection().selection(); visibleSelection.setExtent(visiblePosition); if (selection().granularity() != CharacterGranularity) visibleSelection.expandUsingGranularity(selection().granularity()); if (selection().shouldChangeSelection(visibleSelection)) selection().setSelection(visibleSelection); } m_overflowAutoScrollDelta *= 1.02f; // Accelerate the scroll } return scrollType; }
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::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; }
bool SelectionEditor::modify(EAlteration alter, SelectionDirection direction, TextGranularity granularity, EUserTriggered userTriggered) { if (userTriggered == UserTriggered) { OwnPtrWillBeRawPtr<FrameSelection> trialFrameSelection = FrameSelection::create(); trialFrameSelection->setSelection(m_selection); trialFrameSelection->modify(alter, direction, granularity, NotUserTriggered); if (trialFrameSelection->selection().isRange() && m_selection.isCaret() && !dispatchSelectStart()) return false; } 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_frameSelection->moveTo(position, userTriggered); 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_frameSelection->setExtent(position, userTriggered); } else { TextDirection textDirection = directionOfEnclosingBlock(); if (direction == DirectionForward || (textDirection == LTR && direction == DirectionRight) || (textDirection == RTL && direction == DirectionLeft)) m_frameSelection->setEnd(position, userTriggered); else m_frameSelection->setStart(position, userTriggered); } break; } if (granularity == LineGranularity || granularity == ParagraphGranularity) m_xPosForVerticalArrowNavigation = x; return true; }