TEST_F(EditingUtilitiesTest, directionOfEnclosingBlock) { const char* bodyContent = "<p id='host'><b id='one'></b><b id='two'>22</b></p>"; const char* shadowContent = "<content select=#two></content><p dir=rtl><content select=#one></content><p>"; setBodyContent(bodyContent); setShadowContent(shadowContent, "host"); updateLayoutAndStyleForPainting(); Node* one = document().getElementById("one"); EXPECT_EQ(LTR, directionOfEnclosingBlock(Position(one, 0))); EXPECT_EQ(RTL, directionOfEnclosingBlock(PositionInComposedTree(one, 0))); }
VisiblePosition SelectionEditor::modifyMovingLeft(TextGranularity granularity) { VisiblePosition pos; switch (granularity) { case CharacterGranularity: if (m_selection.isRange()) { if (directionOfSelection() == LTR) pos = createVisiblePosition(m_selection.start(), m_selection.affinity()); else pos = createVisiblePosition(m_selection.end(), m_selection.affinity()); } else { pos = leftPositionOf(createVisiblePosition(m_selection.extent(), m_selection.affinity())); } break; case WordGranularity: { bool skipsSpaceWhenMovingRight = frame() && frame()->editor().behavior().shouldSkipSpaceWhenMovingRight(); pos = leftWordPosition(createVisiblePosition(m_selection.extent(), m_selection.affinity()), skipsSpaceWhenMovingRight); break; } case SentenceGranularity: case LineGranularity: case ParagraphGranularity: case SentenceBoundary: case ParagraphBoundary: case DocumentBoundary: // FIXME: Implement all of the above. pos = modifyMovingBackward(granularity); break; case LineBoundary: pos = leftBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock()); break; } return pos; }
VisiblePosition SelectionModifier::modifyExtendingRight( TextGranularity granularity) { VisiblePosition pos = createVisiblePosition(m_selection.extent(), m_selection.affinity()); // The difference between modifyExtendingRight and modifyExtendingForward is: // modifyExtendingForward always extends forward logically. // modifyExtendingRight behaves the same as modifyExtendingForward except for // extending character or word, it extends forward logically if the enclosing // block is LTR direction, but it extends backward logically if the enclosing // block is RTL direction. switch (granularity) { case CharacterGranularity: if (directionOfEnclosingBlock() == LTR) pos = nextPositionOf(pos, CanSkipOverEditingBoundary); else pos = previousPositionOf(pos, CanSkipOverEditingBoundary); break; case WordGranularity: if (directionOfEnclosingBlock() == LTR) pos = nextWordPositionForPlatform(pos); else pos = previousWordPosition(pos); break; case LineBoundary: if (directionOfEnclosingBlock() == LTR) pos = modifyExtendingForward(granularity); else pos = modifyExtendingBackward(granularity); break; case SentenceGranularity: case LineGranularity: case ParagraphGranularity: case SentenceBoundary: case ParagraphBoundary: case DocumentBoundary: // FIXME: implement all of the above? pos = modifyExtendingForward(granularity); break; } adjustPositionForUserSelectAll(pos, directionOfEnclosingBlock() == LTR); return pos; }
TextDirection SelectionModifier::directionOfSelection() const { InlineBox* startBox = nullptr; InlineBox* endBox = nullptr; // Cache the VisiblePositions because visibleStart() and visibleEnd() // can cause layout, which has the potential to invalidate lineboxes. VisiblePosition startPosition = m_selection.visibleStart(); VisiblePosition endPosition = m_selection.visibleEnd(); if (startPosition.isNotNull()) startBox = computeInlineBoxPosition(startPosition).inlineBox; if (endPosition.isNotNull()) endBox = computeInlineBoxPosition(endPosition).inlineBox; if (startBox && endBox && startBox->direction() == endBox->direction()) return startBox->direction(); return directionOfEnclosingBlock(); }
VisiblePosition SelectionModifier::modifyExtendingBackward( TextGranularity granularity) { VisiblePosition pos = createVisiblePosition(m_selection.extent(), m_selection.affinity()); // Extending a selection backward by word or character from just after a table // selects the table. This "makes sense" from the user perspective, esp. when // deleting. It was done here instead of in VisiblePosition because we want // VPs to iterate over everything. switch (granularity) { case CharacterGranularity: pos = previousPositionOf(pos, CanSkipOverEditingBoundary); break; case WordGranularity: pos = previousWordPosition(pos); break; case SentenceGranularity: pos = previousSentencePosition(pos); break; case LineGranularity: pos = previousLinePosition( pos, lineDirectionPointForBlockDirectionNavigation(EXTENT)); break; case ParagraphGranularity: pos = previousParagraphPosition( pos, lineDirectionPointForBlockDirectionNavigation(EXTENT)); break; case SentenceBoundary: pos = startOfSentence(startForPlatform()); break; case LineBoundary: pos = logicalStartOfLine(startForPlatform()); break; case ParagraphBoundary: pos = startOfParagraph(startForPlatform()); break; case DocumentBoundary: pos = startForPlatform(); if (isEditablePosition(pos.deepEquivalent())) pos = startOfEditableContent(pos); else pos = startOfDocument(pos); break; } adjustPositionForUserSelectAll(pos, !(directionOfEnclosingBlock() == LTR)); return pos; }
VisiblePosition SelectionModifier::modifyExtendingForward( TextGranularity granularity) { VisiblePosition pos = createVisiblePosition(m_selection.extent(), m_selection.affinity()); switch (granularity) { case CharacterGranularity: pos = nextPositionOf(pos, CanSkipOverEditingBoundary); break; case WordGranularity: pos = nextWordPositionForPlatform(pos); break; case SentenceGranularity: pos = nextSentencePosition(pos); break; case LineGranularity: pos = nextLinePosition( pos, lineDirectionPointForBlockDirectionNavigation(EXTENT)); break; case ParagraphGranularity: pos = nextParagraphPosition( pos, lineDirectionPointForBlockDirectionNavigation(EXTENT)); break; case SentenceBoundary: pos = endOfSentence(endForPlatform()); break; case LineBoundary: pos = logicalEndOfLine(endForPlatform()); break; case ParagraphBoundary: pos = endOfParagraph(endForPlatform()); break; case DocumentBoundary: pos = endForPlatform(); if (isEditablePosition(pos.deepEquivalent())) pos = endOfEditableContent(pos); else pos = endOfDocument(pos); break; } adjustPositionForUserSelectAll(pos, directionOfEnclosingBlock() == LTR); return pos; }
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; }