| Index: Source/core/editing/SelectionController.cpp
|
| diff --git a/Source/core/editing/SelectionController.cpp b/Source/core/editing/SelectionController.cpp
|
| index c4f94124af1ab5a8c8b44d61307ea2a2cbacf3c9..6544f3ce7d923d24655242e3ee59a7adbd8fbac6 100644
|
| --- a/Source/core/editing/SelectionController.cpp
|
| +++ b/Source/core/editing/SelectionController.cpp
|
| @@ -64,14 +64,16 @@ DEFINE_TRACE(SelectionController)
|
| visitor->trace(m_frame);
|
| }
|
|
|
| -static void setSelectionIfNeeded(FrameSelection& selection, const VisibleSelection& newSelection)
|
| +namespace {
|
| +
|
| +void setSelectionIfNeeded(FrameSelection& selection, const VisibleSelection& newSelection)
|
| {
|
| if (VisibleSelection::InDOMTree::equalSelections(selection.selection(), newSelection))
|
| return;
|
| selection.setSelection(newSelection);
|
| }
|
|
|
| -static inline bool dispatchSelectStart(Node* node)
|
| +bool dispatchSelectStart(Node* node)
|
| {
|
| if (!node || !node->layoutObject())
|
| return true;
|
| @@ -95,20 +97,186 @@ VisibleSelection expandSelectionToRespectUserSelectAllAlgorithm(Node* targetNode
|
| return newSelection;
|
| }
|
|
|
| -static VisibleSelection expandSelectionToRespectUserSelectAll(Node* targetNode, const VisibleSelection& selection)
|
| +VisibleSelection expandSelectionToRespectUserSelectAll(Node* targetNode, const VisibleSelection& selection)
|
| {
|
| if (RuntimeEnabledFeatures::selectionForComposedTreeEnabled())
|
| return expandSelectionToRespectUserSelectAllAlgorithm<VisibleSelection::InComposedTree>(targetNode, selection);
|
| return expandSelectionToRespectUserSelectAllAlgorithm<VisibleSelection::InDOMTree>(targetNode, selection);
|
| }
|
|
|
| -static bool expandSelectionUsingGranularity(VisibleSelection& selection, TextGranularity granularity)
|
| +bool expandSelectionUsingGranularity(VisibleSelection& selection, TextGranularity granularity)
|
| {
|
| if (RuntimeEnabledFeatures::selectionForComposedTreeEnabled())
|
| return selection.expandUsingGranularityInComposedTree(granularity);
|
| return selection.expandUsingGranularity(granularity);
|
| }
|
|
|
| +template <typename PositionType>
|
| +int textDistance(const PositionType& start, const PositionType& end)
|
| +{
|
| + return TextIteratorAlgorithm<typename PositionType::StrategyType>::rangeLength(start, end, true);
|
| +}
|
| +
|
| +bool canMouseDownStartSelect(Node* node)
|
| +{
|
| + if (!node || !node->layoutObject())
|
| + return true;
|
| +
|
| + if (!node->canStartSelection())
|
| + return false;
|
| +
|
| + return true;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +template <typename Strategy>
|
| +bool SelectionController::handleMousePressEventSingleClickAlgorithm(const MouseEventWithHitTestResults& event)
|
| +{
|
| + TRACE_EVENT0("blink", "SelectionController::handleMousePressEventSingleClick");
|
| + using PositionType = typename Strategy::PositionType;
|
| +
|
| + m_frame->document()->updateLayoutIgnorePendingStylesheets();
|
| + Node* innerNode = event.innerNode();
|
| + if (!(innerNode && innerNode->layoutObject() && m_mouseDownMayStartSelect))
|
| + return false;
|
| +
|
| + // Extend the selection if the Shift key is down, unless the click is in a link.
|
| + bool extendSelection = event.event().shiftKey() && !event.isOverLink();
|
| +
|
| + // Don't restart the selection when the mouse is pressed on an
|
| + // existing selection so we can allow for text dragging.
|
| + if (FrameView* view = m_frame->view()) {
|
| + LayoutPoint vPoint = view->rootFrameToContents(event.event().position());
|
| + if (!extendSelection && selection().contains(vPoint)) {
|
| + m_mouseDownWasSingleClickInSelection = true;
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + PositionWithAffinity eventPos = innerNode->layoutObject()->positionForPoint(event.localPoint());
|
| + VisiblePosition visiblePos(Strategy::toPositionType(eventPos.position()), eventPos.affinity());
|
| + if (visiblePos.isNull())
|
| + visiblePos = VisiblePosition(firstPositionInOrBeforeNode(innerNode), DOWNSTREAM);
|
| + PositionType pos = Strategy::toPositionType(visiblePos.deepEquivalent());
|
| +
|
| + VisibleSelection newSelection = selection().selection();
|
| + TextGranularity granularity = CharacterGranularity;
|
| +
|
| + if (extendSelection && newSelection.isCaretOrRange()) {
|
| + VisibleSelection selectionInUserSelectAll(expandSelectionToRespectUserSelectAll(innerNode, VisibleSelection(VisiblePosition(pos))));
|
| + if (selectionInUserSelectAll.isRange()) {
|
| + if (Strategy::selectionStart(selectionInUserSelectAll).compareTo(Strategy::selectionStart(newSelection)) < 0)
|
| + pos = Strategy::selectionStart(selectionInUserSelectAll);
|
| + else if (Strategy::selectionEnd(newSelection).compareTo(Strategy::selectionEnd(selectionInUserSelectAll)) < 0)
|
| + pos = Strategy::selectionEnd(selectionInUserSelectAll);
|
| + }
|
| +
|
| + if (!m_frame->editor().behavior().shouldConsiderSelectionAsDirectional()) {
|
| + if (pos.isNotNull()) {
|
| + // See <rdar://problem/3668157> REGRESSION (Mail): shift-click deselects when selection
|
| + // was created right-to-left
|
| + PositionType start = Strategy::selectionStart(newSelection);
|
| + PositionType end = Strategy::selectionEnd(newSelection);
|
| + int distanceToStart = textDistance(start, pos);
|
| + int distanceToEnd = textDistance(pos, end);
|
| + if (distanceToStart <= distanceToEnd)
|
| + newSelection = VisibleSelection(end, pos);
|
| + else
|
| + newSelection = VisibleSelection(start, pos);
|
| + }
|
| + } else {
|
| + newSelection.setExtent(pos);
|
| + }
|
| +
|
| + if (selection().granularity() != CharacterGranularity) {
|
| + granularity = selection().granularity();
|
| + expandSelectionUsingGranularity(newSelection, selection().granularity());
|
| + }
|
| + } else {
|
| + newSelection = expandSelectionToRespectUserSelectAll(innerNode, VisibleSelection(visiblePos));
|
| + }
|
| +
|
| + // Updating the selection is considered side-effect of the event and so it doesn't impact the handled state.
|
| + updateSelectionForMouseDownDispatchingSelectStart(innerNode, newSelection, granularity);
|
| + return false;
|
| +}
|
| +
|
| +template <typename Strategy>
|
| +void SelectionController::updateSelectionForMouseDragAlgorithm(const HitTestResult& hitTestResult, Node* mousePressNode, const LayoutPoint& dragStartPos, const IntPoint& lastKnownMousePosition)
|
| +{
|
| + using PositionType = typename Strategy::PositionType;
|
| +
|
| + if (!m_mouseDownMayStartSelect)
|
| + return;
|
| +
|
| + Node* target = hitTestResult.innerNode();
|
| + if (!target)
|
| + return;
|
| +
|
| + PositionWithAffinity rawTargetPosition = selection().selection().positionRespectingEditingBoundary(hitTestResult.localPoint(), target);
|
| + VisiblePosition targetPosition = VisiblePosition(Strategy::toPositionType(rawTargetPosition.position()), rawTargetPosition.affinity());
|
| + // Don't modify the selection if we're not on a node.
|
| + if (targetPosition.isNull())
|
| + return;
|
| +
|
| + // Restart the selection if this is the first mouse move. This work is usually
|
| + // done in handleMousePressEvent, but not if the mouse press was on an existing selection.
|
| + VisibleSelection newSelection = selection().selection();
|
| +
|
| + // Special case to limit selection to the containing block for SVG text.
|
| + // FIXME: Isn't there a better non-SVG-specific way to do this?
|
| + if (Node* selectionBaseNode = Strategy::selectionBase(newSelection).deprecatedNode()) {
|
| + if (LayoutObject* selectionBaseLayoutObject = selectionBaseNode->layoutObject()) {
|
| + if (selectionBaseLayoutObject->isSVGText()) {
|
| + if (target->layoutObject()->containingBlock() != selectionBaseLayoutObject->containingBlock())
|
| + return;
|
| + }
|
| + }
|
| + }
|
| +
|
| + if (m_selectionState == SelectionState::HaveNotStartedSelection && !dispatchSelectStart(target))
|
| + return;
|
| +
|
| + if (m_selectionState != SelectionState::ExtendedSelection) {
|
| + // Always extend selection here because it's caused by a mouse drag
|
| + m_selectionState = SelectionState::ExtendedSelection;
|
| + newSelection = VisibleSelection(targetPosition);
|
| + }
|
| +
|
| + if (RuntimeEnabledFeatures::userSelectAllEnabled()) {
|
| + Node* rootUserSelectAllForMousePressNode = Position::rootUserSelectAllForNode(mousePressNode);
|
| + if (rootUserSelectAllForMousePressNode && rootUserSelectAllForMousePressNode == Position::rootUserSelectAllForNode(target)) {
|
| + newSelection.setBase(PositionType::beforeNode(rootUserSelectAllForMousePressNode).upstream(CanCrossEditingBoundary));
|
| + newSelection.setExtent(PositionType::afterNode(rootUserSelectAllForMousePressNode).downstream(CanCrossEditingBoundary));
|
| + } else {
|
| + // Reset base for user select all when base is inside user-select-all area and extent < base.
|
| + if (rootUserSelectAllForMousePressNode) {
|
| + PositionType eventPosition = Strategy::toPositionType(target->layoutObject()->positionForPoint(hitTestResult.localPoint()).position());
|
| + PositionType dragStartPosition = Strategy::toPositionType(mousePressNode->layoutObject()->positionForPoint(dragStartPos).position());
|
| + if (eventPosition.compareTo(dragStartPosition) < 0)
|
| + newSelection.setBase(PositionType::afterNode(rootUserSelectAllForMousePressNode).downstream(CanCrossEditingBoundary));
|
| + }
|
| +
|
| + Node* rootUserSelectAllForTarget = Position::rootUserSelectAllForNode(target);
|
| + if (rootUserSelectAllForTarget && mousePressNode->layoutObject() && Strategy::toPositionType(target->layoutObject()->positionForPoint(hitTestResult.localPoint()).position()).compareTo(Strategy::toPositionType(mousePressNode->layoutObject()->positionForPoint(dragStartPos).position())) < 0)
|
| + newSelection.setExtent(PositionType::beforeNode(rootUserSelectAllForTarget).upstream(CanCrossEditingBoundary));
|
| + else if (rootUserSelectAllForTarget && mousePressNode->layoutObject())
|
| + newSelection.setExtent(PositionType::afterNode(rootUserSelectAllForTarget).downstream(CanCrossEditingBoundary));
|
| + else
|
| + newSelection.setExtent(targetPosition);
|
| + }
|
| + } else {
|
| + newSelection.setExtent(targetPosition);
|
| + }
|
| +
|
| + if (selection().granularity() != CharacterGranularity)
|
| + expandSelectionUsingGranularity(newSelection, selection().granularity());
|
| +
|
| + selection().setNonDirectionalSelectionIfNeeded(newSelection, selection().granularity(),
|
| + FrameSelection::AdjustEndpointsAtBidiBoundary);
|
| +}
|
| +
|
| bool SelectionController::updateSelectionForMouseDownDispatchingSelectStart(Node* targetNode, const VisibleSelection& selection, TextGranularity granularity)
|
| {
|
| if (Position::nodeIsUserSelectNone(targetNode))
|
| @@ -254,12 +422,6 @@ bool SelectionController::handleMousePressEventTripleClick(const MouseEventWithH
|
| return updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSelectionToRespectUserSelectAll(innerNode, newSelection), ParagraphGranularity);
|
| }
|
|
|
| -template <typename PositionType>
|
| -static int textDistance(const PositionType& start, const PositionType& end)
|
| -{
|
| - return TextIteratorAlgorithm<typename PositionType::StrategyType>::rangeLength(start, end, true);
|
| -}
|
| -
|
| bool SelectionController::handleMousePressEventSingleClick(const MouseEventWithHitTestResults& event)
|
| {
|
| if (RuntimeEnabledFeatures::selectionForComposedTreeEnabled())
|
| @@ -267,89 +429,6 @@ bool SelectionController::handleMousePressEventSingleClick(const MouseEventWithH
|
| return handleMousePressEventSingleClickAlgorithm<VisibleSelection::InDOMTree>(event);
|
| }
|
|
|
| -template <typename Strategy>
|
| -bool SelectionController::handleMousePressEventSingleClickAlgorithm(const MouseEventWithHitTestResults& event)
|
| -{
|
| - TRACE_EVENT0("blink", "SelectionController::handleMousePressEventSingleClick");
|
| - using PositionType = typename Strategy::PositionType;
|
| -
|
| - m_frame->document()->updateLayoutIgnorePendingStylesheets();
|
| - Node* innerNode = event.innerNode();
|
| - if (!(innerNode && innerNode->layoutObject() && m_mouseDownMayStartSelect))
|
| - return false;
|
| -
|
| - // Extend the selection if the Shift key is down, unless the click is in a link.
|
| - bool extendSelection = event.event().shiftKey() && !event.isOverLink();
|
| -
|
| - // Don't restart the selection when the mouse is pressed on an
|
| - // existing selection so we can allow for text dragging.
|
| - if (FrameView* view = m_frame->view()) {
|
| - LayoutPoint vPoint = view->rootFrameToContents(event.event().position());
|
| - if (!extendSelection && selection().contains(vPoint)) {
|
| - m_mouseDownWasSingleClickInSelection = true;
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - PositionWithAffinity eventPos = innerNode->layoutObject()->positionForPoint(event.localPoint());
|
| - VisiblePosition visiblePos(Strategy::toPositionType(eventPos.position()), eventPos.affinity());
|
| - if (visiblePos.isNull())
|
| - visiblePos = VisiblePosition(firstPositionInOrBeforeNode(innerNode), DOWNSTREAM);
|
| - PositionType pos = Strategy::toPositionType(visiblePos.deepEquivalent());
|
| -
|
| - VisibleSelection newSelection = selection().selection();
|
| - TextGranularity granularity = CharacterGranularity;
|
| -
|
| - if (extendSelection && newSelection.isCaretOrRange()) {
|
| - VisibleSelection selectionInUserSelectAll(expandSelectionToRespectUserSelectAll(innerNode, VisibleSelection(VisiblePosition(pos))));
|
| - if (selectionInUserSelectAll.isRange()) {
|
| - if (Strategy::selectionStart(selectionInUserSelectAll).compareTo(Strategy::selectionStart(newSelection)) < 0)
|
| - pos = Strategy::selectionStart(selectionInUserSelectAll);
|
| - else if (Strategy::selectionEnd(newSelection).compareTo(Strategy::selectionEnd(selectionInUserSelectAll)) < 0)
|
| - pos = Strategy::selectionEnd(selectionInUserSelectAll);
|
| - }
|
| -
|
| - if (!m_frame->editor().behavior().shouldConsiderSelectionAsDirectional()) {
|
| - if (pos.isNotNull()) {
|
| - // See <rdar://problem/3668157> REGRESSION (Mail): shift-click deselects when selection
|
| - // was created right-to-left
|
| - PositionType start = Strategy::selectionStart(newSelection);
|
| - PositionType end = Strategy::selectionEnd(newSelection);
|
| - int distanceToStart = textDistance(start, pos);
|
| - int distanceToEnd = textDistance(pos, end);
|
| - if (distanceToStart <= distanceToEnd)
|
| - newSelection = VisibleSelection(end, pos);
|
| - else
|
| - newSelection = VisibleSelection(start, pos);
|
| - }
|
| - } else {
|
| - newSelection.setExtent(pos);
|
| - }
|
| -
|
| - if (selection().granularity() != CharacterGranularity) {
|
| - granularity = selection().granularity();
|
| - expandSelectionUsingGranularity(newSelection, selection().granularity());
|
| - }
|
| - } else {
|
| - newSelection = expandSelectionToRespectUserSelectAll(innerNode, VisibleSelection(visiblePos));
|
| - }
|
| -
|
| - // Updating the selection is considered side-effect of the event and so it doesn't impact the handled state.
|
| - updateSelectionForMouseDownDispatchingSelectStart(innerNode, newSelection, granularity);
|
| - return false;
|
| -}
|
| -
|
| -static inline bool canMouseDownStartSelect(Node* node)
|
| -{
|
| - if (!node || !node->layoutObject())
|
| - return true;
|
| -
|
| - if (!node->canStartSelection())
|
| - return false;
|
| -
|
| - return true;
|
| -}
|
| -
|
| void SelectionController::handleMousePressEvent(const MouseEventWithHitTestResults& event)
|
| {
|
| // If we got the event back, that must mean it wasn't prevented,
|
| @@ -392,81 +471,6 @@ void SelectionController::updateSelectionForMouseDrag(const HitTestResult& hitTe
|
| updateSelectionForMouseDragAlgorithm<VisibleSelection::InDOMTree>(hitTestResult, mousePressNode, dragStartPos, lastKnownMousePosition);
|
| }
|
|
|
| -template <typename Strategy>
|
| -void SelectionController::updateSelectionForMouseDragAlgorithm(const HitTestResult& hitTestResult, Node* mousePressNode, const LayoutPoint& dragStartPos, const IntPoint& lastKnownMousePosition)
|
| -{
|
| - using PositionType = typename Strategy::PositionType;
|
| -
|
| - if (!m_mouseDownMayStartSelect)
|
| - return;
|
| -
|
| - Node* target = hitTestResult.innerNode();
|
| - if (!target)
|
| - return;
|
| -
|
| - PositionWithAffinity rawTargetPosition = selection().selection().positionRespectingEditingBoundary(hitTestResult.localPoint(), target);
|
| - VisiblePosition targetPosition = VisiblePosition(Strategy::toPositionType(rawTargetPosition.position()), rawTargetPosition.affinity());
|
| - // Don't modify the selection if we're not on a node.
|
| - if (targetPosition.isNull())
|
| - return;
|
| -
|
| - // Restart the selection if this is the first mouse move. This work is usually
|
| - // done in handleMousePressEvent, but not if the mouse press was on an existing selection.
|
| - VisibleSelection newSelection = selection().selection();
|
| -
|
| - // Special case to limit selection to the containing block for SVG text.
|
| - // FIXME: Isn't there a better non-SVG-specific way to do this?
|
| - if (Node* selectionBaseNode = Strategy::selectionBase(newSelection).deprecatedNode()) {
|
| - if (LayoutObject* selectionBaseLayoutObject = selectionBaseNode->layoutObject()) {
|
| - if (selectionBaseLayoutObject->isSVGText()) {
|
| - if (target->layoutObject()->containingBlock() != selectionBaseLayoutObject->containingBlock())
|
| - return;
|
| - }
|
| - }
|
| - }
|
| -
|
| - if (m_selectionState == SelectionState::HaveNotStartedSelection && !dispatchSelectStart(target))
|
| - return;
|
| -
|
| - if (m_selectionState != SelectionState::ExtendedSelection) {
|
| - // Always extend selection here because it's caused by a mouse drag
|
| - m_selectionState = SelectionState::ExtendedSelection;
|
| - newSelection = VisibleSelection(targetPosition);
|
| - }
|
| -
|
| - if (RuntimeEnabledFeatures::userSelectAllEnabled()) {
|
| - Node* rootUserSelectAllForMousePressNode = Position::rootUserSelectAllForNode(mousePressNode);
|
| - if (rootUserSelectAllForMousePressNode && rootUserSelectAllForMousePressNode == Position::rootUserSelectAllForNode(target)) {
|
| - newSelection.setBase(PositionType::beforeNode(rootUserSelectAllForMousePressNode).upstream(CanCrossEditingBoundary));
|
| - newSelection.setExtent(PositionType::afterNode(rootUserSelectAllForMousePressNode).downstream(CanCrossEditingBoundary));
|
| - } else {
|
| - // Reset base for user select all when base is inside user-select-all area and extent < base.
|
| - if (rootUserSelectAllForMousePressNode) {
|
| - PositionType eventPosition = Strategy::toPositionType(target->layoutObject()->positionForPoint(hitTestResult.localPoint()).position());
|
| - PositionType dragStartPosition = Strategy::toPositionType(mousePressNode->layoutObject()->positionForPoint(dragStartPos).position());
|
| - if (eventPosition.compareTo(dragStartPosition) < 0)
|
| - newSelection.setBase(PositionType::afterNode(rootUserSelectAllForMousePressNode).downstream(CanCrossEditingBoundary));
|
| - }
|
| -
|
| - Node* rootUserSelectAllForTarget = Position::rootUserSelectAllForNode(target);
|
| - if (rootUserSelectAllForTarget && mousePressNode->layoutObject() && Strategy::toPositionType(target->layoutObject()->positionForPoint(hitTestResult.localPoint()).position()).compareTo(Strategy::toPositionType(mousePressNode->layoutObject()->positionForPoint(dragStartPos).position())) < 0)
|
| - newSelection.setExtent(PositionType::beforeNode(rootUserSelectAllForTarget).upstream(CanCrossEditingBoundary));
|
| - else if (rootUserSelectAllForTarget && mousePressNode->layoutObject())
|
| - newSelection.setExtent(PositionType::afterNode(rootUserSelectAllForTarget).downstream(CanCrossEditingBoundary));
|
| - else
|
| - newSelection.setExtent(targetPosition);
|
| - }
|
| - } else {
|
| - newSelection.setExtent(targetPosition);
|
| - }
|
| -
|
| - if (selection().granularity() != CharacterGranularity)
|
| - expandSelectionUsingGranularity(newSelection, selection().granularity());
|
| -
|
| - selection().setNonDirectionalSelectionIfNeeded(newSelection, selection().granularity(),
|
| - FrameSelection::AdjustEndpointsAtBidiBoundary);
|
| -}
|
| -
|
| bool SelectionController::handleMouseReleaseEvent(const MouseEventWithHitTestResults& event, const LayoutPoint& dragStartPos)
|
| {
|
| bool handled = false;
|
|
|