tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 6eed70821ee6c71d34917751fc406fe9a65a5c9f
parent c6a0e55250831f5322760be73def174222a65943
Author: Masayuki Nakano <masayuki@d-toybox.com>
Date:   Mon, 15 Dec 2025 07:50:40 +0000

Bug 2000978 - Make `SelectionMovementUtils::GetFrameForNodeOffset()` return `FrameAndOffset` instead of using `aReturnOffset` r=emilio,layout-reviewers

Additionally, this adds some shortcut operators to `FrameAndOffset`.
Therefore, some users of derived structs of `FrameAndOffset` become
simpler.

Differential Revision: https://phabricator.services.mozilla.com/D273716

Diffstat:
Mdom/base/Selection.cpp | 36+++++++++++++++++++++---------------
Mdom/events/ContentEventHandler.cpp | 10+++++-----
Mlayout/base/AccessibleCaretManager.cpp | 12+++++++-----
Mlayout/base/CaretAssociationHint.cpp | 2+-
Mlayout/base/nsLayoutUtils.cpp | 2+-
Mlayout/base/nsLayoutUtils.h | 2+-
Mlayout/generic/SelectionMovementUtils.cpp | 135+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mlayout/generic/SelectionMovementUtils.h | 18+++++++++++++-----
Mlayout/generic/nsFrameSelection.cpp | 46++++++++++++++++++++++++----------------------
Mlayout/generic/nsIFrame.cpp | 12++++++------
10 files changed, 149 insertions(+), 126 deletions(-)

diff --git a/dom/base/Selection.cpp b/dom/base/Selection.cpp @@ -3713,7 +3713,6 @@ nsIFrame* Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion, nsINode* node = nullptr; uint32_t nodeOffset = 0; - nsIFrame* frame = nullptr; switch (aRegion) { case nsISelectionController::SELECTION_ANCHOR_REGION: @@ -3732,12 +3731,14 @@ nsIFrame* Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion, nsCOMPtr<nsIContent> content = do_QueryInterface(node); NS_ENSURE_TRUE(content.get(), nullptr); - uint32_t frameOffset = 0; - frame = SelectionMovementUtils::GetFrameForNodeOffset( - content, nodeOffset, mFrameSelection->GetHint(), &frameOffset); - if (!frame) return nullptr; + FrameAndOffset frameAndOffset = SelectionMovementUtils::GetFrameForNodeOffset( + content, nodeOffset, mFrameSelection->GetHint()); + if (!frameAndOffset) { + return nullptr; + } - SelectionMovementUtils::AdjustFrameForLineStart(frame, frameOffset); + SelectionMovementUtils::AdjustFrameForLineStart( + frameAndOffset.mFrame, frameAndOffset.mOffsetInFrameContent); // Figure out what node type we have, then get the // appropriate rect for its nodeOffset. @@ -3747,16 +3748,21 @@ nsIFrame* Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion, if (isText) { nsIFrame* childFrame = nullptr; int32_t frameOffset = 0; - nsresult rv = frame->GetChildFrameContainingOffset( + nsresult rv = frameAndOffset->GetChildFrameContainingOffset( + // FIXME: nodeOffset is offset in content (same as node) but + // frameAndOffset.mFrame may be a frame for its descendant. Therefore, + // frameAndOffset.mOffsetInFrameContent should be used here. nodeOffset, mFrameSelection->GetHint() == CaretAssociationHint::After, &frameOffset, &childFrame); if (NS_FAILED(rv)) return nullptr; if (!childFrame) return nullptr; - frame = childFrame; + frameAndOffset.mFrame = childFrame; // Get the coordinates of the offset into the text frame. - rv = GetCachedFrameOffset(frame, nodeOffset, pt); + rv = GetCachedFrameOffset( + frameAndOffset.mFrame, + static_cast<int32_t>(frameAndOffset.mOffsetInFrameContent), pt); if (NS_FAILED(rv)) return nullptr; } @@ -3767,7 +3773,7 @@ nsIFrame* Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion, // The block position and size are set so as to fill the frame in that axis. // (i.e. block-position of 0, and block-size matching the frame's own block // size). - const WritingMode wm = frame->GetWritingMode(); + const WritingMode wm = frameAndOffset->GetWritingMode(); // Helper to determine the inline-axis position for the aRect outparam. auto GetInlinePosition = [&]() { if (isText) { @@ -3779,20 +3785,20 @@ nsIFrame* Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion, // inline-end edge (rather than physical end of inline axis)? (i.e. if we // have direction:rtl, maybe this code would want to return 0 instead of // height/width?) - return frame->ISize(wm); + return frameAndOffset->ISize(wm); }; // Set the inline position and block-size. Leave inline size and block // position set to 0, as discussed above. if (wm.IsVertical()) { aRect->y = GetInlinePosition(); - aRect->SetWidth(frame->BSize(wm)); + aRect->SetWidth(frameAndOffset->BSize(wm)); } else { aRect->x = GetInlinePosition(); - aRect->SetHeight(frame->BSize(wm)); + aRect->SetHeight(frameAndOffset->BSize(wm)); } - return frame; + return frameAndOffset; } NS_IMETHODIMP @@ -4229,7 +4235,7 @@ void Selection::Modify(const nsAString& aAlter, const nsAString& aDirection, // we may have to swap the direction of movement. const PrimaryFrameData frameForFocus = GetPrimaryFrameForCaretAtFocusNode(visual); - if (frameForFocus.mFrame) { + if (frameForFocus) { if (visual) { // FYI: This was done during a call of GetPrimaryFrameForCaretAtFocusNode. // Therefore, this may not be intended by the original author. diff --git a/dom/events/ContentEventHandler.cpp b/dom/events/ContentEventHandler.cpp @@ -1129,18 +1129,18 @@ nsresult ContentEventHandler::ExpandToClusterBoundary( MOZ_DIAGNOSTIC_ASSERT(mDocument->GetPresShell()); CaretAssociationHint hint = aForward ? CaretAssociationHint::Before : CaretAssociationHint::After; - nsIFrame* frame = SelectionMovementUtils::GetFrameForNodeOffset( + FrameAndOffset frameAndOffset = SelectionMovementUtils::GetFrameForNodeOffset( &aTextNode, int32_t(*aXPOffset), hint); - if (frame) { - auto [startOffset, endOffset] = frame->GetOffsets(); + if (frameAndOffset) { + auto [startOffset, endOffset] = frameAndOffset->GetOffsets(); if (*aXPOffset == static_cast<uint32_t>(startOffset) || *aXPOffset == static_cast<uint32_t>(endOffset)) { return NS_OK; } - if (!frame->IsTextFrame()) { + if (!frameAndOffset->IsTextFrame()) { return NS_ERROR_FAILURE; } - nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame); + nsTextFrame* textFrame = static_cast<nsTextFrame*>(frameAndOffset.mFrame); int32_t newOffsetInFrame = *aXPOffset - startOffset; newOffsetInFrame += aForward ? -1 : 1; // PeekOffsetCharacter() should respect cluster but ignore user-select diff --git a/layout/base/AccessibleCaretManager.cpp b/layout/base/AccessibleCaretManager.cpp @@ -675,9 +675,11 @@ nsresult AccessibleCaretManager::SelectWordOrShortcut(const nsPoint& aPoint) { if (offsets.content) { RefPtr<nsFrameSelection> frameSelection = GetFrameSelection(); if (frameSelection) { - nsIFrame* theFrame = SelectionMovementUtils::GetFrameForNodeOffset( - offsets.content, offsets.offset, offsets.associate); - if (theFrame && theFrame != ptFrame) { + const FrameAndOffset textFrameAndOffsetContainingWordBoundary = + SelectionMovementUtils::GetFrameForNodeOffset( + offsets.content, offsets.offset, offsets.associate); + if (textFrameAndOffsetContainingWordBoundary && + textFrameAndOffsetContainingWordBoundary != ptFrame) { SetSelectionDragState(true); frameSelection->HandleClick( MOZ_KnownLive(offsets.content) /* bug 1636889 */, @@ -1221,7 +1223,7 @@ bool AccessibleCaretManager::RestrictCaretDraggingOffsets( : GetLastVisibleLeafFrameOrUnselectableChildFrame( *GetSelection()->GetLastRange(), getter_AddRefs(content), &offsetInContent); - if (!frameAndOffset.mFrame) { + if (!frameAndOffset) { return false; } @@ -1248,7 +1250,7 @@ bool AccessibleCaretManager::RestrictCaretDraggingOffsets( eSelectCluster, dir, static_cast<int32_t>(frameAndOffset.mOffsetInFrameContent), nsPoint(0, 0), {PeekOffsetOption::JumpLines, PeekOffsetOption::StopAtScroller}); - nsresult rv = frameAndOffset.mFrame->PeekOffset(&limit); + nsresult rv = frameAndOffset->PeekOffset(&limit); if (NS_FAILED(rv)) { limit.mResultContent = content; limit.mContentOffset = offsetInContent; diff --git a/layout/base/CaretAssociationHint.cpp b/layout/base/CaretAssociationHint.cpp @@ -57,7 +57,7 @@ CaretAssociationHint ComputeCaretAssociationHint( return CaretAssociationHint::After; } } - return frameData.mFrame ? frameData.mHint : aDefault; + return frameData ? frameData.mHint : aDefault; } } // namespace mozilla diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp @@ -9304,7 +9304,7 @@ static bool LineHasNonEmptyContent(nsLineBox* aLine) { } /* static */ -bool nsLayoutUtils::IsInvisibleBreak(nsINode* aNode, +bool nsLayoutUtils::IsInvisibleBreak(const nsINode* aNode, nsIFrame** aNextLineFrame) { if (aNextLineFrame) { *aNextLineFrame = nullptr; diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h @@ -3056,7 +3056,7 @@ class nsLayoutUtils { * assigned to first frame on the next line if such a next line exists, null * otherwise. */ - static bool IsInvisibleBreak(nsINode* aNode, + static bool IsInvisibleBreak(const nsINode* aNode, nsIFrame** aNextLineFrame = nullptr); static nsRect ComputeSVGOriginBox(mozilla::dom::SVGViewportElement*); diff --git a/layout/generic/SelectionMovementUtils.cpp b/layout/generic/SelectionMovementUtils.cpp @@ -97,7 +97,7 @@ SelectionMovementUtils::PeekOffsetForCaretMove( SelectionMovementUtils::GetPrimaryFrameForCaret( aContent, aOffset, aOptions.contains(PeekOffsetOption::Visual), aHint, aCaretBidiLevel); - if (!frameForFocus.mFrame) { + if (!frameForFocus) { return Err(NS_ERROR_FAILURE); } @@ -106,7 +106,7 @@ SelectionMovementUtils::PeekOffsetForCaretMove( aAmount, aDirection, static_cast<int32_t>(frameForFocus.mOffsetInFrameContent), aDesiredCaretPos, aOptions, eDefaultBehavior, aAncestorLimiter); - nsresult rv = frameForFocus.mFrame->PeekOffset(&pos); + nsresult rv = frameForFocus->PeekOffset(&pos); if (NS_FAILED(rv)) { return Err(rv); } @@ -118,33 +118,36 @@ nsPrevNextBidiLevels SelectionMovementUtils::GetPrevNextBidiLevels( nsIContent* aNode, uint32_t aContentOffset, CaretAssociationHint aHint, bool aJumpLines, const Element* aAncestorLimiter) { // Get the level of the frames on each side - nsIFrame* currentFrame; - uint32_t currentOffset; nsDirection direction; nsPrevNextBidiLevels levels{}; levels.SetData(nullptr, nullptr, intl::BidiEmbeddingLevel::LTR(), intl::BidiEmbeddingLevel::LTR()); - currentFrame = SelectionMovementUtils::GetFrameForNodeOffset( - aNode, aContentOffset, aHint, &currentOffset); - if (!currentFrame) { + FrameAndOffset currentFrameAndOffset = + SelectionMovementUtils::GetFrameForNodeOffset(aNode, aContentOffset, + aHint); + if (!currentFrameAndOffset) { return levels; } - auto [frameStart, frameEnd] = currentFrame->GetOffsets(); + auto [frameStart, frameEnd] = currentFrameAndOffset->GetOffsets(); if (0 == frameStart && 0 == frameEnd) { direction = eDirPrevious; - } else if (static_cast<uint32_t>(frameStart) == currentOffset) { + } else if (static_cast<uint32_t>(frameStart) == + currentFrameAndOffset.mOffsetInFrameContent) { direction = eDirPrevious; - } else if (static_cast<uint32_t>(frameEnd) == currentOffset) { + } else if (static_cast<uint32_t>(frameEnd) == + currentFrameAndOffset.mOffsetInFrameContent) { direction = eDirNext; } else { // we are neither at the beginning nor at the end of the frame, so we have // no worries - intl::BidiEmbeddingLevel currentLevel = currentFrame->GetEmbeddingLevel(); - levels.SetData(currentFrame, currentFrame, currentLevel, currentLevel); + intl::BidiEmbeddingLevel currentLevel = + currentFrameAndOffset->GetEmbeddingLevel(); + levels.SetData(currentFrameAndOffset.mFrame, currentFrameAndOffset.mFrame, + currentLevel, currentLevel); return levels; } @@ -152,12 +155,12 @@ nsPrevNextBidiLevels SelectionMovementUtils::GetPrevNextBidiLevels( if (aJumpLines) { peekOffsetOptions += PeekOffsetOption::JumpLines; } - nsIFrame* newFrame = currentFrame + nsIFrame* newFrame = currentFrameAndOffset ->GetFrameFromDirection(direction, peekOffsetOptions, aAncestorLimiter) .mFrame; - FrameBidiData currentBidi = currentFrame->GetBidiData(); + FrameBidiData currentBidi = currentFrameAndOffset->GetBidiData(); intl::BidiEmbeddingLevel currentLevel = currentBidi.embeddingLevel; intl::BidiEmbeddingLevel newLevel = newFrame ? newFrame->GetEmbeddingLevel() : currentBidi.baseLevel; @@ -166,8 +169,8 @@ nsPrevNextBidiLevels SelectionMovementUtils::GetPrevNextBidiLevels( // incorrectly. // XXX This could be removed once bug 339786 is fixed. if (!aJumpLines) { - if (currentFrame->IsBrFrame()) { - currentFrame = nullptr; + if (currentFrameAndOffset->IsBrFrame()) { + currentFrameAndOffset = {nullptr, 0u}; currentLevel = currentBidi.baseLevel; } if (newFrame && newFrame->IsBrFrame()) { @@ -177,9 +180,11 @@ nsPrevNextBidiLevels SelectionMovementUtils::GetPrevNextBidiLevels( } if (direction == eDirNext) { - levels.SetData(currentFrame, newFrame, currentLevel, newLevel); + levels.SetData(currentFrameAndOffset.mFrame, newFrame, currentLevel, + newLevel); } else { - levels.SetData(newFrame, currentFrame, newLevel, currentLevel); + levels.SetData(newFrame, currentFrameAndOffset.mFrame, newLevel, + currentLevel); } return levels; @@ -246,23 +251,22 @@ static bool IsDisplayContents(const nsIContent* aContent) { } // static -nsIFrame* SelectionMovementUtils::GetFrameForNodeOffset( - nsIContent* aNode, uint32_t aOffset, CaretAssociationHint aHint, - uint32_t* aReturnOffset /* = nullptr */) { +FrameAndOffset SelectionMovementUtils::GetFrameForNodeOffset( + const nsIContent* aNode, uint32_t aOffset, CaretAssociationHint aHint) { if (!aNode) { - return nullptr; + return {}; } if (static_cast<int32_t>(aOffset) < 0) { - return nullptr; + return {}; } if (!aNode->GetPrimaryFrame() && !IsDisplayContents(aNode)) { - return nullptr; + return {}; } nsIFrame *returnFrame = nullptr, *lastFrame = aNode->GetPrimaryFrame(); - nsCOMPtr<nsIContent> theNode; + const nsIContent* theNode = nullptr; uint32_t offsetInFrameContent, offsetInLastFrameContent = aOffset; while (true) { @@ -316,11 +320,10 @@ nsIFrame* SelectionMovementUtils::GetFrameForNodeOffset( aOffset = aOffset > childIndex ? theNode->GetChildCount() : 0; continue; } + // Check to see if theNode is a text node. If it is, translate // aOffset into an offset into the text node. - - RefPtr<Text> textNode = theNode->GetAsText(); - if (textNode) { + if (const Text* textNode = Text::FromNode(theNode)) { if (theNode->GetPrimaryFrame()) { if (aOffset > childIndex) { uint32_t textLength = textNode->Length(); @@ -339,7 +342,7 @@ nsIFrame* SelectionMovementUtils::GetFrameForNodeOffset( nsCOMPtr<nsIContent> newChildNode = aNode->GetChildAt_Deprecated(newChildIndex); if (!newChildNode) { - return nullptr; + return {}; } aNode = newChildNode; @@ -357,32 +360,36 @@ nsIFrame* SelectionMovementUtils::GetFrameForNodeOffset( // If the node is a ShadowRoot, the frame needs to be adjusted, // because a ShadowRoot does not get a frame. Its children are rendered // as children of the host. - if (ShadowRoot* shadow = ShadowRoot::FromNode(theNode)) { + if (const ShadowRoot* shadow = ShadowRoot::FromNode(theNode)) { theNode = shadow->GetHost(); } returnFrame = theNode->GetPrimaryFrame(); - if (!returnFrame) { - if (aHint == CaretAssociationHint::Before) { - if (aOffset > 0) { - --aOffset; - continue; - } - break; - } - if (aOffset < theNode->GetChildCount()) { - ++aOffset; + if (returnFrame) { + // FIXME: offsetInFrameContent has not been updated for theNode yet when + // theNode is different from aNode. E.g., if a child at aNode and aOffset + // is an <img>, theNode is now the <img> but offsetInFrameContent is the + // offset for aNode. + break; + } + + if (aHint == CaretAssociationHint::Before) { + if (aOffset > 0) { + --aOffset; continue; } break; } - + if (aOffset < theNode->GetChildCount()) { + ++aOffset; + continue; + } break; } // end while if (!returnFrame) { if (!lastFrame) { - return nullptr; + return {}; } returnFrame = lastFrame; offsetInFrameContent = offsetInLastFrameContent; @@ -405,10 +412,7 @@ nsIFrame* SelectionMovementUtils::GetFrameForNodeOffset( returnFrame->GetChildFrameContainingOffset( static_cast<int32_t>(offsetInFrameContent), aHint == CaretAssociationHint::After, &unused, &returnFrame); - if (aReturnOffset) { - *aReturnOffset = offsetInFrameContent; - } - return returnFrame; + return {returnFrame, offsetInFrameContent}; } // static @@ -755,12 +759,14 @@ CaretFrameData SelectionMovementUtils::GetCaretFrameForNodeOffset( MOZ_ASSERT_IF(aForceEditableRegion == ForceEditableRegion::Yes, aContentNode->IsEditable()); - result.mFrame = result.mUnadjustedFrame = - SelectionMovementUtils::GetFrameForNodeOffset( - aContentNode, aOffset, aFrameHint, &result.mOffsetInFrameContent); - if (!result.mFrame) { + const FrameAndOffset frameAndOffset = + SelectionMovementUtils::GetFrameForNodeOffset(aContentNode, aOffset, + aFrameHint); + if (!frameAndOffset) { return {}; } + result.mFrame = result.mUnadjustedFrame = frameAndOffset.mFrame; + result.mOffsetInFrameContent = frameAndOffset.mOffsetInFrameContent; if (SelectionMovementUtils::AdjustFrameForLineStart( result.mFrame, result.mOffsetInFrameContent)) { @@ -779,14 +785,14 @@ CaretFrameData SelectionMovementUtils::GetCaretFrameForNodeOffset( // // Direction Style from visibility->mDirection // ------------------ - if (!result.mFrame->PresContext()->BidiEnabled()) { + if (!result->PresContext()->BidiEnabled()) { return result; } // If there has been a reflow, take the caret Bidi level to be the level of // the current frame if (aBidiLevel & BIDI_LEVEL_UNDEFINED) { - aBidiLevel = result.mFrame->GetEmbeddingLevel(); + aBidiLevel = result->GetEmbeddingLevel(); } nsIFrame* frameBefore; @@ -796,7 +802,7 @@ CaretFrameData SelectionMovementUtils::GetCaretFrameForNodeOffset( intl::BidiEmbeddingLevel levelAfter; // Bidi level of the character after the caret - auto [start, end] = result.mFrame->GetOffsets(); + auto [start, end] = result->GetOffsets(); if (start == 0 || end == 0 || static_cast<uint32_t>(start) == result.mOffsetInFrameContent || static_cast<uint32_t>(end) == result.mOffsetInFrameContent) { @@ -829,7 +835,7 @@ CaretFrameData SelectionMovementUtils::GetCaretFrameForNodeOffset( if (result.mFrame != frameBefore) { if (frameBefore) { // if there is a frameBefore, move into it result.mFrame = frameBefore; - std::tie(start, end) = result.mFrame->GetOffsets(); + std::tie(start, end) = result->GetOffsets(); result.mOffsetInFrameContent = end; } else { // if there is no frameBefore, we must be at the beginning of @@ -860,7 +866,7 @@ CaretFrameData SelectionMovementUtils::GetCaretFrameForNodeOffset( if (frameAfter) { // if there is a frameAfter, move into it result.mFrame = frameAfter; - std::tie(start, end) = result.mFrame->GetOffsets(); + std::tie(start, end) = result->GetOffsets(); result.mOffsetInFrameContent = start; } else { // if there is no frameAfter, we must be at the end of the line @@ -895,8 +901,8 @@ CaretFrameData SelectionMovementUtils::GetCaretFrameForNodeOffset( aBidiLevel); if (MOZ_LIKELY(frameOrError.isOk())) { result.mFrame = frameOrError.unwrap(); - std::tie(start, end) = result.mFrame->GetOffsets(); - levelAfter = result.mFrame->GetEmbeddingLevel(); + std::tie(start, end) = result->GetOffsets(); + levelAfter = result->GetEmbeddingLevel(); if (aBidiLevel.IsRTL()) { // c8: caret to the right of the rightmost character result.mOffsetInFrameContent = levelAfter.IsRTL() ? start : end; @@ -919,8 +925,8 @@ CaretFrameData SelectionMovementUtils::GetCaretFrameForNodeOffset( frameBefore, eDirPrevious, aBidiLevel); if (MOZ_LIKELY(frameOrError.isOk())) { result.mFrame = frameOrError.unwrap(); - std::tie(start, end) = result.mFrame->GetOffsets(); - levelBefore = result.mFrame->GetEmbeddingLevel(); + std::tie(start, end) = result->GetOffsets(); + levelBefore = result->GetEmbeddingLevel(); if (aBidiLevel.IsRTL()) { // c12: caret to the left of the leftmost character result.mOffsetInFrameContent = levelBefore.IsRTL() ? end : start; @@ -947,7 +953,7 @@ PrimaryFrameData SelectionMovementUtils::GetPrimaryFrameForCaret( const PrimaryFrameData result = SelectionMovementUtils::GetPrimaryOrCaretFrameForNodeOffset( aContent, aOffset, aVisual, aHint, aCaretBidiLevel); - if (result.mFrame) { + if (result) { return result; } } @@ -981,13 +987,12 @@ PrimaryFrameData SelectionMovementUtils::GetPrimaryOrCaretFrameForNodeOffset( nullptr, aContent, aOffset, aHint, aCaretBidiLevel, aContent && aContent->IsEditable() ? ForceEditableRegion::Yes : ForceEditableRegion::No); - return {{result.mFrame, result.mOffsetInFrameContent}, result.mHint}; + return result; } - uint32_t offset = 0; - nsIFrame* theFrame = SelectionMovementUtils::GetFrameForNodeOffset( - aContent, aOffset, aHint, &offset); - return {{theFrame, offset}, aHint}; + return { + SelectionMovementUtils::GetFrameForNodeOffset(aContent, aOffset, aHint), + aHint}; } } // namespace mozilla diff --git a/layout/generic/SelectionMovementUtils.h b/layout/generic/SelectionMovementUtils.h @@ -30,6 +30,16 @@ struct MOZ_STACK_CLASS FrameAndOffset { return mFrame ? mFrame->GetContent() : nullptr; } + operator nsIFrame*() const { return mFrame; } + + explicit operator bool() const { return !!mFrame; } + [[nodiscard]] bool operator!() const { return !mFrame; } + + nsIFrame* operator->() const { + MOZ_ASSERT(mFrame); + return mFrame; + } + nsIFrame* mFrame = nullptr; // The offset in mFrame->GetContent(). uint32_t mOffsetInFrameContent = 0; @@ -87,13 +97,11 @@ class SelectionMovementUtils final { * that frame. * * @param aNode input parameter for the node to look at - * TODO: Make this `const nsIContent*` for `ContentEventHandler`. * @param aOffset offset into above node. - * @param aReturnOffset will contain offset into frame. */ - static nsIFrame* GetFrameForNodeOffset(nsIContent* aNode, uint32_t aOffset, - CaretAssociationHint aHint, - uint32_t* aReturnOffset = nullptr); + static FrameAndOffset GetFrameForNodeOffset(const nsIContent* aNode, + uint32_t aOffset, + CaretAssociationHint aHint); /** * Return the first visible point in or at a leaf node in aRange or the first diff --git a/layout/generic/nsFrameSelection.cpp b/layout/generic/nsFrameSelection.cpp @@ -803,7 +803,7 @@ nsresult nsFrameSelection::MoveCaret(nsDirection aDirection, Caret::IsVisualMovement(aExtendSelection, aMovementStyle); const PrimaryFrameData frameForFocus = sel->GetPrimaryFrameForCaretAtFocusNode(visualMovement); - if (!frameForFocus.mFrame) { + if (!frameForFocus) { return NS_ERROR_FAILURE; } if (visualMovement) { @@ -1104,8 +1104,7 @@ void nsFrameSelection::BidiLevelFromMove(PresShell* aPresShell, void nsFrameSelection::BidiLevelFromClick(nsIContent* aNode, uint32_t aContentOffset) { - nsIFrame* clickInFrame = nullptr; - clickInFrame = SelectionMovementUtils::GetFrameForNodeOffset( + nsIFrame* clickInFrame = SelectionMovementUtils::GetFrameForNodeOffset( aNode, aContentOffset, mCaret.mHint); if (!clickInFrame) { return; @@ -1184,30 +1183,33 @@ void nsFrameSelection::MaintainedRange::AdjustContentOffsets( amount = eSelectEndLine; } - uint32_t offset; - nsIFrame* frame = SelectionMovementUtils::GetFrameForNodeOffset( - aOffsets.content, aOffsets.offset, CaretAssociationHint::After, - &offset); + FrameAndOffset frameAndOffset = + SelectionMovementUtils::GetFrameForNodeOffset( + aOffsets.content, aOffsets.offset, CaretAssociationHint::After); PeekOffsetOptions peekOffsetOptions{}; if (aStopAtScroller == StopAtScroller::Yes) { peekOffsetOptions += PeekOffsetOption::StopAtScroller; } - if (frame && amount == eSelectWord && direction == eDirPrevious) { + if (frameAndOffset && amount == eSelectWord && direction == eDirPrevious) { // To avoid selecting the previous word when at start of word, // first move one character forward. - PeekOffsetStruct charPos(eSelectCharacter, eDirNext, - static_cast<int32_t>(offset), nsPoint(0, 0), - peekOffsetOptions); - if (NS_SUCCEEDED(frame->PeekOffset(&charPos))) { - frame = charPos.mResultFrame; - offset = charPos.mContentOffset; + PeekOffsetStruct charPos( + eSelectCharacter, eDirNext, + static_cast<int32_t>(frameAndOffset.mOffsetInFrameContent), + nsPoint(0, 0), peekOffsetOptions); + if (NS_SUCCEEDED(frameAndOffset->PeekOffset(&charPos))) { + frameAndOffset = {charPos.mResultFrame, + static_cast<uint32_t>(charPos.mContentOffset)}; } } - PeekOffsetStruct pos(amount, direction, static_cast<int32_t>(offset), - nsPoint(0, 0), peekOffsetOptions); - if (frame && NS_SUCCEEDED(frame->PeekOffset(&pos)) && pos.mResultContent) { + PeekOffsetStruct pos( + amount, direction, + static_cast<int32_t>(frameAndOffset.mOffsetInFrameContent), + nsPoint(0, 0), peekOffsetOptions); + if (frameAndOffset && NS_SUCCEEDED(frameAndOffset->PeekOffset(&pos)) && + pos.mResultContent) { aOffsets.content = pos.mResultContent; aOffsets.offset = pos.mContentOffset; } @@ -1982,20 +1984,20 @@ nsresult nsFrameSelection::PhysicalMove(int16_t aDirection, int16_t aAmount, WritingMode wm; const PrimaryFrameData frameForFocus = sel->GetPrimaryFrameForCaretAtFocusNode(true); - if (frameForFocus.mFrame) { + if (frameForFocus) { // FYI: Setting the caret association hint was done during a call of // GetPrimaryFrameForCaretAtFocusNode. Therefore, this may not be intended // by the original author. sel->GetFrameSelection()->SetHint(frameForFocus.mHint); - if (!frameForFocus.mFrame->Style()->IsTextCombined()) { - wm = frameForFocus.mFrame->GetWritingMode(); + if (!frameForFocus->Style()->IsTextCombined()) { + wm = frameForFocus->GetWritingMode(); } else { // Using different direction for horizontal-in-vertical would // make it hard to navigate via keyboard. Inherit the moving // direction from its parent. - MOZ_ASSERT(frameForFocus.mFrame->IsTextFrame()); - wm = frameForFocus.mFrame->GetParent()->GetWritingMode(); + MOZ_ASSERT(frameForFocus->IsTextFrame()); + wm = frameForFocus->GetParent()->GetWritingMode(); MOZ_ASSERT(wm.IsVertical(), "Text combined " "can only appear in vertical text"); diff --git a/layout/generic/nsIFrame.cpp b/layout/generic/nsIFrame.cpp @@ -5338,14 +5338,14 @@ nsresult nsIFrame::SelectByTypeAtPoint(const nsPoint& aPoint, return NS_ERROR_FAILURE; } - uint32_t offset; - nsIFrame* frame = SelectionMovementUtils::GetFrameForNodeOffset( - offsets.content, offsets.offset, offsets.associate, &offset); - if (!frame) { + FrameAndOffset frameAndOffset = SelectionMovementUtils::GetFrameForNodeOffset( + offsets.content, offsets.offset, offsets.associate); + if (!frameAndOffset) { return NS_ERROR_FAILURE; } - return frame->PeekBackwardAndForwardForSelection( - aBeginAmountType, aEndAmountType, static_cast<int32_t>(offset), + return frameAndOffset->PeekBackwardAndForwardForSelection( + aBeginAmountType, aEndAmountType, + static_cast<int32_t>(frameAndOffset.mOffsetInFrameContent), aBeginAmountType != eSelectWord, aSelectFlags); }