forked from mirrors/gecko-dev
Bug 1816581 - part 6: Move the static methods used for moving caret or considering caret geometry into new utility class r=emilio
The methods are in `nsCaret`, `nsFrameSelection` and `dom::Selection`. That makes harder to read, and they are called each other, so, they are reused for different purpose. Therefore, it must be better to move them into a new class. Then, the name differences may become more unclear. If you think so, feel free to request renaming some methods of them. Differential Revision: https://phabricator.services.mozilla.com/D197288
This commit is contained in:
parent
20faadc9da
commit
cd53d5600e
13 changed files with 923 additions and 828 deletions
|
|
@ -35,6 +35,7 @@
|
||||||
#include "mozilla/HTMLEditor.h"
|
#include "mozilla/HTMLEditor.h"
|
||||||
#include "mozilla/IntegerRange.h"
|
#include "mozilla/IntegerRange.h"
|
||||||
#include "mozilla/PresShell.h"
|
#include "mozilla/PresShell.h"
|
||||||
|
#include "mozilla/SelectionMovementUtils.h"
|
||||||
#include "mozilla/dom/Element.h"
|
#include "mozilla/dom/Element.h"
|
||||||
#include "mozilla/dom/HTMLBRElement.h"
|
#include "mozilla/dom/HTMLBRElement.h"
|
||||||
#include "mozilla/dom/Selection.h"
|
#include "mozilla/dom/Selection.h"
|
||||||
|
|
@ -658,11 +659,10 @@ int32_t HyperTextAccessible::CaretLineNumber() {
|
||||||
nsIContent* caretContent = caretNode->AsContent();
|
nsIContent* caretContent = caretNode->AsContent();
|
||||||
if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent)) return -1;
|
if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent)) return -1;
|
||||||
|
|
||||||
int32_t returnOffsetUnused;
|
|
||||||
uint32_t caretOffset = domSel->FocusOffset();
|
uint32_t caretOffset = domSel->FocusOffset();
|
||||||
CaretAssociationHint hint = frameSelection->GetHint();
|
CaretAssociationHint hint = frameSelection->GetHint();
|
||||||
nsIFrame* caretFrame = frameSelection->GetFrameForNodeOffset(
|
nsIFrame* caretFrame = SelectionMovementUtils::GetFrameForNodeOffset(
|
||||||
caretContent, caretOffset, hint, &returnOffsetUnused);
|
caretContent, caretOffset, hint);
|
||||||
NS_ENSURE_TRUE(caretFrame, -1);
|
NS_ENSURE_TRUE(caretFrame, -1);
|
||||||
|
|
||||||
AutoAssertNoDomMutations guard; // The nsILineIterators below will break if
|
AutoAssertNoDomMutations guard; // The nsILineIterators below will break if
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
#include "mozilla/PresShell.h"
|
#include "mozilla/PresShell.h"
|
||||||
#include "mozilla/RangeBoundary.h"
|
#include "mozilla/RangeBoundary.h"
|
||||||
#include "mozilla/RangeUtils.h"
|
#include "mozilla/RangeUtils.h"
|
||||||
|
#include "mozilla/SelectionMovementUtils.h"
|
||||||
#include "mozilla/StackWalk.h"
|
#include "mozilla/StackWalk.h"
|
||||||
#include "mozilla/StaticPrefs_dom.h"
|
#include "mozilla/StaticPrefs_dom.h"
|
||||||
#include "mozilla/Telemetry.h"
|
#include "mozilla/Telemetry.h"
|
||||||
|
|
@ -1536,16 +1537,15 @@ nsresult Selection::StyledRanges::GetIndicesForInterval(
|
||||||
nsIFrame* Selection::GetPrimaryFrameForAnchorNode() const {
|
nsIFrame* Selection::GetPrimaryFrameForAnchorNode() const {
|
||||||
MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
|
MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
|
||||||
|
|
||||||
int32_t frameOffset = 0;
|
|
||||||
nsCOMPtr<nsIContent> content = do_QueryInterface(GetAnchorNode());
|
nsCOMPtr<nsIContent> content = do_QueryInterface(GetAnchorNode());
|
||||||
if (content && mFrameSelection) {
|
if (content && mFrameSelection) {
|
||||||
return nsFrameSelection::GetFrameForNodeOffset(
|
return SelectionMovementUtils::GetFrameForNodeOffset(
|
||||||
content, AnchorOffset(), mFrameSelection->GetHint(), &frameOffset);
|
content, AnchorOffset(), mFrameSelection->GetHint());
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Selection::PrimaryFrameData Selection::GetPrimaryFrameForCaretAtFocusNode(
|
PrimaryFrameData Selection::GetPrimaryFrameForCaretAtFocusNode(
|
||||||
bool aVisual) const {
|
bool aVisual) const {
|
||||||
nsIContent* content = nsIContent::FromNodeOrNull(GetFocusNode());
|
nsIContent* content = nsIContent::FromNodeOrNull(GetFocusNode());
|
||||||
if (!content || !mFrameSelection || !mFrameSelection->GetPresShell()) {
|
if (!content || !mFrameSelection || !mFrameSelection->GetPresShell()) {
|
||||||
|
|
@ -1558,61 +1558,8 @@ Selection::PrimaryFrameData Selection::GetPrimaryFrameForCaretAtFocusNode(
|
||||||
CaretAssociationHint hint = mFrameSelection->GetHint();
|
CaretAssociationHint hint = mFrameSelection->GetHint();
|
||||||
intl::BidiEmbeddingLevel caretBidiLevel =
|
intl::BidiEmbeddingLevel caretBidiLevel =
|
||||||
mFrameSelection->GetCaretBidiLevel();
|
mFrameSelection->GetCaretBidiLevel();
|
||||||
return Selection::GetPrimaryFrameForCaret(content, FocusOffset(), aVisual,
|
return SelectionMovementUtils::GetPrimaryFrameForCaret(
|
||||||
hint, caretBidiLevel);
|
content, FocusOffset(), aVisual, hint, caretBidiLevel);
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
Selection::PrimaryFrameData Selection::GetPrimaryFrameForCaret(
|
|
||||||
nsIContent* aContent, uint32_t aOffset, bool aVisual,
|
|
||||||
CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel) {
|
|
||||||
MOZ_ASSERT(aContent);
|
|
||||||
|
|
||||||
{
|
|
||||||
const PrimaryFrameData result = 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 GetPrimaryOrCaretFrameForNodeOffset(parent, *offset, aVisual, aHint,
|
|
||||||
aCaretBidiLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
Selection::PrimaryFrameData Selection::GetPrimaryOrCaretFrameForNodeOffset(
|
|
||||||
nsIContent* aContent, uint32_t aOffset, bool aVisual,
|
|
||||||
CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel) {
|
|
||||||
if (aVisual) {
|
|
||||||
const nsCaret::CaretFrameData result = nsCaret::GetCaretFrameForNodeOffset(
|
|
||||||
nullptr, aContent, static_cast<int32_t>(aOffset), aHint,
|
|
||||||
aCaretBidiLevel,
|
|
||||||
aContent && aContent->IsEditable() ? nsCaret::ForceEditableRegion::Yes
|
|
||||||
: nsCaret::ForceEditableRegion::No);
|
|
||||||
return {result.mFrame, static_cast<uint32_t>(result.mOffsetInFrameContent),
|
|
||||||
result.mHint};
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t offset = 0;
|
|
||||||
nsIFrame* theFrame = nsFrameSelection::GetFrameForNodeOffset(
|
|
||||||
aContent, static_cast<int32_t>(aOffset), aHint, &offset);
|
|
||||||
return {theFrame, static_cast<uint32_t>(offset), aHint};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Selection::SelectFramesOf(nsIContent* aContent, bool aSelected) const {
|
void Selection::SelectFramesOf(nsIContent* aContent, bool aSelected) const {
|
||||||
|
|
@ -3235,12 +3182,12 @@ nsIFrame* Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion,
|
||||||
|
|
||||||
nsCOMPtr<nsIContent> content = do_QueryInterface(node);
|
nsCOMPtr<nsIContent> content = do_QueryInterface(node);
|
||||||
NS_ENSURE_TRUE(content.get(), nullptr);
|
NS_ENSURE_TRUE(content.get(), nullptr);
|
||||||
int32_t frameOffset = 0;
|
uint32_t frameOffset = 0;
|
||||||
frame = nsFrameSelection::GetFrameForNodeOffset(
|
frame = SelectionMovementUtils::GetFrameForNodeOffset(
|
||||||
content, nodeOffset, mFrameSelection->GetHint(), &frameOffset);
|
content, nodeOffset, mFrameSelection->GetHint(), &frameOffset);
|
||||||
if (!frame) return nullptr;
|
if (!frame) return nullptr;
|
||||||
|
|
||||||
nsFrameSelection::AdjustFrameForLineStart(frame, frameOffset);
|
SelectionMovementUtils::AdjustFrameForLineStart(frame, frameOffset);
|
||||||
|
|
||||||
// Figure out what node type we have, then get the
|
// Figure out what node type we have, then get the
|
||||||
// appropriate rect for its nodeOffset.
|
// appropriate rect for its nodeOffset.
|
||||||
|
|
@ -3249,7 +3196,7 @@ nsIFrame* Selection::GetSelectionEndPointGeometry(SelectionRegion aRegion,
|
||||||
nsPoint pt(0, 0);
|
nsPoint pt(0, 0);
|
||||||
if (isText) {
|
if (isText) {
|
||||||
nsIFrame* childFrame = nullptr;
|
nsIFrame* childFrame = nullptr;
|
||||||
frameOffset = 0;
|
int32_t frameOffset = 0;
|
||||||
nsresult rv = frame->GetChildFrameContainingOffset(
|
nsresult rv = frame->GetChildFrameContainingOffset(
|
||||||
nodeOffset, mFrameSelection->GetHint() == CaretAssociationHint::After,
|
nodeOffset, mFrameSelection->GetHint() == CaretAssociationHint::After,
|
||||||
&frameOffset, &childFrame);
|
&frameOffset, &childFrame);
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,6 @@
|
||||||
#include "mozilla/WeakPtr.h"
|
#include "mozilla/WeakPtr.h"
|
||||||
#include "mozilla/dom/Highlight.h"
|
#include "mozilla/dom/Highlight.h"
|
||||||
#include "mozilla/dom/StyledRange.h"
|
#include "mozilla/dom/StyledRange.h"
|
||||||
#include "mozilla/intl/Bidi.h"
|
|
||||||
#include "mozilla/intl/BidiEmbeddingLevel.h"
|
|
||||||
#include "nsDirection.h"
|
#include "nsDirection.h"
|
||||||
#include "nsISelectionController.h"
|
#include "nsISelectionController.h"
|
||||||
#include "nsISelectionListener.h"
|
#include "nsISelectionListener.h"
|
||||||
|
|
@ -48,6 +46,7 @@ class PostContentIterator;
|
||||||
enum class CaretAssociationHint;
|
enum class CaretAssociationHint;
|
||||||
enum class TableSelectionMode : uint32_t;
|
enum class TableSelectionMode : uint32_t;
|
||||||
struct AutoPrepareFocusRange;
|
struct AutoPrepareFocusRange;
|
||||||
|
struct PrimaryFrameData;
|
||||||
namespace dom {
|
namespace dom {
|
||||||
class DocGroup;
|
class DocGroup;
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
|
|
@ -258,25 +257,6 @@ class Selection final : public nsSupportsWeakReference,
|
||||||
|
|
||||||
nsIFrame* GetPrimaryFrameForAnchorNode() const;
|
nsIFrame* GetPrimaryFrameForAnchorNode() const;
|
||||||
|
|
||||||
struct MOZ_STACK_CLASS PrimaryFrameData final {
|
|
||||||
// The frame which should be used to layout the caret.
|
|
||||||
nsIFrame* mFrame = nullptr;
|
|
||||||
// The offset in content of mFrame. This is valid only when mFrame is not
|
|
||||||
// nullptr.
|
|
||||||
uint32_t mOffsetInFrameContent = 0;
|
|
||||||
// Whether the caret should be put before or after the point. This is valid
|
|
||||||
// only when mFrame is not nullptr.
|
|
||||||
CaretAssociationHint mHint{0}; // Before
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get primary frame and some other data for putting caret or extending
|
|
||||||
* selection at the point.
|
|
||||||
*/
|
|
||||||
static PrimaryFrameData GetPrimaryFrameForCaret(
|
|
||||||
nsIContent* aContent, uint32_t aOffset, bool aVisual,
|
|
||||||
CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get primary frame and some other data for putting caret or extending
|
* Get primary frame and some other data for putting caret or extending
|
||||||
* selection at the focus point.
|
* selection at the focus point.
|
||||||
|
|
@ -751,13 +731,6 @@ class Selection final : public nsSupportsWeakReference,
|
||||||
Document* aDocument,
|
Document* aDocument,
|
||||||
ErrorResult&);
|
ErrorResult&);
|
||||||
|
|
||||||
// This is helper method for GetPrimaryFrameForCaret.
|
|
||||||
// If aVisual is true, this returns caret frame.
|
|
||||||
// If false, this returns primary frame.
|
|
||||||
static PrimaryFrameData GetPrimaryOrCaretFrameForNodeOffset(
|
|
||||||
nsIContent* aContent, uint32_t aOffset, bool aVisual,
|
|
||||||
CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel);
|
|
||||||
|
|
||||||
// Get the cached value for nsTextFrame::GetPointFromOffset.
|
// Get the cached value for nsTextFrame::GetPointFromOffset.
|
||||||
nsresult GetCachedFrameOffset(nsIFrame* aFrame, int32_t inOffset,
|
nsresult GetCachedFrameOffset(nsIFrame* aFrame, int32_t inOffset,
|
||||||
nsPoint& aPoint);
|
nsPoint& aPoint);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
#include "mozilla/PresShell.h"
|
#include "mozilla/PresShell.h"
|
||||||
#include "mozilla/RangeBoundary.h"
|
#include "mozilla/RangeBoundary.h"
|
||||||
#include "mozilla/RangeUtils.h"
|
#include "mozilla/RangeUtils.h"
|
||||||
|
#include "mozilla/SelectionMovementUtils.h"
|
||||||
#include "mozilla/TextComposition.h"
|
#include "mozilla/TextComposition.h"
|
||||||
#include "mozilla/TextEditor.h"
|
#include "mozilla/TextEditor.h"
|
||||||
#include "mozilla/TextEvents.h"
|
#include "mozilla/TextEvents.h"
|
||||||
|
|
@ -1119,11 +1120,10 @@ nsresult ContentEventHandler::ExpandToClusterBoundary(
|
||||||
NS_ASSERTION(*aXPOffset <= aTextNode.TextLength(), "offset is out of range.");
|
NS_ASSERTION(*aXPOffset <= aTextNode.TextLength(), "offset is out of range.");
|
||||||
|
|
||||||
MOZ_DIAGNOSTIC_ASSERT(mDocument->GetPresShell());
|
MOZ_DIAGNOSTIC_ASSERT(mDocument->GetPresShell());
|
||||||
int32_t offsetInFrame;
|
|
||||||
CaretAssociationHint hint =
|
CaretAssociationHint hint =
|
||||||
aForward ? CaretAssociationHint::Before : CaretAssociationHint::After;
|
aForward ? CaretAssociationHint::Before : CaretAssociationHint::After;
|
||||||
nsIFrame* frame = nsFrameSelection::GetFrameForNodeOffset(
|
nsIFrame* frame = SelectionMovementUtils::GetFrameForNodeOffset(
|
||||||
&aTextNode, int32_t(*aXPOffset), hint, &offsetInFrame);
|
&aTextNode, int32_t(*aXPOffset), hint);
|
||||||
if (frame) {
|
if (frame) {
|
||||||
auto [startOffset, endOffset] = frame->GetOffsets();
|
auto [startOffset, endOffset] = frame->GetOffsets();
|
||||||
if (*aXPOffset == static_cast<uint32_t>(startOffset) ||
|
if (*aXPOffset == static_cast<uint32_t>(startOffset) ||
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
#include "mozilla/IMEStateManager.h"
|
#include "mozilla/IMEStateManager.h"
|
||||||
#include "mozilla/IntegerPrintfMacros.h"
|
#include "mozilla/IntegerPrintfMacros.h"
|
||||||
#include "mozilla/PresShell.h"
|
#include "mozilla/PresShell.h"
|
||||||
|
#include "mozilla/SelectionMovementUtils.h"
|
||||||
#include "mozilla/StaticAnalysisFunctions.h"
|
#include "mozilla/StaticAnalysisFunctions.h"
|
||||||
#include "mozilla/StaticPrefs_layout.h"
|
#include "mozilla/StaticPrefs_layout.h"
|
||||||
#include "nsCaret.h"
|
#include "nsCaret.h"
|
||||||
|
|
@ -656,9 +657,8 @@ nsresult AccessibleCaretManager::SelectWordOrShortcut(const nsPoint& aPoint) {
|
||||||
if (offsets.content) {
|
if (offsets.content) {
|
||||||
RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
|
RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
|
||||||
if (frameSelection) {
|
if (frameSelection) {
|
||||||
int32_t offset;
|
nsIFrame* theFrame = SelectionMovementUtils::GetFrameForNodeOffset(
|
||||||
nsIFrame* theFrame = nsFrameSelection::GetFrameForNodeOffset(
|
offsets.content, offsets.offset, offsets.associate);
|
||||||
offsets.content, offsets.offset, offsets.associate, &offset);
|
|
||||||
if (theFrame && theFrame != ptFrame) {
|
if (theFrame && theFrame != ptFrame) {
|
||||||
SetSelectionDragState(true);
|
SetSelectionDragState(true);
|
||||||
frameSelection->HandleClick(
|
frameSelection->HandleClick(
|
||||||
|
|
@ -1077,9 +1077,11 @@ nsIFrame* AccessibleCaretManager::GetFrameForFirstRangeStartOrLastRangeEnd(
|
||||||
hint = CaretAssociationHint::Before;
|
hint = CaretAssociationHint::Before;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsCOMPtr<nsIContent> startContent = do_QueryInterface(startNode);
|
nsCOMPtr<nsIContent> startContent = nsIContent::FromNodeOrNull(startNode);
|
||||||
nsIFrame* startFrame = nsFrameSelection::GetFrameForNodeOffset(
|
uint32_t outOffset = 0;
|
||||||
startContent, nodeOffset, hint, aOutOffset);
|
nsIFrame* startFrame = SelectionMovementUtils::GetFrameForNodeOffset(
|
||||||
|
startContent, nodeOffset, hint, &outOffset);
|
||||||
|
*aOutOffset = static_cast<int32_t>(outOffset);
|
||||||
|
|
||||||
if (!startFrame) {
|
if (!startFrame) {
|
||||||
ErrorResult err;
|
ErrorResult err;
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
#include "mozilla/dom/Selection.h"
|
#include "mozilla/dom/Selection.h"
|
||||||
#include "nsIBidiKeyboard.h"
|
#include "nsIBidiKeyboard.h"
|
||||||
#include "nsContentUtils.h"
|
#include "nsContentUtils.h"
|
||||||
|
#include "SelectionMovementUtils.h"
|
||||||
|
|
||||||
using namespace mozilla;
|
using namespace mozilla;
|
||||||
using namespace mozilla::dom;
|
using namespace mozilla::dom;
|
||||||
|
|
@ -49,76 +50,6 @@ using BidiEmbeddingLevel = mozilla::intl::BidiEmbeddingLevel;
|
||||||
// like an insignificant dot
|
// like an insignificant dot
|
||||||
static const int32_t kMinBidiIndicatorPixels = 2;
|
static const int32_t kMinBidiIndicatorPixels = 2;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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, int32_t* aOffset,
|
|
||||||
bool aEditableOnly) {
|
|
||||||
nsLineBox* line = FindContainingLine(*aFrame);
|
|
||||||
if (!line) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int32_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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nsCaret::nsCaret()
|
nsCaret::nsCaret()
|
||||||
: mOverrideOffset(0),
|
: mOverrideOffset(0),
|
||||||
mBlinkCount(-1),
|
mBlinkCount(-1),
|
||||||
|
|
@ -431,9 +362,10 @@ nsIFrame* nsCaret::GetFrameAndOffset(const Selection* aSelection,
|
||||||
nsIContent* contentNode = focusNode->AsContent();
|
nsIContent* contentNode = focusNode->AsContent();
|
||||||
nsFrameSelection* frameSelection = aSelection->GetFrameSelection();
|
nsFrameSelection* frameSelection = aSelection->GetFrameSelection();
|
||||||
BidiEmbeddingLevel bidiLevel = frameSelection->GetCaretBidiLevel();
|
BidiEmbeddingLevel bidiLevel = frameSelection->GetCaretBidiLevel();
|
||||||
const nsCaret::CaretFrameData result = nsCaret::GetCaretFrameForNodeOffset(
|
const CaretFrameData result =
|
||||||
frameSelection, contentNode, focusOffset, frameSelection->GetHint(),
|
SelectionMovementUtils::GetCaretFrameForNodeOffset(
|
||||||
bidiLevel, ForceEditableRegion::No);
|
frameSelection, contentNode, focusOffset, frameSelection->GetHint(),
|
||||||
|
bidiLevel, ForceEditableRegion::No);
|
||||||
// FIXME: It's odd to update nsFrameSelection within this method which is
|
// FIXME: It's odd to update nsFrameSelection within this method which is
|
||||||
// named as a getter.
|
// named as a getter.
|
||||||
if (result.mFrame) {
|
if (result.mFrame) {
|
||||||
|
|
@ -702,210 +634,6 @@ void nsCaret::StopBlinking() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nsCaret::CaretFrameData nsCaret::GetCaretFrameForNodeOffset(
|
|
||||||
const nsFrameSelection* aFrameSelection, nsIContent* aContentNode,
|
|
||||||
int32_t aOffset, CaretAssociationHint aFrameHint,
|
|
||||||
BidiEmbeddingLevel aBidiLevel, ForceEditableRegion aForceEditableRegion) {
|
|
||||||
if (!aContentNode || !aContentNode->IsInComposedDoc()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
CaretFrameData result;
|
|
||||||
result.mHint = aFrameHint;
|
|
||||||
if (aFrameSelection) {
|
|
||||||
PresShell* presShell = aFrameSelection->GetPresShell();
|
|
||||||
if (!presShell) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (presShell->GetDocument() != aContentNode->GetComposedDoc()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
result.mHint = aFrameSelection->GetHint();
|
|
||||||
}
|
|
||||||
|
|
||||||
MOZ_ASSERT_IF(aForceEditableRegion == ForceEditableRegion::Yes,
|
|
||||||
aContentNode->IsEditable());
|
|
||||||
|
|
||||||
result.mFrame = result.mUnadjustedFrame =
|
|
||||||
nsFrameSelection::GetFrameForNodeOffset(aContentNode, aOffset, aFrameHint,
|
|
||||||
&result.mOffsetInFrameContent);
|
|
||||||
if (!result.mFrame) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nsFrameSelection::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()) {
|
|
||||||
// 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;
|
|
||||||
BidiEmbeddingLevel
|
|
||||||
levelBefore; // Bidi level of the character before the caret
|
|
||||||
BidiEmbeddingLevel
|
|
||||||
levelAfter; // Bidi level of the character after the caret
|
|
||||||
|
|
||||||
auto [start, end] = result.mFrame->GetOffsets();
|
|
||||||
if (start == 0 || end == 0 || start == result.mOffsetInFrameContent ||
|
|
||||||
end == result.mOffsetInFrameContent) {
|
|
||||||
nsPrevNextBidiLevels levels = nsFrameSelection::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.
|
|
||||||
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.
|
|
||||||
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 =
|
|
||||||
nsFrameSelection::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 =
|
|
||||||
nsFrameSelection::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;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
|
size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
|
||||||
size_t total = aMallocSizeOf(this);
|
size_t total = aMallocSizeOf(this);
|
||||||
if (mPresShell) {
|
if (mPresShell) {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@
|
||||||
#ifndef nsCaret_h__
|
#ifndef nsCaret_h__
|
||||||
#define nsCaret_h__
|
#define nsCaret_h__
|
||||||
|
|
||||||
#include "mozilla/intl/BidiEmbeddingLevel.h"
|
|
||||||
#include "mozilla/MemoryReporting.h"
|
#include "mozilla/MemoryReporting.h"
|
||||||
#include "mozilla/dom/Selection.h"
|
#include "mozilla/dom/Selection.h"
|
||||||
#include "nsCoord.h"
|
#include "nsCoord.h"
|
||||||
|
|
@ -179,42 +178,6 @@ class nsCaret final : public nsISelectionListener {
|
||||||
static nsIFrame* GetGeometry(const mozilla::dom::Selection* aSelection,
|
static nsIFrame* GetGeometry(const mozilla::dom::Selection* aSelection,
|
||||||
nsRect* aRect);
|
nsRect* aRect);
|
||||||
|
|
||||||
enum class ForceEditableRegion { No, Yes };
|
|
||||||
|
|
||||||
struct MOZ_STACK_CLASS CaretFrameData final {
|
|
||||||
// The frame which should be used to compute caret geometry when caret is
|
|
||||||
// put at a specific DOM point.
|
|
||||||
nsIFrame* mFrame = nullptr;
|
|
||||||
// The frame which is found only from a DOM point. This frame becomes
|
|
||||||
// different from mFrame when the point is around end of a line or
|
|
||||||
// at a bidi text boundary.
|
|
||||||
nsIFrame* mUnadjustedFrame = nullptr;
|
|
||||||
// The content offset when caret is in the content of mFrame. This is
|
|
||||||
// valid only when mFrame is not nullptr.
|
|
||||||
int32_t mOffsetInFrameContent = 0;
|
|
||||||
// Whether the caret should be put before or after the point.
|
|
||||||
CaretAssociationHint mHint{0}; // Before
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a frame for considering caret geometry.
|
|
||||||
*
|
|
||||||
* @param aFrameSelection [optional] If this is specified and selection in
|
|
||||||
* aContent is not managed by the specified
|
|
||||||
* instance, return nullptr.
|
|
||||||
* @param aContentNode The content node where selection is collapsed.
|
|
||||||
* @param aOffset Collapsed position in aContentNode
|
|
||||||
* @param aFrameHint Caret association hint.
|
|
||||||
* @param aBidiLevel
|
|
||||||
* @param aForceEditableRegion Whether selection should be limited in
|
|
||||||
* editable region or not.
|
|
||||||
*/
|
|
||||||
static CaretFrameData GetCaretFrameForNodeOffset(
|
|
||||||
const nsFrameSelection* aFrameSelection, nsIContent* aContentNode,
|
|
||||||
int32_t aOffset, CaretAssociationHint aFrameHint,
|
|
||||||
mozilla::intl::BidiEmbeddingLevel aBidiLevel,
|
|
||||||
ForceEditableRegion aForceEditableRegion);
|
|
||||||
|
|
||||||
static nsRect GetGeometryForFrame(nsIFrame* aFrame, int32_t aFrameOffset,
|
static nsRect GetGeometryForFrame(nsIFrame* aFrame, int32_t aFrameOffset,
|
||||||
nscoord* aBidiIndicatorSize);
|
nscoord* aBidiIndicatorSize);
|
||||||
|
|
||||||
|
|
|
||||||
682
layout/generic/SelectionMovementUtils.cpp
Normal file
682
layout/generic/SelectionMovementUtils.cpp
Normal file
|
|
@ -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
|
||||||
177
layout/generic/SelectionMovementUtils.h
Normal file
177
layout/generic/SelectionMovementUtils.h
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
/* -*- 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/. */
|
||||||
|
|
||||||
|
#ifndef mozilla_SelectionMovementUtils_h
|
||||||
|
#define mozilla_SelectionMovementUtils_h
|
||||||
|
|
||||||
|
#include "mozilla/intl/BidiEmbeddingLevel.h"
|
||||||
|
#include "mozilla/Attributes.h"
|
||||||
|
#include "mozilla/EnumSet.h"
|
||||||
|
#include "mozilla/Result.h"
|
||||||
|
#include "nsIFrame.h"
|
||||||
|
|
||||||
|
struct nsPrevNextBidiLevels;
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
|
||||||
|
class PresShell;
|
||||||
|
enum class PeekOffsetOption : uint16_t;
|
||||||
|
|
||||||
|
namespace intl {
|
||||||
|
class BidiEmbeddingLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MOZ_STACK_CLASS PrimaryFrameData {
|
||||||
|
// The frame which should be used to layout the caret.
|
||||||
|
nsIFrame* mFrame = nullptr;
|
||||||
|
// The offset in content of mFrame. This is valid only when mFrame is not
|
||||||
|
// nullptr.
|
||||||
|
uint32_t mOffsetInFrameContent = 0;
|
||||||
|
// Whether the caret should be put before or after the point. This is valid
|
||||||
|
// only when mFrame is not nullptr.
|
||||||
|
CaretAssociationHint mHint{0}; // Before
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MOZ_STACK_CLASS CaretFrameData : public PrimaryFrameData {
|
||||||
|
// The frame which is found only from a DOM point. This frame becomes
|
||||||
|
// different from mFrame when the point is around end of a line or
|
||||||
|
// at a bidi text boundary.
|
||||||
|
nsIFrame* mUnadjustedFrame = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ForceEditableRegion : bool { No, Yes };
|
||||||
|
|
||||||
|
class SelectionMovementUtils final {
|
||||||
|
public:
|
||||||
|
using PeekOffsetOptions = EnumSet<PeekOffsetOption>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a node and its child offset, return the nsIFrame and the offset into
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetPrevNextBidiLevels will return the frames and associated Bidi levels of
|
||||||
|
* the characters logically before and after a (collapsed) selection.
|
||||||
|
*
|
||||||
|
* @param aNode is the node containing the selection
|
||||||
|
* @param aContentOffset is the offset of the selection in the node
|
||||||
|
* @param aJumpLines
|
||||||
|
* If true, look across line boundaries.
|
||||||
|
* If false, behave as if there were base-level frames at line edges.
|
||||||
|
*
|
||||||
|
* @return A struct holding the before/after frame and the before/after
|
||||||
|
* level.
|
||||||
|
*
|
||||||
|
* At the beginning and end of each line there is assumed to be a frame with
|
||||||
|
* Bidi level equal to the paragraph embedding level.
|
||||||
|
*
|
||||||
|
* In these cases the before frame and after frame respectively will be
|
||||||
|
* nullptr.
|
||||||
|
*/
|
||||||
|
static nsPrevNextBidiLevels GetPrevNextBidiLevels(nsIContent* aNode,
|
||||||
|
uint32_t aContentOffset,
|
||||||
|
CaretAssociationHint aHint,
|
||||||
|
bool aJumpLines);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PeekOffsetForCaretMove() only peek offset for caret move from the specified
|
||||||
|
* point of the normal selection. I.e., won't change selection ranges nor
|
||||||
|
* bidi information.
|
||||||
|
*/
|
||||||
|
static Result<PeekOffsetStruct, nsresult> PeekOffsetForCaretMove(
|
||||||
|
nsIContent* aContent, uint32_t aOffset, nsDirection aDirection,
|
||||||
|
CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel,
|
||||||
|
const nsSelectionAmount aAmount, const nsPoint& aDesiredCaretPos,
|
||||||
|
PeekOffsetOptions aOptions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Result<bool, nsresult> 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 Err(NS_ERROR_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a frame for considering caret geometry.
|
||||||
|
*
|
||||||
|
* @param aFrameSelection [optional] If this is specified and selection in
|
||||||
|
* aContent is not managed by the specified
|
||||||
|
* instance, return nullptr.
|
||||||
|
* @param aContentNode The content node where selection is collapsed.
|
||||||
|
* @param aOffset Collapsed position in aContentNode
|
||||||
|
* @param aFrameHint Caret association hint.
|
||||||
|
* @param aBidiLevel
|
||||||
|
* @param aForceEditableRegion Whether selection should be limited in
|
||||||
|
* editable region or not.
|
||||||
|
*/
|
||||||
|
static CaretFrameData GetCaretFrameForNodeOffset(
|
||||||
|
const nsFrameSelection* aFrameSelection, nsIContent* aContentNode,
|
||||||
|
uint32_t aOffset, CaretAssociationHint aFrameHint,
|
||||||
|
intl::BidiEmbeddingLevel aBidiLevel,
|
||||||
|
ForceEditableRegion aForceEditableRegion);
|
||||||
|
|
||||||
|
static bool AdjustFrameForLineStart(nsIFrame*& aFrame,
|
||||||
|
uint32_t& aFrameOffset);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get primary frame and some other data for putting caret or extending
|
||||||
|
* selection at the point.
|
||||||
|
*/
|
||||||
|
static PrimaryFrameData GetPrimaryFrameForCaret(
|
||||||
|
nsIContent* aContent, uint32_t aOffset, bool aVisual,
|
||||||
|
CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* GetFrameFromLevel will scan in a given direction
|
||||||
|
* until it finds a frame with a Bidi level less than or equal to a given
|
||||||
|
* level. It will return the last frame before this.
|
||||||
|
*
|
||||||
|
* @param aPresContext is the context to use
|
||||||
|
* @param aFrameIn is the frame to start from
|
||||||
|
* @param aDirection is the direction to scan
|
||||||
|
* @param aBidiLevel is the level to search for
|
||||||
|
*/
|
||||||
|
static Result<nsIFrame*, nsresult> GetFrameFromLevel(
|
||||||
|
nsIFrame* aFrameIn, nsDirection aDirection,
|
||||||
|
intl::BidiEmbeddingLevel aBidiLevel);
|
||||||
|
|
||||||
|
// This is helper method for GetPrimaryFrameForCaret.
|
||||||
|
// If aVisual is true, this returns caret frame.
|
||||||
|
// If false, this returns primary frame.
|
||||||
|
static PrimaryFrameData GetPrimaryOrCaretFrameForNodeOffset(
|
||||||
|
nsIContent* aContent, uint32_t aOffset, bool aVisual,
|
||||||
|
CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mozilla
|
||||||
|
|
||||||
|
#endif // #ifndef mozilla_SelectionMovementUtils_h
|
||||||
|
|
@ -153,6 +153,7 @@ EXPORTS.mozilla += [
|
||||||
"ScrollPositionUpdate.h",
|
"ScrollPositionUpdate.h",
|
||||||
"ScrollSnapInfo.h",
|
"ScrollSnapInfo.h",
|
||||||
"ScrollSnapTargetId.h",
|
"ScrollSnapTargetId.h",
|
||||||
|
"SelectionMovementUtils.h",
|
||||||
"ViewportFrame.h",
|
"ViewportFrame.h",
|
||||||
"WritingModes.h",
|
"WritingModes.h",
|
||||||
]
|
]
|
||||||
|
|
@ -227,6 +228,7 @@ UNIFIED_SOURCES += [
|
||||||
"ScrollSnap.cpp",
|
"ScrollSnap.cpp",
|
||||||
"ScrollSnapInfo.cpp",
|
"ScrollSnapInfo.cpp",
|
||||||
"ScrollVelocityQueue.cpp",
|
"ScrollVelocityQueue.cpp",
|
||||||
|
"SelectionMovementUtils.cpp",
|
||||||
"StickyScrollContainer.cpp",
|
"StickyScrollContainer.cpp",
|
||||||
"ViewportFrame.cpp",
|
"ViewportFrame.cpp",
|
||||||
"WBRFrame.cpp",
|
"WBRFrame.cpp",
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,8 @@
|
||||||
#include "nsFocusManager.h"
|
#include "nsFocusManager.h"
|
||||||
#include "nsPIDOMWindow.h"
|
#include "nsPIDOMWindow.h"
|
||||||
|
|
||||||
|
#include "SelectionMovementUtils.h"
|
||||||
|
|
||||||
using namespace mozilla;
|
using namespace mozilla;
|
||||||
using namespace mozilla::dom;
|
using namespace mozilla::dom;
|
||||||
|
|
||||||
|
|
@ -755,7 +757,7 @@ nsresult nsFrameSelection::MoveCaret(nsDirection aDirection,
|
||||||
|
|
||||||
bool visualMovement =
|
bool visualMovement =
|
||||||
mCaret.IsVisualMovement(aContinueSelection, aMovementStyle);
|
mCaret.IsVisualMovement(aContinueSelection, aMovementStyle);
|
||||||
const Selection::PrimaryFrameData frameForFocus =
|
const PrimaryFrameData frameForFocus =
|
||||||
sel->GetPrimaryFrameForCaretAtFocusNode(visualMovement);
|
sel->GetPrimaryFrameForCaretAtFocusNode(visualMovement);
|
||||||
if (!frameForFocus.mFrame) {
|
if (!frameForFocus.mFrame) {
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
|
|
@ -766,7 +768,8 @@ nsresult nsFrameSelection::MoveCaret(nsDirection aDirection,
|
||||||
SetHint(frameForFocus.mHint);
|
SetHint(frameForFocus.mHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<bool, nsresult> isIntraLineCaretMove = IsIntraLineCaretMove(aAmount);
|
Result<bool, nsresult> isIntraLineCaretMove =
|
||||||
|
SelectionMovementUtils::IsIntraLineCaretMove(aAmount);
|
||||||
nsDirection direction{aDirection};
|
nsDirection direction{aDirection};
|
||||||
if (isIntraLineCaretMove.isErr()) {
|
if (isIntraLineCaretMove.isErr()) {
|
||||||
return isIntraLineCaretMove.unwrapErr();
|
return isIntraLineCaretMove.unwrapErr();
|
||||||
|
|
@ -812,7 +815,7 @@ nsresult nsFrameSelection::MoveCaret(nsDirection aDirection,
|
||||||
if (result.isOk() && result.inspect().mResultContent) {
|
if (result.isOk() && result.inspect().mResultContent) {
|
||||||
const PeekOffsetStruct& pos = result.inspect();
|
const PeekOffsetStruct& pos = result.inspect();
|
||||||
nsIFrame* theFrame;
|
nsIFrame* theFrame;
|
||||||
int32_t currentOffset, frameStart, frameEnd;
|
int32_t frameStart, frameEnd;
|
||||||
|
|
||||||
if (aAmount <= eSelectWordNoSpace) {
|
if (aAmount <= eSelectWordNoSpace) {
|
||||||
// For left/right, PeekOffset() sets pos.mResultFrame correctly, but does
|
// For left/right, PeekOffset() sets pos.mResultFrame correctly, but does
|
||||||
|
|
@ -822,8 +825,7 @@ nsresult nsFrameSelection::MoveCaret(nsDirection aDirection,
|
||||||
// at the end of this frame, not at the beginning of the next one.
|
// at the end of this frame, not at the beginning of the next one.
|
||||||
theFrame = pos.mResultFrame;
|
theFrame = pos.mResultFrame;
|
||||||
std::tie(frameStart, frameEnd) = theFrame->GetOffsets();
|
std::tie(frameStart, frameEnd) = theFrame->GetOffsets();
|
||||||
currentOffset = pos.mContentOffset;
|
if (frameEnd == pos.mContentOffset && !(frameStart == 0 && frameEnd == 0))
|
||||||
if (frameEnd == currentOffset && !(frameStart == 0 && frameEnd == 0))
|
|
||||||
tHint = CaretAssociationHint::Before;
|
tHint = CaretAssociationHint::Before;
|
||||||
else
|
else
|
||||||
tHint = CaretAssociationHint::After;
|
tHint = CaretAssociationHint::After;
|
||||||
|
|
@ -832,8 +834,8 @@ nsresult nsFrameSelection::MoveCaret(nsDirection aDirection,
|
||||||
// or not at all. In these cases, get the frame based on the content and
|
// or not at all. In these cases, get the frame based on the content and
|
||||||
// hint returned by PeekOffset().
|
// hint returned by PeekOffset().
|
||||||
tHint = pos.mAttach;
|
tHint = pos.mAttach;
|
||||||
theFrame = GetFrameForNodeOffset(pos.mResultContent, pos.mContentOffset,
|
theFrame = SelectionMovementUtils::GetFrameForNodeOffset(
|
||||||
tHint, ¤tOffset);
|
pos.mResultContent, pos.mContentOffset, tHint);
|
||||||
if (!theFrame) return NS_ERROR_FAILURE;
|
if (!theFrame) return NS_ERROR_FAILURE;
|
||||||
|
|
||||||
std::tie(frameStart, frameEnd) = theFrame->GetOffsets();
|
std::tie(frameStart, frameEnd) = theFrame->GetOffsets();
|
||||||
|
|
@ -939,147 +941,15 @@ Result<PeekOffsetStruct, nsresult> nsFrameSelection::PeekOffsetForCaretMove(
|
||||||
options += PeekOffsetOption::ForceEditableRegion;
|
options += PeekOffsetOption::ForceEditableRegion;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nsFrameSelection::PeekOffsetForCaretMove(
|
return SelectionMovementUtils::PeekOffsetForCaretMove(
|
||||||
content, selection->FocusOffset(), aDirection, GetHint(),
|
content, selection->FocusOffset(), aDirection, GetHint(),
|
||||||
GetCaretBidiLevel(), aAmount, aDesiredCaretPos, options);
|
GetCaretBidiLevel(), aAmount, aDesiredCaretPos, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
|
||||||
mozilla::Result<mozilla::PeekOffsetStruct, nsresult>
|
|
||||||
nsFrameSelection::PeekOffsetForCaretMove(
|
|
||||||
nsIContent* aContent, uint32_t aOffset, nsDirection aDirection,
|
|
||||||
CaretAssociationHint aHint,
|
|
||||||
mozilla::intl::BidiEmbeddingLevel aCaretBidiLevel,
|
|
||||||
const nsSelectionAmount aAmount, const nsPoint& aDesiredCaretPos,
|
|
||||||
mozilla::PeekOffsetOptions aOptions) {
|
|
||||||
const Selection::PrimaryFrameData frameForFocus =
|
|
||||||
Selection::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;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsPrevNextBidiLevels nsFrameSelection::GetPrevNextBidiLevels(
|
nsPrevNextBidiLevels nsFrameSelection::GetPrevNextBidiLevels(
|
||||||
nsIContent* aNode, uint32_t aContentOffset, bool aJumpLines) const {
|
nsIContent* aNode, uint32_t aContentOffset, bool aJumpLines) const {
|
||||||
return GetPrevNextBidiLevels(aNode, aContentOffset, mCaret.mHint, aJumpLines);
|
return SelectionMovementUtils::GetPrevNextBidiLevels(
|
||||||
}
|
aNode, aContentOffset, mCaret.mHint, aJumpLines);
|
||||||
|
|
||||||
// static
|
|
||||||
nsPrevNextBidiLevels nsFrameSelection::GetPrevNextBidiLevels(
|
|
||||||
nsIContent* aNode, uint32_t aContentOffset, CaretAssociationHint aHint,
|
|
||||||
bool aJumpLines) {
|
|
||||||
// Get the level of the frames on each side
|
|
||||||
nsIFrame* currentFrame;
|
|
||||||
int32_t currentOffset;
|
|
||||||
nsDirection direction;
|
|
||||||
|
|
||||||
nsPrevNextBidiLevels levels{};
|
|
||||||
levels.SetData(nullptr, nullptr, mozilla::intl::BidiEmbeddingLevel::LTR(),
|
|
||||||
mozilla::intl::BidiEmbeddingLevel::LTR());
|
|
||||||
|
|
||||||
currentFrame = GetFrameForNodeOffset(
|
|
||||||
aNode, static_cast<int32_t>(aContentOffset), aHint, ¤tOffset);
|
|
||||||
if (!currentFrame) {
|
|
||||||
return levels;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto [frameStart, frameEnd] = currentFrame->GetOffsets();
|
|
||||||
|
|
||||||
if (0 == frameStart && 0 == frameEnd) {
|
|
||||||
direction = eDirPrevious;
|
|
||||||
} else if (frameStart == currentOffset) {
|
|
||||||
direction = eDirPrevious;
|
|
||||||
} else if (frameEnd == currentOffset) {
|
|
||||||
direction = eDirNext;
|
|
||||||
} else {
|
|
||||||
// we are neither at the beginning nor at the end of the frame, so we have
|
|
||||||
// no worries
|
|
||||||
mozilla::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();
|
|
||||||
mozilla::intl::BidiEmbeddingLevel currentLevel = currentBidi.embeddingLevel;
|
|
||||||
mozilla::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> nsFrameSelection::GetFrameFromLevel(
|
|
||||||
nsIFrame* aFrameIn, nsDirection aDirection,
|
|
||||||
mozilla::intl::BidiEmbeddingLevel aBidiLevel) {
|
|
||||||
if (!aFrameIn) {
|
|
||||||
return Err(NS_ERROR_NULL_POINTER);
|
|
||||||
}
|
|
||||||
|
|
||||||
mozilla::intl::BidiEmbeddingLevel foundLevel =
|
|
||||||
mozilla::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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount) {
|
nsresult nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount) {
|
||||||
|
|
@ -1109,7 +979,8 @@ void nsFrameSelection::BidiLevelFromMove(PresShell* aPresShell,
|
||||||
case eSelectEndLine:
|
case eSelectEndLine:
|
||||||
case eSelectNoAmount: {
|
case eSelectNoAmount: {
|
||||||
nsPrevNextBidiLevels levels =
|
nsPrevNextBidiLevels levels =
|
||||||
GetPrevNextBidiLevels(aNode, aContentOffset, aHint, false);
|
SelectionMovementUtils::GetPrevNextBidiLevels(aNode, aContentOffset,
|
||||||
|
aHint, false);
|
||||||
|
|
||||||
SetCaretBidiLevelAndMaybeSchedulePaint(
|
SetCaretBidiLevelAndMaybeSchedulePaint(
|
||||||
aHint == CaretAssociationHint::Before ? levels.mLevelBefore
|
aHint == CaretAssociationHint::Before ? levels.mLevelBefore
|
||||||
|
|
@ -1133,10 +1004,8 @@ void nsFrameSelection::BidiLevelFromMove(PresShell* aPresShell,
|
||||||
void nsFrameSelection::BidiLevelFromClick(nsIContent* aNode,
|
void nsFrameSelection::BidiLevelFromClick(nsIContent* aNode,
|
||||||
uint32_t aContentOffset) {
|
uint32_t aContentOffset) {
|
||||||
nsIFrame* clickInFrame = nullptr;
|
nsIFrame* clickInFrame = nullptr;
|
||||||
int32_t OffsetNotUsed;
|
clickInFrame = SelectionMovementUtils::GetFrameForNodeOffset(
|
||||||
|
aNode, aContentOffset, mCaret.mHint);
|
||||||
clickInFrame = GetFrameForNodeOffset(aNode, aContentOffset, mCaret.mHint,
|
|
||||||
&OffsetNotUsed);
|
|
||||||
if (!clickInFrame) return;
|
if (!clickInFrame) return;
|
||||||
|
|
||||||
SetCaretBidiLevelAndMaybeSchedulePaint(clickInFrame->GetEmbeddingLevel());
|
SetCaretBidiLevelAndMaybeSchedulePaint(clickInFrame->GetEmbeddingLevel());
|
||||||
|
|
@ -1212,10 +1081,10 @@ void nsFrameSelection::MaintainedRange::AdjustContentOffsets(
|
||||||
amount = eSelectEndLine;
|
amount = eSelectEndLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t offset;
|
uint32_t offset;
|
||||||
nsIFrame* frame =
|
nsIFrame* frame = SelectionMovementUtils::GetFrameForNodeOffset(
|
||||||
GetFrameForNodeOffset(aOffsets.content, aOffsets.offset,
|
aOffsets.content, aOffsets.offset, CaretAssociationHint::After,
|
||||||
CaretAssociationHint::After, &offset);
|
&offset);
|
||||||
|
|
||||||
PeekOffsetOptions peekOffsetOptions{};
|
PeekOffsetOptions peekOffsetOptions{};
|
||||||
if (aStopAtScroller == StopAtScroller::Yes) {
|
if (aStopAtScroller == StopAtScroller::Yes) {
|
||||||
|
|
@ -1224,16 +1093,17 @@ void nsFrameSelection::MaintainedRange::AdjustContentOffsets(
|
||||||
if (frame && amount == eSelectWord && direction == eDirPrevious) {
|
if (frame && amount == eSelectWord && direction == eDirPrevious) {
|
||||||
// To avoid selecting the previous word when at start of word,
|
// To avoid selecting the previous word when at start of word,
|
||||||
// first move one character forward.
|
// first move one character forward.
|
||||||
PeekOffsetStruct charPos(eSelectCharacter, eDirNext, offset,
|
PeekOffsetStruct charPos(eSelectCharacter, eDirNext,
|
||||||
nsPoint(0, 0), peekOffsetOptions);
|
static_cast<int32_t>(offset), nsPoint(0, 0),
|
||||||
|
peekOffsetOptions);
|
||||||
if (NS_SUCCEEDED(frame->PeekOffset(&charPos))) {
|
if (NS_SUCCEEDED(frame->PeekOffset(&charPos))) {
|
||||||
frame = charPos.mResultFrame;
|
frame = charPos.mResultFrame;
|
||||||
offset = charPos.mContentOffset;
|
offset = charPos.mContentOffset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PeekOffsetStruct pos(amount, direction, offset, nsPoint(0, 0),
|
PeekOffsetStruct pos(amount, direction, static_cast<int32_t>(offset),
|
||||||
peekOffsetOptions);
|
nsPoint(0, 0), peekOffsetOptions);
|
||||||
if (frame && NS_SUCCEEDED(frame->PeekOffset(&pos)) && pos.mResultContent) {
|
if (frame && NS_SUCCEEDED(frame->PeekOffset(&pos)) && pos.mResultContent) {
|
||||||
aOffsets.content = pos.mResultContent;
|
aOffsets.content = pos.mResultContent;
|
||||||
aOffsets.offset = pos.mContentOffset;
|
aOffsets.offset = pos.mContentOffset;
|
||||||
|
|
@ -1760,187 +1630,6 @@ nsresult nsFrameSelection::RepaintSelection(SelectionType aSelectionType) {
|
||||||
return mDomSelections[index]->Repaint(mPresShell->GetPresContext());
|
return mDomSelections[index]->Repaint(mPresShell->GetPresContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool IsDisplayContents(const nsIContent* aContent) {
|
|
||||||
return aContent->IsElement() && aContent->AsElement()->IsDisplayContents();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool nsFrameSelection::AdjustFrameForLineStart(nsIFrame*& aFrame,
|
|
||||||
int32_t& aFrameOffset) {
|
|
||||||
if (!aFrame->HasSignificantTerminalNewline()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto [start, end] = aFrame->GetOffsets();
|
|
||||||
if (aFrameOffset != end) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsIFrame* nextSibling = aFrame->GetNextSibling();
|
|
||||||
if (!nextSibling) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
aFrame = nextSibling;
|
|
||||||
std::tie(start, end) = aFrame->GetOffsets();
|
|
||||||
aFrameOffset = start;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
nsIFrame* nsFrameSelection::GetFrameForNodeOffset(nsIContent* aNode,
|
|
||||||
int32_t aOffset,
|
|
||||||
CaretAssociationHint aHint,
|
|
||||||
int32_t* aReturnOffset) {
|
|
||||||
if (!aNode || !aReturnOffset) return nullptr;
|
|
||||||
|
|
||||||
if (aOffset < 0) return nullptr;
|
|
||||||
|
|
||||||
if (!aNode->GetPrimaryFrame() && !IsDisplayContents(aNode)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsIFrame* returnFrame = nullptr;
|
|
||||||
nsCOMPtr<nsIContent> theNode;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
*aReturnOffset = aOffset;
|
|
||||||
|
|
||||||
theNode = aNode;
|
|
||||||
|
|
||||||
if (aNode->IsElement()) {
|
|
||||||
int32_t childIndex = 0;
|
|
||||||
int32_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;
|
|
||||||
} else {
|
|
||||||
// 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();
|
|
||||||
|
|
||||||
*aReturnOffset = (int32_t)textLength;
|
|
||||||
} else {
|
|
||||||
*aReturnOffset = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
int32_t numChildren = aNode->GetChildCount();
|
|
||||||
int32_t newChildIndex = aHint == CaretAssociationHint::Before
|
|
||||||
? childIndex - 1
|
|
||||||
: childIndex + 1;
|
|
||||||
|
|
||||||
if (newChildIndex >= 0 && newChildIndex < numChildren) {
|
|
||||||
nsCOMPtr<nsIContent> newChildNode =
|
|
||||||
aNode->GetChildAt_Deprecated(newChildIndex);
|
|
||||||
if (!newChildNode) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
aNode = newChildNode;
|
|
||||||
aOffset = aHint == CaretAssociationHint::Before
|
|
||||||
? aNode->GetChildCount()
|
|
||||||
: 0;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// 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;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
int32_t end = theNode->GetChildCount();
|
|
||||||
if (aOffset < end) {
|
|
||||||
++aOffset;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
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;
|
|
||||||
*aReturnOffset = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// find the child frame containing the offset we want
|
|
||||||
returnFrame->GetChildFrameContainingOffset(
|
|
||||||
*aReturnOffset, aHint == CaretAssociationHint::After, &aOffset,
|
|
||||||
&returnFrame);
|
|
||||||
return returnFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsIFrame* nsFrameSelection::GetFrameToPageSelect() const {
|
nsIFrame* nsFrameSelection::GetFrameToPageSelect() const {
|
||||||
if (NS_WARN_IF(!mPresShell)) {
|
if (NS_WARN_IF(!mPresShell)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
@ -2180,7 +1869,7 @@ nsresult nsFrameSelection::PhysicalMove(int16_t aDirection, int16_t aAmount,
|
||||||
{eDirNext, blockNextAmount}};
|
{eDirNext, blockNextAmount}};
|
||||||
|
|
||||||
WritingMode wm;
|
WritingMode wm;
|
||||||
const Selection::PrimaryFrameData frameForFocus =
|
const PrimaryFrameData frameForFocus =
|
||||||
sel->GetPrimaryFrameForCaretAtFocusNode(true);
|
sel->GetPrimaryFrameForCaretAtFocusNode(true);
|
||||||
if (frameForFocus.mFrame) {
|
if (frameForFocus.mFrame) {
|
||||||
// FYI: Setting the caret association hint was done during a call of
|
// FYI: Setting the caret association hint was done during a call of
|
||||||
|
|
|
||||||
|
|
@ -485,21 +485,6 @@ class nsFrameSelection final {
|
||||||
|
|
||||||
bool IsValidSelectionPoint(nsINode* aNode) const;
|
bool IsValidSelectionPoint(nsINode* aNode) const;
|
||||||
|
|
||||||
static bool AdjustFrameForLineStart(nsIFrame*& aFrame, int32_t& aFrameOffset);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a node and its child offset, return the nsIFrame and the offset into
|
|
||||||
* 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, int32_t aOffset,
|
|
||||||
CaretAssociationHint aHint,
|
|
||||||
int32_t* aReturnOffset);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetFrameToPageSelect() returns a frame which is ancestor limit of
|
* GetFrameToPageSelect() returns a frame which is ancestor limit of
|
||||||
* per-page selection. The frame may not be scrollable. E.g.,
|
* per-page selection. The frame may not be scrollable. E.g.,
|
||||||
|
|
@ -740,24 +725,6 @@ class nsFrameSelection final {
|
||||||
nsPrevNextBidiLevels GetPrevNextBidiLevels(nsIContent* aNode,
|
nsPrevNextBidiLevels GetPrevNextBidiLevels(nsIContent* aNode,
|
||||||
uint32_t aContentOffset,
|
uint32_t aContentOffset,
|
||||||
bool aJumpLines) const;
|
bool aJumpLines) const;
|
||||||
static nsPrevNextBidiLevels GetPrevNextBidiLevels(nsIContent* aNode,
|
|
||||||
uint32_t aContentOffset,
|
|
||||||
CaretAssociationHint aHint,
|
|
||||||
bool aJumpLines);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GetFrameFromLevel will scan in a given direction
|
|
||||||
* until it finds a frame with a Bidi level less than or equal to a given
|
|
||||||
* level. It will return the last frame before this.
|
|
||||||
*
|
|
||||||
* @param aPresContext is the context to use
|
|
||||||
* @param aFrameIn is the frame to start from
|
|
||||||
* @param aDirection is the direction to scan
|
|
||||||
* @param aBidiLevel is the level to search for
|
|
||||||
*/
|
|
||||||
static mozilla::Result<nsIFrame*, nsresult> GetFrameFromLevel(
|
|
||||||
nsIFrame* aFrameIn, nsDirection aDirection,
|
|
||||||
mozilla::intl::BidiEmbeddingLevel aBidiLevel);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MaintainSelection will track the normal selection as being "sticky".
|
* MaintainSelection will track the normal selection as being "sticky".
|
||||||
|
|
@ -916,19 +883,6 @@ class nsFrameSelection final {
|
||||||
const nsSelectionAmount aAmount, CaretMovementStyle aMovementStyle,
|
const nsSelectionAmount aAmount, CaretMovementStyle aMovementStyle,
|
||||||
const nsPoint& aDesiredCaretPos) const;
|
const nsPoint& aDesiredCaretPos) const;
|
||||||
|
|
||||||
/**
|
|
||||||
* PeekOffsetForCaretMove() only peek offset for caret move from the specified
|
|
||||||
* point of the normal selection. I.e., won't change selection ranges nor
|
|
||||||
* bidi information.
|
|
||||||
*/
|
|
||||||
static mozilla::Result<mozilla::PeekOffsetStruct, nsresult>
|
|
||||||
PeekOffsetForCaretMove(nsIContent* aContent, uint32_t aOffset,
|
|
||||||
nsDirection aDirection, CaretAssociationHint aHint,
|
|
||||||
mozilla::intl::BidiEmbeddingLevel aCaretBidiLevel,
|
|
||||||
const nsSelectionAmount aAmount,
|
|
||||||
const nsPoint& aDesiredCaretPos,
|
|
||||||
mozilla::PeekOffsetOptions aOptions);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CreateRangeExtendedToSomewhere() is common method to implement
|
* CreateRangeExtendedToSomewhere() is common method to implement
|
||||||
* CreateRangeExtendedTo*(). This method creates a range extended from
|
* CreateRangeExtendedTo*(). This method creates a range extended from
|
||||||
|
|
@ -940,29 +894,6 @@ class nsFrameSelection final {
|
||||||
const nsSelectionAmount aAmount,
|
const nsSelectionAmount aAmount,
|
||||||
CaretMovementStyle aMovementStyle);
|
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<bool, nsresult> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InvalidateDesiredCaretPos(); // do not listen to mDesiredCaretPos.mValue
|
void InvalidateDesiredCaretPos(); // do not listen to mDesiredCaretPos.mValue
|
||||||
// you must get another.
|
// you must get another.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@
|
||||||
#include "mozilla/PresShell.h"
|
#include "mozilla/PresShell.h"
|
||||||
#include "mozilla/PresShellInlines.h"
|
#include "mozilla/PresShellInlines.h"
|
||||||
#include "mozilla/ResultExtensions.h"
|
#include "mozilla/ResultExtensions.h"
|
||||||
|
#include "mozilla/SelectionMovementUtils.h"
|
||||||
#include "mozilla/Sprintf.h"
|
#include "mozilla/Sprintf.h"
|
||||||
#include "mozilla/StaticAnalysisFunctions.h"
|
#include "mozilla/StaticAnalysisFunctions.h"
|
||||||
#include "mozilla/StaticPrefs_layout.h"
|
#include "mozilla/StaticPrefs_layout.h"
|
||||||
|
|
@ -4939,15 +4940,15 @@ nsresult nsIFrame::SelectByTypeAtPoint(nsPresContext* aPresContext,
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t offset;
|
uint32_t offset;
|
||||||
nsIFrame* frame = nsFrameSelection::GetFrameForNodeOffset(
|
nsIFrame* frame = SelectionMovementUtils::GetFrameForNodeOffset(
|
||||||
offsets.content, offsets.offset, offsets.associate, &offset);
|
offsets.content, offsets.offset, offsets.associate, &offset);
|
||||||
if (!frame) {
|
if (!frame) {
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
return frame->PeekBackwardAndForward(aBeginAmountType, aEndAmountType, offset,
|
return frame->PeekBackwardAndForward(
|
||||||
aBeginAmountType != eSelectWord,
|
aBeginAmountType, aEndAmountType, static_cast<int32_t>(offset),
|
||||||
aSelectFlags);
|
aBeginAmountType != eSelectWord, aSelectFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue