diff options
Diffstat (limited to 'layout/generic/SelectionMovementUtils.cpp')
-rw-r--r-- | layout/generic/SelectionMovementUtils.cpp | 682 |
1 files changed, 682 insertions, 0 deletions
diff --git a/layout/generic/SelectionMovementUtils.cpp b/layout/generic/SelectionMovementUtils.cpp new file mode 100644 index 0000000000..a71da2c205 --- /dev/null +++ b/layout/generic/SelectionMovementUtils.cpp @@ -0,0 +1,682 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ErrorList.h" +#include "SelectionMovementUtils.h" +#include "mozilla/CaretAssociationHint.h" +#include "mozilla/Maybe.h" +#include "mozilla/PresShell.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/dom/ShadowRoot.h" +#include "mozilla/intl/BidiEmbeddingLevel.h" +#include "nsBidiPresUtils.h" +#include "nsBlockFrame.h" +#include "nsCaret.h" +#include "nsCOMPtr.h" +#include "nsFrameSelection.h" +#include "nsFrameTraversal.h" +#include "nsIContent.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.h" +#include "nsLayoutUtils.h" +#include "nsPresContext.h" +#include "nsTextFrame.h" + +namespace mozilla { +using namespace dom; +// FYI: This was done during a call of GetPrimaryFrameForCaretAtFocusNode. +// Therefore, this may not be intended by the original author. + +// static +Result<PeekOffsetStruct, nsresult> +SelectionMovementUtils::PeekOffsetForCaretMove( + nsIContent* aContent, uint32_t aOffset, nsDirection aDirection, + CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel, + const nsSelectionAmount aAmount, const nsPoint& aDesiredCaretPos, + PeekOffsetOptions aOptions) { + const PrimaryFrameData frameForFocus = + SelectionMovementUtils::GetPrimaryFrameForCaret( + aContent, aOffset, aOptions.contains(PeekOffsetOption::Visual), aHint, + aCaretBidiLevel); + if (!frameForFocus.mFrame) { + return Err(NS_ERROR_FAILURE); + } + + aOptions += {PeekOffsetOption::JumpLines, PeekOffsetOption::IsKeyboardSelect}; + PeekOffsetStruct pos( + aAmount, aDirection, + static_cast<int32_t>(frameForFocus.mOffsetInFrameContent), + aDesiredCaretPos, aOptions); + nsresult rv = frameForFocus.mFrame->PeekOffset(&pos); + if (NS_FAILED(rv)) { + return Err(rv); + } + return pos; +} + +// static +nsPrevNextBidiLevels SelectionMovementUtils::GetPrevNextBidiLevels( + nsIContent* aNode, uint32_t aContentOffset, CaretAssociationHint aHint, + bool aJumpLines) { + // 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, ¤tOffset); + if (!currentFrame) { + return levels; + } + + auto [frameStart, frameEnd] = currentFrame->GetOffsets(); + + if (0 == frameStart && 0 == frameEnd) { + direction = eDirPrevious; + } else if (static_cast<uint32_t>(frameStart) == currentOffset) { + direction = eDirPrevious; + } else if (static_cast<uint32_t>(frameEnd) == currentOffset) { + 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); + return levels; + } + + PeekOffsetOptions peekOffsetOptions{PeekOffsetOption::StopAtScroller}; + if (aJumpLines) { + peekOffsetOptions += PeekOffsetOption::JumpLines; + } + nsIFrame* newFrame = + currentFrame->GetFrameFromDirection(direction, peekOffsetOptions).mFrame; + + FrameBidiData currentBidi = currentFrame->GetBidiData(); + intl::BidiEmbeddingLevel currentLevel = currentBidi.embeddingLevel; + intl::BidiEmbeddingLevel newLevel = + newFrame ? newFrame->GetEmbeddingLevel() : currentBidi.baseLevel; + + // If not jumping lines, disregard br frames, since they might be positioned + // incorrectly. + // XXX This could be removed once bug 339786 is fixed. + if (!aJumpLines) { + if (currentFrame->IsBrFrame()) { + currentFrame = nullptr; + currentLevel = currentBidi.baseLevel; + } + if (newFrame && newFrame->IsBrFrame()) { + newFrame = nullptr; + newLevel = currentBidi.baseLevel; + } + } + + if (direction == eDirNext) { + levels.SetData(currentFrame, newFrame, currentLevel, newLevel); + } else { + levels.SetData(newFrame, currentFrame, newLevel, currentLevel); + } + + return levels; +} + +// static +Result<nsIFrame*, nsresult> SelectionMovementUtils::GetFrameFromLevel( + nsIFrame* aFrameIn, nsDirection aDirection, + intl::BidiEmbeddingLevel aBidiLevel) { + if (!aFrameIn) { + return Err(NS_ERROR_NULL_POINTER); + } + + intl::BidiEmbeddingLevel foundLevel = intl::BidiEmbeddingLevel::LTR(); + + nsFrameIterator frameIterator(aFrameIn->PresContext(), aFrameIn, + nsFrameIterator::Type::Leaf, + false, // aVisual + false, // aLockInScrollView + false, // aFollowOOFs + false // aSkipPopupChecks + ); + + nsIFrame* foundFrame = aFrameIn; + nsIFrame* theFrame = nullptr; + do { + theFrame = foundFrame; + foundFrame = frameIterator.Traverse(aDirection == eDirNext); + if (!foundFrame) { + return Err(NS_ERROR_FAILURE); + } + foundLevel = foundFrame->GetEmbeddingLevel(); + + } while (foundLevel > aBidiLevel); + + MOZ_ASSERT(theFrame); + return theFrame; +} + +bool SelectionMovementUtils::AdjustFrameForLineStart(nsIFrame*& aFrame, + uint32_t& aFrameOffset) { + if (!aFrame->HasSignificantTerminalNewline()) { + return false; + } + + auto [start, end] = aFrame->GetOffsets(); + if (aFrameOffset != static_cast<uint32_t>(end)) { + return false; + } + + nsIFrame* nextSibling = aFrame->GetNextSibling(); + if (!nextSibling) { + return false; + } + + aFrame = nextSibling; + std::tie(start, end) = aFrame->GetOffsets(); + aFrameOffset = start; + return true; +} + +static bool IsDisplayContents(const nsIContent* aContent) { + return aContent->IsElement() && aContent->AsElement()->IsDisplayContents(); +} + +// static +nsIFrame* SelectionMovementUtils::GetFrameForNodeOffset( + nsIContent* aNode, uint32_t aOffset, CaretAssociationHint aHint, + uint32_t* aReturnOffset /* = nullptr */) { + if (!aNode) { + return nullptr; + } + + if (static_cast<int32_t>(aOffset) < 0) { + return nullptr; + } + + if (!aNode->GetPrimaryFrame() && !IsDisplayContents(aNode)) { + return nullptr; + } + + nsIFrame* returnFrame = nullptr; + nsCOMPtr<nsIContent> theNode; + uint32_t offsetInFrameContent; + + while (true) { + offsetInFrameContent = aOffset; + + theNode = aNode; + + if (aNode->IsElement()) { + uint32_t childIndex = 0; + uint32_t numChildren = theNode->GetChildCount(); + + if (aHint == CaretAssociationHint::Before) { + if (aOffset > 0) { + childIndex = aOffset - 1; + } else { + childIndex = aOffset; + } + } else { + MOZ_ASSERT(aHint == CaretAssociationHint::After); + if (aOffset >= numChildren) { + if (numChildren > 0) { + childIndex = numChildren - 1; + } else { + childIndex = 0; + } + } else { + childIndex = aOffset; + } + } + + if (childIndex > 0 || numChildren > 0) { + nsCOMPtr<nsIContent> childNode = + theNode->GetChildAt_Deprecated(childIndex); + + if (!childNode) { + break; + } + + theNode = childNode; + } + + // Now that we have the child node, check if it too + // can contain children. If so, descend into child. + if (theNode->IsElement() && theNode->GetChildCount() && + !theNode->HasIndependentSelection()) { + aNode = theNode; + 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 (theNode->GetPrimaryFrame()) { + if (aOffset > childIndex) { + uint32_t textLength = textNode->Length(); + + offsetInFrameContent = textLength; + } else { + offsetInFrameContent = 0; + } + } else { + uint32_t numChildren = aNode->GetChildCount(); + uint32_t newChildIndex = aHint == CaretAssociationHint::Before + ? childIndex - 1 + : childIndex + 1; + + if (newChildIndex < numChildren) { + nsCOMPtr<nsIContent> newChildNode = + aNode->GetChildAt_Deprecated(newChildIndex); + if (!newChildNode) { + return nullptr; + } + + aNode = newChildNode; + aOffset = aHint == CaretAssociationHint::Before + ? aNode->GetChildCount() + : 0; + continue; + } // newChildIndex is illegal which means we're at first or last + // child. Just use original node to get the frame. + theNode = aNode; + } + } + } + + // 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)) { + theNode = shadow->GetHost(); + } + + returnFrame = theNode->GetPrimaryFrame(); + if (!returnFrame) { + if (aHint == CaretAssociationHint::Before) { + if (aOffset > 0) { + --aOffset; + continue; + } + break; + } + if (aOffset < theNode->GetChildCount()) { + ++aOffset; + continue; + } + break; + } + + break; + } // end while + + if (!returnFrame) { + return nullptr; + } + + // If we ended up here and were asked to position the caret after a visible + // break, let's return the frame on the next line instead if it exists. + if (aOffset > 0 && (uint32_t)aOffset >= aNode->Length() && + theNode == aNode->GetLastChild()) { + nsIFrame* newFrame; + nsLayoutUtils::IsInvisibleBreak(theNode, &newFrame); + if (newFrame) { + returnFrame = newFrame; + offsetInFrameContent = 0; + } + } + + // find the child frame containing the offset we want + int32_t unused = 0; + returnFrame->GetChildFrameContainingOffset( + static_cast<int32_t>(offsetInFrameContent), + aHint == CaretAssociationHint::After, &unused, &returnFrame); + if (aReturnOffset) { + *aReturnOffset = offsetInFrameContent; + } + return returnFrame; +} + +/** + * Find the first frame in an in-order traversal of the frame subtree rooted + * at aFrame which is either a text frame logically at the end of a line, + * or which is aStopAtFrame. Return null if no such frame is found. We don't + * descend into the children of non-eLineParticipant frames. + */ +static nsIFrame* CheckForTrailingTextFrameRecursive(nsIFrame* aFrame, + nsIFrame* aStopAtFrame) { + if (aFrame == aStopAtFrame || + ((aFrame->IsTextFrame() && + (static_cast<nsTextFrame*>(aFrame))->IsAtEndOfLine()))) { + return aFrame; + } + if (!aFrame->IsLineParticipant()) { + return nullptr; + } + + for (nsIFrame* f : aFrame->PrincipalChildList()) { + if (nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame)) { + return r; + } + } + return nullptr; +} + +static nsLineBox* FindContainingLine(nsIFrame* aFrame) { + while (aFrame && aFrame->IsLineParticipant()) { + nsIFrame* parent = aFrame->GetParent(); + nsBlockFrame* blockParent = do_QueryFrame(parent); + if (blockParent) { + bool isValid; + nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid); + return isValid ? iter.GetLine().get() : nullptr; + } + aFrame = parent; + } + return nullptr; +} + +static void AdjustCaretFrameForLineEnd(nsIFrame** aFrame, uint32_t* aOffset, + bool aEditableOnly) { + nsLineBox* line = FindContainingLine(*aFrame); + if (!line) { + return; + } + uint32_t count = line->GetChildCount(); + for (nsIFrame* f = line->mFirstChild; count > 0; + --count, f = f->GetNextSibling()) { + nsIFrame* r = CheckForTrailingTextFrameRecursive(f, *aFrame); + if (r == *aFrame) { + return; + } + if (!r) { + continue; + } + // If found text frame is non-editable but the start frame content is + // editable, we don't want to put caret into the non-editable text node. + // We should return the given frame as-is in this case. + if (aEditableOnly && !r->GetContent()->IsEditable()) { + return; + } + // We found our frame, but we may not be able to properly paint the caret + // if -moz-user-modify differs from our actual frame. + MOZ_ASSERT(r->IsTextFrame(), "Expected text frame"); + *aFrame = r; + *aOffset = (static_cast<nsTextFrame*>(r))->GetContentEnd(); + return; + // FYI: Setting the caret association hint was done during a call of + // GetPrimaryFrameForCaretAtFocusNode. Therefore, this may not be intended + // by the original author. + } +} + +CaretFrameData SelectionMovementUtils::GetCaretFrameForNodeOffset( + const nsFrameSelection* aFrameSelection, nsIContent* aContentNode, + uint32_t aOffset, CaretAssociationHint aFrameHint, + intl::BidiEmbeddingLevel aBidiLevel, + ForceEditableRegion aForceEditableRegion) { + if (!aContentNode || !aContentNode->IsInComposedDoc()) { + return {}; + } + + CaretFrameData result; + result.mHint = aFrameHint; + if (aFrameSelection) { + PresShell* presShell = aFrameSelection->GetPresShell(); + if (!presShell) { + return {}; + } + + if (!aContentNode || !aContentNode->IsInComposedDoc() || + presShell->GetDocument() != aContentNode->GetComposedDoc()) { + return {}; + } + + result.mHint = aFrameSelection->GetHint(); + } + + MOZ_ASSERT_IF(aForceEditableRegion == ForceEditableRegion::Yes, + aContentNode->IsEditable()); + + result.mFrame = result.mUnadjustedFrame = + SelectionMovementUtils::GetFrameForNodeOffset( + aContentNode, aOffset, aFrameHint, &result.mOffsetInFrameContent); + if (!result.mFrame) { + return {}; + } + + if (SelectionMovementUtils::AdjustFrameForLineStart( + result.mFrame, result.mOffsetInFrameContent)) { + result.mHint = CaretAssociationHint::After; + } else { + // if the frame is after a text frame that's logically at the end of the + // line (e.g. if the frame is a <br> frame), then put the caret at the end + // of that text frame instead. This way, the caret will be positioned as if + // trailing whitespace was not trimmed. + AdjustCaretFrameForLineEnd( + &result.mFrame, &result.mOffsetInFrameContent, + aForceEditableRegion == ForceEditableRegion::Yes); + } + + // Mamdouh : modification of the caret to work at rtl and ltr with Bidi + // + // Direction Style from visibility->mDirection + // ------------------ + if (!result.mFrame->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(); + } + + nsIFrame* frameBefore; + nsIFrame* frameAfter; + intl::BidiEmbeddingLevel + levelBefore; // Bidi level of the character before the caret + intl::BidiEmbeddingLevel + levelAfter; // Bidi level of the character after the caret + + auto [start, end] = result.mFrame->GetOffsets(); + if (start == 0 || end == 0 || + static_cast<uint32_t>(start) == result.mOffsetInFrameContent || + static_cast<uint32_t>(end) == result.mOffsetInFrameContent) { + nsPrevNextBidiLevels levels = SelectionMovementUtils::GetPrevNextBidiLevels( + aContentNode, aOffset, result.mHint, false); + + /* Boundary condition, we need to know the Bidi levels of the characters + * before and after the caret */ + if (levels.mFrameBefore || levels.mFrameAfter) { + frameBefore = levels.mFrameBefore; + frameAfter = levels.mFrameAfter; + levelBefore = levels.mLevelBefore; + levelAfter = levels.mLevelAfter; + + if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore)) { + aBidiLevel = + std::max(aBidiLevel, std::min(levelBefore, levelAfter)); // rule c3 + aBidiLevel = + std::min(aBidiLevel, std::max(levelBefore, levelAfter)); // rule c4 + if (aBidiLevel == levelBefore || // rule c1 + (aBidiLevel > levelBefore && aBidiLevel < levelAfter && + aBidiLevel.IsSameDirection(levelBefore)) || // rule c5 + (aBidiLevel < levelBefore && aBidiLevel > levelAfter && + aBidiLevel.IsSameDirection(levelBefore))) // rule c9 + { + if (result.mFrame != frameBefore) { + if (frameBefore) { // if there is a frameBefore, move into it + result.mFrame = frameBefore; + std::tie(start, end) = result.mFrame->GetOffsets(); + result.mOffsetInFrameContent = end; + } else { + // if there is no frameBefore, we must be at the beginning of + // the line so we stay with the current frame. Exception: when + // the first frame on the line has a different Bidi level from + // the paragraph level, there is no real frame for the caret to + // be in. We have to find the visually first frame on the line. + intl::BidiEmbeddingLevel baseLevel = frameAfter->GetBaseLevel(); + if (baseLevel != levelAfter) { + PeekOffsetStruct pos(eSelectBeginLine, eDirPrevious, 0, + nsPoint(0, 0), + {PeekOffsetOption::StopAtScroller, + PeekOffsetOption::Visual}); + if (NS_SUCCEEDED(frameAfter->PeekOffset(&pos))) { + result.mFrame = pos.mResultFrame; + result.mOffsetInFrameContent = pos.mContentOffset; + } + } + } + } + } else if (aBidiLevel == levelAfter || // rule c2 + (aBidiLevel > levelBefore && aBidiLevel < levelAfter && + aBidiLevel.IsSameDirection(levelAfter)) || // rule c6 + (aBidiLevel < levelBefore && aBidiLevel > levelAfter && + aBidiLevel.IsSameDirection(levelAfter))) // rule c10 + { + if (result.mFrame != frameAfter) { + if (frameAfter) { + // if there is a frameAfter, move into it + result.mFrame = frameAfter; + std::tie(start, end) = result.mFrame->GetOffsets(); + result.mOffsetInFrameContent = start; + } else { + // if there is no frameAfter, we must be at the end of the line + // so we stay with the current frame. + // Exception: when the last frame on the line has a different + // Bidi level from the paragraph level, there is no real frame + // for the caret to be in. We have to find the visually last + // frame on the line. + intl::BidiEmbeddingLevel baseLevel = frameBefore->GetBaseLevel(); + if (baseLevel != levelBefore) { + PeekOffsetStruct pos(eSelectEndLine, eDirNext, 0, nsPoint(0, 0), + {PeekOffsetOption::StopAtScroller, + PeekOffsetOption::Visual}); + if (NS_SUCCEEDED(frameBefore->PeekOffset(&pos))) { + result.mFrame = pos.mResultFrame; + result.mOffsetInFrameContent = pos.mContentOffset; + } + } + } + } + } else if (aBidiLevel > levelBefore && + aBidiLevel < levelAfter && // rule c7/8 + // before and after have the same parity + levelBefore.IsSameDirection(levelAfter) && + // caret has different parity + !aBidiLevel.IsSameDirection(levelAfter)) { + MOZ_ASSERT_IF(aFrameSelection && aFrameSelection->GetPresShell(), + aFrameSelection->GetPresShell()->GetPresContext() == + frameAfter->PresContext()); + Result<nsIFrame*, nsresult> frameOrError = + SelectionMovementUtils::GetFrameFromLevel(frameAfter, eDirNext, + aBidiLevel); + if (MOZ_LIKELY(frameOrError.isOk())) { + result.mFrame = frameOrError.unwrap(); + std::tie(start, end) = result.mFrame->GetOffsets(); + levelAfter = result.mFrame->GetEmbeddingLevel(); + if (aBidiLevel.IsRTL()) { + // c8: caret to the right of the rightmost character + result.mOffsetInFrameContent = levelAfter.IsRTL() ? start : end; + } else { + // c7: caret to the left of the leftmost character + result.mOffsetInFrameContent = levelAfter.IsRTL() ? end : start; + } + } + } else if (aBidiLevel < levelBefore && + aBidiLevel > levelAfter && // rule c11/12 + // before and after have the same parity + levelBefore.IsSameDirection(levelAfter) && + // caret has different parity + !aBidiLevel.IsSameDirection(levelAfter)) { + MOZ_ASSERT_IF(aFrameSelection && aFrameSelection->GetPresShell(), + aFrameSelection->GetPresShell()->GetPresContext() == + frameBefore->PresContext()); + Result<nsIFrame*, nsresult> frameOrError = + SelectionMovementUtils::GetFrameFromLevel( + frameBefore, eDirPrevious, aBidiLevel); + if (MOZ_LIKELY(frameOrError.isOk())) { + result.mFrame = frameOrError.unwrap(); + std::tie(start, end) = result.mFrame->GetOffsets(); + levelBefore = result.mFrame->GetEmbeddingLevel(); + if (aBidiLevel.IsRTL()) { + // c12: caret to the left of the leftmost character + result.mOffsetInFrameContent = levelBefore.IsRTL() ? end : start; + } else { + // c11: caret to the right of the rightmost character + result.mOffsetInFrameContent = levelBefore.IsRTL() ? start : end; + } + } + } + } + } + } + + return result; +} + +// static +PrimaryFrameData SelectionMovementUtils::GetPrimaryFrameForCaret( + nsIContent* aContent, uint32_t aOffset, bool aVisual, + CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel) { + MOZ_ASSERT(aContent); + + { + const PrimaryFrameData result = + SelectionMovementUtils::GetPrimaryOrCaretFrameForNodeOffset( + aContent, aOffset, aVisual, aHint, aCaretBidiLevel); + if (result.mFrame) { + return result; + } + } + + // If aContent is whitespace only, we promote focus node to parent because + // whitespace only node might have no frame. + + if (!aContent->TextIsOnlyWhitespace()) { + return {}; + } + + nsIContent* parent = aContent->GetParent(); + if (NS_WARN_IF(!parent)) { + return {}; + } + const Maybe<uint32_t> offset = parent->ComputeIndexOf(aContent); + if (NS_WARN_IF(offset.isNothing())) { + return {}; + } + return SelectionMovementUtils::GetPrimaryOrCaretFrameForNodeOffset( + parent, *offset, aVisual, aHint, aCaretBidiLevel); +} + +// static +PrimaryFrameData SelectionMovementUtils::GetPrimaryOrCaretFrameForNodeOffset( + nsIContent* aContent, uint32_t aOffset, bool aVisual, + CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel) { + if (aVisual) { + const CaretFrameData result = + SelectionMovementUtils::GetCaretFrameForNodeOffset( + nullptr, aContent, aOffset, aHint, aCaretBidiLevel, + aContent && aContent->IsEditable() ? ForceEditableRegion::Yes + : ForceEditableRegion::No); + return {result.mFrame, result.mOffsetInFrameContent, result.mHint}; + } + + uint32_t offset = 0; + nsIFrame* theFrame = SelectionMovementUtils::GetFrameForNodeOffset( + aContent, aOffset, aHint, &offset); + return {theFrame, offset, aHint}; +} + +} // namespace mozilla |