diff --git a/editor/libeditor/EditorBase.cpp b/editor/libeditor/EditorBase.cpp index 74c6d91091d5..a2eec999e68d 100644 --- a/editor/libeditor/EditorBase.cpp +++ b/editor/libeditor/EditorBase.cpp @@ -4671,54 +4671,63 @@ nsresult EditorBase::ExtendSelectionForDelete( return NS_OK; } - nsCOMPtr selectionController = - GetSelectionController(); - if (NS_WARN_IF(!selectionController)) { + RefPtr frameSelection = + SelectionRefPtr()->GetFrameSelection(); + if (NS_WARN_IF(!frameSelection)) { return NS_ERROR_NOT_INITIALIZED; } + Result, nsresult> result(NS_ERROR_UNEXPECTED); + nsIEditor::EDirection directionAndAmountResult = *aDirectionAndAmount; switch (*aDirectionAndAmount) { - case eNextWord: { - nsresult rv = selectionController->WordExtendForDelete(true); + case eNextWord: + result = + frameSelection->CreateRangeExtendedToNextWordBoundary(); if (NS_WARN_IF(Destroyed())) { return NS_ERROR_EDITOR_DESTROYED; } NS_WARNING_ASSERTION( - NS_SUCCEEDED(rv), - "nsISelectionController::WordExtendForDelete(true) failed"); + result.isOk(), + "nsFrameSelection::CreateRangeExtendedToNextWordBoundary() failed"); // DeleteSelectionWithTransaction() doesn't handle these actions // because it's inside batching, so don't confuse it: - *aDirectionAndAmount = eNone; - return rv; - } - case ePreviousWord: { - nsresult rv = selectionController->WordExtendForDelete(false); + directionAndAmountResult = eNone; + break; + case ePreviousWord: + result = frameSelection + ->CreateRangeExtendedToPreviousWordBoundary(); if (NS_WARN_IF(Destroyed())) { return NS_ERROR_EDITOR_DESTROYED; } NS_WARNING_ASSERTION( - NS_SUCCEEDED(rv), - "nsISelectionController::WordExtendForDelete(false) failed"); - *aDirectionAndAmount = eNone; - return rv; - } - case eNext: { - nsresult rv = selectionController->CharacterExtendForDelete(); + result.isOk(), + "nsFrameSelection::CreateRangeExtendedToPreviousWordBoundary() " + "failed"); + // DeleteSelectionWithTransaction() doesn't handle these actions + // because it's inside batching, so don't confuse it: + directionAndAmountResult = eNone; + break; + case eNext: + result = + frameSelection + ->CreateRangeExtendedToNextGraphemeClusterBoundary(); if (NS_WARN_IF(Destroyed())) { return NS_ERROR_EDITOR_DESTROYED; } - NS_WARNING_ASSERTION( - NS_SUCCEEDED(rv), - "nsISelectionController::CharacterExtendForDelete() failed"); + NS_WARNING_ASSERTION(result.isOk(), + "nsFrameSelection::" + "CreateRangeExtendedToNextGraphemeClusterBoundary() " + "failed"); // Don't set aDirectionAndAmount to eNone (see Bug 502259) - return rv; - } + break; case ePrevious: { // Only extend the selection where the selection is after a UTF-16 // surrogate pair or a variation selector. // For other cases we don't want to do that, in order // to make sure that pressing backspace will only delete the last // typed character. + // XXX This is odd if the previous one is a sequence for a grapheme + // cluster. EditorRawDOMPoint atStartOfSelection = EditorBase::GetStartPoint(*SelectionRefPtr()); if (NS_WARN_IF(!atStartOfSelection.IsSet())) { @@ -4734,52 +4743,80 @@ nsresult EditorBase::ExtendSelectionForDelete( return NS_OK; } - if (insertionPoint.IsInTextNode()) { - const nsTextFragment* data = - &insertionPoint.GetContainerAsText()->TextFragment(); - uint32_t offset = insertionPoint.Offset(); - if ((offset > 1 && - data->IsLowSurrogateFollowingHighSurrogateAt(offset - 1)) || - (offset > 0 && - gfxFontUtils::IsVarSelector(data->CharAt(offset - 1)))) { - nsresult rv = selectionController->CharacterExtendForBackspace(); - if (NS_WARN_IF(Destroyed())) { - return NS_ERROR_EDITOR_DESTROYED; - } - NS_WARNING_ASSERTION( - NS_SUCCEEDED(rv), - "nsISelectionController::CharacterExtendForBackspace() failed"); - return rv; - } + if (!insertionPoint.IsInTextNode()) { + return NS_OK; } - return NS_OK; - } - case eToBeginningOfLine: { - // Select to beginning - nsresult rv = selectionController->IntraLineMove(false, true); + + const nsTextFragment* data = + &insertionPoint.GetContainerAsText()->TextFragment(); + uint32_t offset = insertionPoint.Offset(); + if (!(offset > 1 && + data->IsLowSurrogateFollowingHighSurrogateAt(offset - 1)) && + !(offset > 0 && + gfxFontUtils::IsVarSelector(data->CharAt(offset - 1)))) { + return NS_OK; + } + // Different from the `eNext` case, we look for character boundary. + // I'm not sure whether this inconsistency between "Delete" and + // "Backspace" is intentional or not. + result = + frameSelection + ->CreateRangeExtendedToPreviousCharacterBoundary(); if (NS_WARN_IF(Destroyed())) { return NS_ERROR_EDITOR_DESTROYED; } NS_WARNING_ASSERTION( - NS_SUCCEEDED(rv), - "nsISelectionController::IntraLineMove(false, true) failed"); - *aDirectionAndAmount = eNone; - return rv; + result.isOk(), + "nsFrameSelection::" + "CreateRangeExtendedToPreviousGraphemeClusterBoundary() failed"); + break; } - case eToEndOfLine: { - nsresult rv = selectionController->IntraLineMove(true, true); + case eToBeginningOfLine: + result = frameSelection + ->CreateRangeExtendedToPreviousHardLineBreak(); if (NS_WARN_IF(Destroyed())) { return NS_ERROR_EDITOR_DESTROYED; } NS_WARNING_ASSERTION( - NS_SUCCEEDED(rv), - "nsISelectionController::IntraLineMove(true, true) failed"); - *aDirectionAndAmount = eNext; - return rv; - } + result.isOk(), + "nsFrameSelection::CreateRangeExtendedToPreviousHardLineBreak() " + "failed"); + directionAndAmountResult = eNone; + break; + case eToEndOfLine: + result = + frameSelection->CreateRangeExtendedToNextHardLineBreak(); + if (NS_WARN_IF(Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + result.isOk(), + "nsFrameSelection::CreateRangeExtendedToNextHardLineBreak() failed"); + directionAndAmountResult = eNext; + break; default: return NS_OK; } + + if (result.isErr()) { + return result.unwrapErr(); + } + *aDirectionAndAmount = directionAndAmountResult; + StaticRange* range = result.inspect().get(); + if (!range || NS_WARN_IF(!range->IsPositioned())) { + return NS_OK; + } + ErrorResult error; + MOZ_KnownLive(SelectionRefPtr()) + ->SetStartAndEndInLimiter(range->StartRef().AsRaw(), + range->EndRef().AsRaw(), error); + if (NS_WARN_IF(Destroyed())) { + error.SuppressException(); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION(!error.Failed(), + "Selection::SetBaseAndExtentInLimiter() failed"); + return error.StealNSResult(); } nsresult EditorBase::DeleteSelectionWithTransaction( diff --git a/editor/libeditor/HTMLEditSubActionHandler.cpp b/editor/libeditor/HTMLEditSubActionHandler.cpp index 14591c4398c6..9a4038f3a589 100644 --- a/editor/libeditor/HTMLEditSubActionHandler.cpp +++ b/editor/libeditor/HTMLEditSubActionHandler.cpp @@ -3140,10 +3140,10 @@ EditActionResult HTMLEditor::HandleDeleteNonCollapsedSelection( if (SelectionRefPtr()->RangeCount() == 1) { if (nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0)) { RefPtr extendedRange = - GetExtendedRangeToIncludeInvisibleNodes(*firstRange); + GetRangeExtendedToIncludeInvisibleNodes(*firstRange); if (!extendedRange) { NS_WARNING( - "HTMLEditor::GetExtendedRangeToIncludeInvisibleNodes() failed"); + "HTMLEditor::GetRangeExtendedToIncludeInvisibleNodes() failed"); return EditActionResult(NS_ERROR_FAILURE); } ErrorResult error; @@ -7473,7 +7473,7 @@ size_t HTMLEditor::CollectChildren( } already_AddRefed -HTMLEditor::GetExtendedRangeToIncludeInvisibleNodes( +HTMLEditor::GetRangeExtendedToIncludeInvisibleNodes( const AbstractRange& aAbstractRange) { MOZ_ASSERT(IsEditActionDataAvailable()); MOZ_ASSERT(!aAbstractRange.Collapsed()); diff --git a/editor/libeditor/HTMLEditor.h b/editor/libeditor/HTMLEditor.h index a3fdeb946eee..938a2c42e3d3 100644 --- a/editor/libeditor/HTMLEditor.h +++ b/editor/libeditor/HTMLEditor.h @@ -2279,7 +2279,7 @@ class HTMLEditor final : public TextEditor, nsIEditor::EDirection aDirectionAndAmount); /** - * GetExtendedRangeToIncludeInvisibleNodes() returns extended range. + * GetRangeExtendedToIncludeInvisibleNodes() returns extended range. * If there are some invisible nodes around aAbstractRange, they may * be included. * @@ -2287,7 +2287,7 @@ class HTMLEditor final : public TextEditor, * and must be positioned. * @return Extended range. */ - already_AddRefed GetExtendedRangeToIncludeInvisibleNodes( + already_AddRefed GetRangeExtendedToIncludeInvisibleNodes( const dom::AbstractRange& aAbstractRange); /** diff --git a/layout/generic/nsFrameSelection.cpp b/layout/generic/nsFrameSelection.cpp index 08f88f33a970..e1daaff592b4 100644 --- a/layout/generic/nsFrameSelection.cpp +++ b/layout/generic/nsFrameSelection.cpp @@ -70,6 +70,7 @@ static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID); #include "mozilla/dom/Element.h" #include "mozilla/dom/Selection.h" #include "mozilla/dom/ShadowRoot.h" +#include "mozilla/dom/StaticRange.h" #include "mozilla/dom/Text.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/SelectionBinding.h" @@ -305,6 +306,15 @@ struct MOZ_RAII AutoPrepareFocusRange { ////////////BEGIN nsFrameSelection methods +template Result, nsresult> +nsFrameSelection::CreateRangeExtendedToSomewhere( + nsDirection aDirection, const nsSelectionAmount aAmount, + CaretMovementStyle aMovementStyle); +template Result, nsresult> +nsFrameSelection::CreateRangeExtendedToSomewhere( + nsDirection aDirection, const nsSelectionAmount aAmount, + CaretMovementStyle aMovementStyle); + nsFrameSelection::nsFrameSelection(PresShell* aPresShell, nsIContent* aLimiter, const bool aAccessibleCaretEnabled) { for (size_t i = 0; i < ArrayLength(mDomSelections); i++) { @@ -640,11 +650,6 @@ nsresult nsFrameSelection::MoveCaret(nsDirection aDirection, bool aContinueSelection, const nsSelectionAmount aAmount, CaretMovementStyle aMovementStyle) { - bool visualMovement = aMovementStyle == eVisual || - (aMovementStyle == eUsePrefStyle && - (mCaret.mMovementStyle == 1 || - (mCaret.mMovementStyle == 2 && !aContinueSelection))); - NS_ENSURE_STATE(mPresShell); // Flush out layout, since we need it to be up to date to do caret // positioning. @@ -668,8 +673,7 @@ nsresult nsFrameSelection::MoveCaret(nsDirection aDirection, } int32_t scrollFlags = Selection::SCROLL_FOR_CARET_MOVE; - const bool isEditorSelection = sel->IsEditorSelection(); - if (isEditorSelection) { + if (sel->IsEditorSelection()) { // If caret moves in editor, it should cause scrolling even if it's in // overflow: hidden;. scrollFlags |= Selection::SCROLL_OVERFLOW_HIDDEN; @@ -728,55 +732,33 @@ nsresult nsFrameSelection::MoveCaret(nsDirection aDirection, return NS_OK; } + bool visualMovement = + mCaret.IsVisualMovement(aContinueSelection, aMovementStyle); nsIFrame* frame; int32_t offsetused = 0; - nsresult result = + nsresult rv = sel->GetPrimaryFrameForFocusNode(&frame, &offsetused, visualMovement); - - if (NS_FAILED(result) || !frame) - return NS_FAILED(result) ? result : NS_ERROR_FAILURE; - - const auto forceEditableRegion = - isEditorSelection ? nsPeekOffsetStruct::ForceEditableRegion::Yes - : nsPeekOffsetStruct::ForceEditableRegion::No; - const nsBidiDirection paraDir = nsBidiPresUtils::ParagraphDirection(frame); + if (NS_FAILED(rv) || !frame) { + return NS_FAILED(rv) ? rv : NS_ERROR_FAILURE; + } CaretAssociateHint tHint(mCaret.mHint); // temporary variable so we dont set // mCaret.mHint until it is necessary - nsDirection direction{eDirPrevious}; - switch (aAmount) { - case eSelectCharacter: - case eSelectCluster: - case eSelectWord: - case eSelectWordNoSpace: - InvalidateDesiredPos(); - direction = (visualMovement && paraDir == NSBIDI_RTL) - ? nsDirection(1 - aDirection) - : aDirection; - break; - case eSelectLine: - direction = aDirection; - break; - case eSelectBeginLine: - case eSelectEndLine: - InvalidateDesiredPos(); - direction = (visualMovement && paraDir == NSBIDI_RTL) - ? nsDirection(1 - aDirection) - : aDirection; - break; - default: - return NS_ERROR_FAILURE; + Result isIntraLineCaretMove = IsIntraLineCaretMove(aAmount); + if (isIntraLineCaretMove.isErr()) { + return NS_ERROR_FAILURE; + } + if (isIntraLineCaretMove.inspect()) { + // Forget old caret position for moving caret to different line since + // caret position may be changed. + InvalidateDesiredPos(); } - // set data using mLimiters.mLimiter to stop on scroll views. If we have a - // limiter then we stop peeking when we hit scrollable views. If no limiter - // then just let it go ahead - nsPeekOffsetStruct pos(aAmount, direction, offsetused, desiredPos, true, - mLimiters.mLimiter != nullptr, true, visualMovement, - aContinueSelection, forceEditableRegion); - - if (NS_SUCCEEDED(result = frame->PeekOffset(&pos)) && pos.mResultContent) { + Result result = PeekOffsetForCaretMove( + aDirection, aContinueSelection, aAmount, aMovementStyle, desiredPos); + if (result.isOk() && result.inspect().mResultContent) { + const nsPeekOffsetStruct& pos = result.inspect(); nsIFrame* theFrame; int32_t currentOffset, frameStart, frameEnd; @@ -837,8 +819,8 @@ nsresult nsFrameSelection::MoveCaret(nsDirection aDirection, const FocusMode focusMode = aContinueSelection ? FocusMode::kExtendSelection : FocusMode::kCollapseToNewPoint; - result = TakeFocus(MOZ_KnownLive(pos.mResultContent), pos.mContentOffset, - pos.mContentOffset, tHint, focusMode); + rv = TakeFocus(MOZ_KnownLive(pos.mResultContent), pos.mContentOffset, + pos.mContentOffset, tHint, focusMode); } else if (aAmount <= eSelectWordNoSpace && aDirection == eDirNext && !aContinueSelection) { // Collapse selection if PeekOffset failed, we either @@ -851,14 +833,69 @@ nsresult nsFrameSelection::MoveCaret(nsDirection aDirection, mCaret.mHint = CARET_ASSOCIATE_BEFORE; // We're now at the end of the // frame to the left. } - result = NS_OK; + rv = NS_OK; + } else { + rv = result.isErr() ? result.unwrapErr() : NS_OK; } - if (NS_SUCCEEDED(result)) { - result = sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, - ScrollAxis(), ScrollAxis(), scrollFlags); + if (NS_SUCCEEDED(rv)) { + rv = sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, + ScrollAxis(), ScrollAxis(), scrollFlags); } - return result; + return rv; +} + +Result nsFrameSelection::PeekOffsetForCaretMove( + nsDirection aDirection, bool aContinueSelection, + const nsSelectionAmount aAmount, CaretMovementStyle aMovementStyle, + const nsPoint& aDesiredPos) const { + Selection* selection = + mDomSelections[GetIndexFromSelectionType(SelectionType::eNormal)]; + if (!selection) { + return Err(NS_ERROR_NULL_POINTER); + } + + const bool visualMovement = + mCaret.IsVisualMovement(aContinueSelection, aMovementStyle); + + nsIFrame* frame = nullptr; + int32_t offsetused = 0; + nsresult rv = selection->GetPrimaryFrameForFocusNode(&frame, &offsetused, + visualMovement); + if (NS_FAILED(rv) || !frame) { + return Err(NS_FAILED(rv) ? rv : NS_ERROR_FAILURE); + } + + const auto kForceEditableRegion = + selection->IsEditorSelection() + ? nsPeekOffsetStruct::ForceEditableRegion::Yes + : nsPeekOffsetStruct::ForceEditableRegion::No; + const nsBidiDirection kParagraphDirection = + nsBidiPresUtils::ParagraphDirection(frame); + + nsDirection direction{aDirection}; + Result isIntraLineCareMove = IsIntraLineCaretMove(aAmount); + if (isIntraLineCareMove.isErr()) { + return Err(NS_ERROR_FAILURE); + } + if (isIntraLineCareMove.inspect()) { + // If caret is moving in same line and user expects visual movement, + // we revert the direction if direction of current paragraph is RTL. + direction = (visualMovement && kParagraphDirection == NSBIDI_RTL) + ? nsDirection(1 - aDirection) + : aDirection; + } + + // set data using mLimiters.mLimiter to stop on scroll views. If we have a + // limiter then we stop peeking when we hit scrollable views. If no limiter + // then just let it go ahead + nsPeekOffsetStruct pos(aAmount, direction, offsetused, aDesiredPos, true, + !!mLimiters.mLimiter, true, visualMovement, + aContinueSelection, kForceEditableRegion); + if (NS_FAILED(rv = frame->PeekOffset(&pos))) { + return Err(rv); + } + return pos; } nsPrevNextBidiLevels nsFrameSelection::GetPrevNextBidiLevels( @@ -1999,6 +2036,58 @@ nsresult nsFrameSelection::IntraLineMove(bool aForward, bool aExtend) { } } +template +Result, nsresult> +nsFrameSelection::CreateRangeExtendedToSomewhere( + nsDirection aDirection, const nsSelectionAmount aAmount, + CaretMovementStyle aMovementStyle) { + MOZ_ASSERT(aDirection == eDirNext || aDirection == eDirPrevious); + MOZ_ASSERT(aAmount == eSelectCharacter || aAmount == eSelectCluster || + aAmount == eSelectWord || aAmount == eSelectBeginLine || + aAmount == eSelectEndLine); + MOZ_ASSERT(aMovementStyle == eLogical || aMovementStyle == eVisual || + aMovementStyle == eUsePrefStyle); + + if (!mPresShell) { + return Err(NS_ERROR_UNEXPECTED); + } + OwningNonNull presShell(*mPresShell); + presShell->FlushPendingNotifications(FlushType::Layout); + if (!mPresShell) { + return Err(NS_ERROR_FAILURE); + } + Selection* selection = + mDomSelections[GetIndexFromSelectionType(SelectionType::eNormal)]; + if (!selection || selection->RangeCount() != 1) { + return Err(NS_ERROR_FAILURE); + } + RefPtr firstRange = selection->GetRangeAt(0); + if (!firstRange || !firstRange->IsPositioned()) { + return Err(NS_ERROR_FAILURE); + } + Result result = PeekOffsetForCaretMove( + aDirection, true, aAmount, aMovementStyle, nsPoint(0, 0)); + if (result.isErr()) { + return Err(NS_ERROR_FAILURE); + } + const nsPeekOffsetStruct& pos = result.inspect(); + RefPtr range; + if (NS_WARN_IF(!pos.mResultContent)) { + return range; + } + if (aDirection == eDirPrevious) { + range = RangeType::Create( + RawRangeBoundary(pos.mResultContent, pos.mContentOffset), + firstRange->EndRef(), IgnoreErrors()); + } else { + range = RangeType::Create( + firstRange->StartRef(), + RawRangeBoundary(pos.mResultContent, pos.mContentOffset), + IgnoreErrors()); + } + return range; +} + nsresult nsFrameSelection::SelectAll() { nsCOMPtr rootContent; if (mLimiters.mLimiter) { diff --git a/layout/generic/nsFrameSelection.h b/layout/generic/nsFrameSelection.h index dd4502177b8a..9d210ac6ccbf 100644 --- a/layout/generic/nsFrameSelection.h +++ b/layout/generic/nsFrameSelection.h @@ -11,6 +11,7 @@ #include "mozilla/Attributes.h" #include "mozilla/EventForwards.h" #include "mozilla/dom/Selection.h" +#include "mozilla/Result.h" #include "mozilla/TextRange.h" #include "mozilla/UniquePtr.h" #include "nsIFrame.h" @@ -566,6 +567,74 @@ class nsFrameSelection final { MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult IntraLineMove(bool aForward, bool aExtend); + /** + * CreateRangeExtendedToNextGraphemeClusterBoundary() returns range which is + * extended from normal selection range to start of next grapheme cluster + * boundary. + */ + template + MOZ_CAN_RUN_SCRIPT mozilla::Result, nsresult> + CreateRangeExtendedToNextGraphemeClusterBoundary() { + return CreateRangeExtendedToSomewhere(eDirNext, eSelectCluster, + eLogical); + } + + /** + * CreateRangeExtendedToPreviousCharacterBoundary() returns range which is + * extended from normal selection range to start of previous character + * boundary. + */ + template + MOZ_CAN_RUN_SCRIPT mozilla::Result, nsresult> + CreateRangeExtendedToPreviousCharacterBoundary() { + return CreateRangeExtendedToSomewhere( + eDirPrevious, eSelectCharacter, eLogical); + } + + /** + * CreateRangeExtendedToNextWordBoundary() returns range which is + * extended from normal selection range to start of next word boundary. + */ + template + MOZ_CAN_RUN_SCRIPT mozilla::Result, nsresult> + CreateRangeExtendedToNextWordBoundary() { + return CreateRangeExtendedToSomewhere(eDirNext, eSelectWord, + eLogical); + } + + /** + * CreateRangeExtendedToPreviousWordBoundary() returns range which is + * extended from normal selection range to start of previous word boundary. + */ + template + MOZ_CAN_RUN_SCRIPT mozilla::Result, nsresult> + CreateRangeExtendedToPreviousWordBoundary() { + return CreateRangeExtendedToSomewhere(eDirPrevious, eSelectWord, + eLogical); + } + + /** + * CreateRangeExtendedToPreviousHardLineBreak() returns range which is + * extended from normal selection range to previous hard line break. + */ + template + MOZ_CAN_RUN_SCRIPT mozilla::Result, nsresult> + CreateRangeExtendedToPreviousHardLineBreak() { + return CreateRangeExtendedToSomewhere( + eDirPrevious, eSelectBeginLine, eLogical); + } + + /** + * CreateRangeExtendedToNextHardLineBreak() returns range which is extended + * from normal selection range to next hard line break. + */ + template + MOZ_CAN_RUN_SCRIPT mozilla::Result, nsresult> + CreateRangeExtendedToNextHardLineBreak() { + return CreateRangeExtendedToSomewhere(eDirNext, eSelectEndLine, + eLogical); + } + /** * Select All will generally be called from the nsiselectioncontroller * implementations. it will select the whole doc @@ -770,6 +839,49 @@ class nsFrameSelection final { nsSelectionAmount aAmount, CaretMovementStyle aMovementStyle); + /** + * PeekOffsetForCaretMove() only peek offset for caret move. I.e., won't + * change selection ranges nor bidi information. + */ + mozilla::Result PeekOffsetForCaretMove( + nsDirection aDirection, bool aContinueSelection, + const nsSelectionAmount aAmount, CaretMovementStyle aMovementStyle, + const nsPoint& aDesiredPos) const; + + /** + * CreateRangeExtendedToSomewhere() is common method to implement + * CreateRangeExtendedTo*(). This method creates a range extended from + * normal selection range. + */ + template + MOZ_CAN_RUN_SCRIPT mozilla::Result, nsresult> + CreateRangeExtendedToSomewhere(nsDirection aDirection, + const nsSelectionAmount aAmount, + CaretMovementStyle aMovementStyle); + + /** + * IsIntraLineCaretMove() is a helper method for PeekOffsetForCaretMove() + * and CreateRangeExtendedToSomwhereFromNormalSelection(). This returns + * whether aAmount is intra line move or is crossing hard line break. + * This returns error if aMount is not supported by the methods. + */ + static mozilla::Result IsIntraLineCaretMove( + nsSelectionAmount aAmount) { + switch (aAmount) { + case eSelectCharacter: + case eSelectCluster: + case eSelectWord: + case eSelectWordNoSpace: + case eSelectBeginLine: + case eSelectEndLine: + return true; + case eSelectLine: + return false; + default: + return mozilla::Err(NS_ERROR_FAILURE); + } + } + nsresult FetchDesiredPos( nsPoint& aDesiredPos); // the position requested by the Key Handling for // up down @@ -902,6 +1014,14 @@ class nsFrameSelection final { CaretAssociateHint mHint = mozilla::CARET_ASSOCIATE_BEFORE; nsBidiLevel mBidiLevel = BIDI_LEVEL_UNDEFINED; int8_t mMovementStyle = 0; + + bool IsVisualMovement(bool aContinueSelection, + CaretMovementStyle aMovementStyle) const { + return aMovementStyle == eVisual || + (aMovementStyle == eUsePrefStyle && + (mMovementStyle == 1 || + (mMovementStyle == 2 && !aContinueSelection))); + } }; Caret mCaret;