Commit 73efb139 authored by Daniel Libby's avatar Daniel Libby Committed by Chromium LUCI CQ

Add ability to query SelectionState relative to a cursor position

For composited selection to work in CAP, we'll need to record the start
and end of the selection at paint time. To facilitate this, we'll need
to know whether the selection rect we compute in order to paint the
selection background contains the start/end of the frame's selection.

This CL adds a way to query this information for a given cursor position
which will be consumed at paint time. There is a mechanism to understand
the selection state relative to a layout object, which we use to
determine which offsets are valid for comparison. The state returned
will inform the caller of the intersection of the frame's selection.

Bug: 1065049

Change-Id: I217f9e8859b0790543285ddd5de100adba1e5ecd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2567021
Commit-Queue: Daniel Libby <dlibby@microsoft.com>
Reviewed-by: default avatarKoji Ishii <kojii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#833228}
parent bf2be059
...@@ -1288,6 +1288,11 @@ LayoutSelectionStatus FrameSelection::ComputeLayoutSelectionStatus( ...@@ -1288,6 +1288,11 @@ LayoutSelectionStatus FrameSelection::ComputeLayoutSelectionStatus(
return layout_selection_->ComputeSelectionStatus(cursor); return layout_selection_->ComputeSelectionStatus(cursor);
} }
SelectionState FrameSelection::ComputeLayoutSelectionStateForCursor(
const NGInlineCursorPosition& position) const {
return layout_selection_->ComputeSelectionStateForCursor(position);
}
bool FrameSelection::IsDirectional() const { bool FrameSelection::IsDirectional() const {
return is_directional_; return is_directional_;
} }
......
...@@ -51,6 +51,7 @@ class FrameCaret; ...@@ -51,6 +51,7 @@ class FrameCaret;
class GranularityStrategy; class GranularityStrategy;
class GraphicsContext; class GraphicsContext;
class NGInlineCursor; class NGInlineCursor;
class NGInlineCursorPosition;
class Range; class Range;
class SelectionEditor; class SelectionEditor;
class LayoutSelection; class LayoutSelection;
...@@ -285,6 +286,8 @@ class CORE_EXPORT FrameSelection final ...@@ -285,6 +286,8 @@ class CORE_EXPORT FrameSelection final
const LayoutText& text) const; const LayoutText& text) const;
LayoutSelectionStatus ComputeLayoutSelectionStatus( LayoutSelectionStatus ComputeLayoutSelectionStatus(
const NGInlineCursor& cursor) const; const NGInlineCursor& cursor) const;
SelectionState ComputeLayoutSelectionStateForCursor(
const NGInlineCursorPosition& position) const;
void Trace(Visitor*) const override; void Trace(Visitor*) const override;
......
...@@ -582,10 +582,10 @@ static SelectionState GetSelectionStateFor(const LayoutText& layout_text) { ...@@ -582,10 +582,10 @@ static SelectionState GetSelectionStateFor(const LayoutText& layout_text) {
return layout_text.GetSelectionState(); return layout_text.GetSelectionState();
} }
static SelectionState GetSelectionStateFor(const NGInlineCursor& cursor) { static SelectionState GetSelectionStateFor(
DCHECK(cursor.Current().GetLayoutObject()); const NGInlineCursorPosition& position) {
return GetSelectionStateFor( DCHECK(position.GetLayoutObject());
To<LayoutText>(*cursor.Current().GetLayoutObject())); return GetSelectionStateFor(To<LayoutText>(*position.GetLayoutObject()));
} }
bool LayoutSelection::IsSelected(const LayoutObject& layout_object) { bool LayoutSelection::IsSelected(const LayoutObject& layout_object) {
...@@ -665,7 +665,7 @@ LayoutSelectionStatus LayoutSelection::ComputeSelectionStatus( ...@@ -665,7 +665,7 @@ LayoutSelectionStatus LayoutSelection::ComputeSelectionStatus(
const NGTextOffset offset = cursor.Current().TextOffset(); const NGTextOffset offset = cursor.Current().TextOffset();
const unsigned start_offset = offset.start; const unsigned start_offset = offset.start;
const unsigned end_offset = offset.end; const unsigned end_offset = offset.end;
switch (GetSelectionStateFor(cursor)) { switch (GetSelectionStateFor(cursor.Current())) {
case SelectionState::kStart: { case SelectionState::kStart: {
const unsigned start_in_block = paint_range_->start_offset.value(); const unsigned start_in_block = paint_range_->start_offset.value();
const bool is_continuous = start_in_block <= end_offset; const bool is_continuous = start_in_block <= end_offset;
...@@ -709,6 +709,61 @@ LayoutSelectionStatus LayoutSelection::ComputeSelectionStatus( ...@@ -709,6 +709,61 @@ LayoutSelectionStatus LayoutSelection::ComputeSelectionStatus(
} }
} }
SelectionState LayoutSelection::ComputeSelectionStateForCursor(
const NGInlineCursorPosition& position) const {
if (!position)
return SelectionState::kNone;
DCHECK(position.IsText());
// Selection on ellipsis is not supported.
if (position.IsEllipsis())
return SelectionState::kNone;
const NGTextOffset offset = position.TextOffset();
const unsigned start_offset = offset.start;
const unsigned end_offset = offset.end;
// Determine the state of the overall selection, relative to the LayoutObject
// associated with the current cursor position. This state will allow us know
// which offset comparisons are valid, and determine if the selection
// endpoints fall within the current cursor position.
switch (GetSelectionStateFor(position)) {
case SelectionState::kStart: {
const unsigned start_in_block = paint_range_->start_offset.value();
return start_offset <= start_in_block && start_in_block <= end_offset
? SelectionState::kStart
: SelectionState::kNone;
}
case SelectionState::kEnd: {
const unsigned end_in_block = paint_range_->end_offset.value();
return start_offset <= end_in_block && end_in_block <= end_offset
? SelectionState::kEnd
: SelectionState::kNone;
}
case SelectionState::kStartAndEnd: {
const unsigned start_in_block = paint_range_->start_offset.value();
const unsigned end_in_block = paint_range_->end_offset.value();
const bool is_start_in_current_cursor =
start_offset <= start_in_block && start_in_block <= end_offset;
const bool is_end_in_current_cursor =
start_offset <= end_in_block && end_in_block <= end_offset;
if (is_start_in_current_cursor && is_end_in_current_cursor)
return SelectionState::kStartAndEnd;
else if (is_start_in_current_cursor)
return SelectionState::kStart;
else if (is_end_in_current_cursor)
return SelectionState::kEnd;
else
return SelectionState::kInside;
}
case SelectionState::kInside: {
return SelectionState::kInside;
}
default:
return SelectionState::kNone;
}
}
static NewPaintRangeAndSelectedNodes CalcSelectionRangeAndSetSelectionState( static NewPaintRangeAndSelectedNodes CalcSelectionRangeAndSetSelectionState(
const FrameSelection& frame_selection) { const FrameSelection& frame_selection) {
const SelectionInDOMTree& selection_in_dom = const SelectionInDOMTree& selection_in_dom =
......
...@@ -33,10 +33,12 @@ class IntRect; ...@@ -33,10 +33,12 @@ class IntRect;
class LayoutObject; class LayoutObject;
class LayoutText; class LayoutText;
class NGInlineCursor; class NGInlineCursor;
class NGInlineCursorPosition;
class FrameSelection; class FrameSelection;
struct LayoutSelectionStatus; struct LayoutSelectionStatus;
struct LayoutTextSelectionStatus; struct LayoutTextSelectionStatus;
class SelectionPaintRange; class SelectionPaintRange;
enum class SelectionState;
class LayoutSelection final : public GarbageCollected<LayoutSelection> { class LayoutSelection final : public GarbageCollected<LayoutSelection> {
public: public:
...@@ -50,6 +52,15 @@ class LayoutSelection final : public GarbageCollected<LayoutSelection> { ...@@ -50,6 +52,15 @@ class LayoutSelection final : public GarbageCollected<LayoutSelection> {
LayoutTextSelectionStatus ComputeSelectionStatus(const LayoutText&) const; LayoutTextSelectionStatus ComputeSelectionStatus(const LayoutText&) const;
LayoutSelectionStatus ComputeSelectionStatus(const NGInlineCursor&) const; LayoutSelectionStatus ComputeSelectionStatus(const NGInlineCursor&) const;
// Compute the layout selection state relative to the current item of the
// given NGInlineCursor. E.g. a state of kStart means that the selection
// starts within the position (and ends elsewhere), where kStartAndEnd means
// the selection both starts and ends within the position. This information is
// used at paint time to determine the edges of the layout selection.
SelectionState ComputeSelectionStateForCursor(
const NGInlineCursorPosition&) const;
static bool IsSelected(const LayoutObject&); static bool IsSelected(const LayoutObject&);
void ContextDestroyed(); void ContextDestroyed();
......
...@@ -982,6 +982,14 @@ class NGLayoutSelectionTest ...@@ -982,6 +982,14 @@ class NGLayoutSelectionTest
return Selection().ComputeLayoutSelectionStatus(cursor); return Selection().ComputeLayoutSelectionStatus(cursor);
} }
SelectionState ComputeLayoutSelectionStateForCursor(
const LayoutObject& layout_object) const {
DCHECK(layout_object.IsText());
NGInlineCursor cursor;
cursor.MoveTo(layout_object);
return Selection().ComputeLayoutSelectionStateForCursor(cursor.Current());
}
void SetSelectionAndUpdateLayoutSelection(const std::string& selection_text) { void SetSelectionAndUpdateLayoutSelection(const std::string& selection_text) {
const SelectionInDOMTree& selection = const SelectionInDOMTree& selection =
SetSelectionTextToBody(selection_text); SetSelectionTextToBody(selection_text);
...@@ -1033,6 +1041,7 @@ TEST_F(NGLayoutSelectionTest, TwoNGBlockFlows) { ...@@ -1033,6 +1041,7 @@ TEST_F(NGLayoutSelectionTest, TwoNGBlockFlows) {
GetDocument().body()->firstChild()->firstChild()->GetLayoutObject(); GetDocument().body()->firstChild()->firstChild()->GetLayoutObject();
EXPECT_EQ(LayoutSelectionStatus(1u, 3u, SelectSoftLineBreak::kSelected), EXPECT_EQ(LayoutSelectionStatus(1u, 3u, SelectSoftLineBreak::kSelected),
ComputeLayoutSelectionStatus(*foo)); ComputeLayoutSelectionStatus(*foo));
EXPECT_EQ(SelectionState::kStart, ComputeLayoutSelectionStateForCursor(*foo));
LayoutObject* const bar = GetDocument() LayoutObject* const bar = GetDocument()
.body() .body()
->firstChild() ->firstChild()
...@@ -1041,6 +1050,101 @@ TEST_F(NGLayoutSelectionTest, TwoNGBlockFlows) { ...@@ -1041,6 +1050,101 @@ TEST_F(NGLayoutSelectionTest, TwoNGBlockFlows) {
->GetLayoutObject(); ->GetLayoutObject();
EXPECT_EQ(LayoutSelectionStatus(0u, 2u, SelectSoftLineBreak::kNotSelected), EXPECT_EQ(LayoutSelectionStatus(0u, 2u, SelectSoftLineBreak::kNotSelected),
ComputeLayoutSelectionStatus(*bar)); ComputeLayoutSelectionStatus(*bar));
EXPECT_EQ(SelectionState::kEnd, ComputeLayoutSelectionStateForCursor(*bar));
}
TEST_F(NGLayoutSelectionTest, StartAndEndState) {
SetSelectionAndUpdateLayoutSelection("<div>f^oo|</div><div>bar</div>");
LayoutObject* const foo =
GetDocument().body()->firstChild()->firstChild()->GetLayoutObject();
EXPECT_EQ(LayoutSelectionStatus(1u, 3u, SelectSoftLineBreak::kNotSelected),
ComputeLayoutSelectionStatus(*foo));
EXPECT_EQ(SelectionState::kStartAndEnd,
ComputeLayoutSelectionStateForCursor(*foo));
LayoutObject* const bar = GetDocument()
.body()
->firstChild()
->nextSibling()
->firstChild()
->GetLayoutObject();
EXPECT_EQ(LayoutSelectionStatus(0u, 0u, SelectSoftLineBreak::kNotSelected),
ComputeLayoutSelectionStatus(*bar));
EXPECT_EQ(SelectionState::kNone, ComputeLayoutSelectionStateForCursor(*bar));
}
TEST_F(NGLayoutSelectionTest, StartAndEndMultilineState) {
SetSelectionAndUpdateLayoutSelection(
"<div style='white-space:pre'>f^oo\nbar\nba|z</div>");
LayoutObject* const div_text =
GetDocument().body()->firstChild()->firstChild()->GetLayoutObject();
NGInlineCursor cursor(*(div_text->ContainingNGBlockFlow()));
cursor.MoveTo(*div_text);
EXPECT_EQ(LayoutSelectionStatus(1u, 3u, SelectSoftLineBreak::kNotSelected),
Selection().ComputeLayoutSelectionStatus(cursor));
EXPECT_EQ(SelectionState::kStart,
Selection().ComputeLayoutSelectionStateForCursor(cursor.Current()));
// Move to 'bar' text.
cursor.MoveToNext();
cursor.MoveToNext();
cursor.MoveToNext();
EXPECT_EQ(LayoutSelectionStatus(4u, 7u, SelectSoftLineBreak::kNotSelected),
Selection().ComputeLayoutSelectionStatus(cursor));
EXPECT_EQ(SelectionState::kInside,
Selection().ComputeLayoutSelectionStateForCursor(cursor.Current()));
// Move to 'baz' text.
cursor.MoveToNext();
cursor.MoveToNext();
cursor.MoveToNext();
EXPECT_EQ(LayoutSelectionStatus(8u, 10u, SelectSoftLineBreak::kNotSelected),
Selection().ComputeLayoutSelectionStatus(cursor));
EXPECT_EQ(SelectionState::kEnd,
Selection().ComputeLayoutSelectionStateForCursor(cursor.Current()));
}
TEST_F(NGLayoutSelectionTest, BeforeStartAndAfterEndMultilineState) {
SetSelectionAndUpdateLayoutSelection(
"<div style='white-space:pre'>foo\nba^r</div><div "
"style='white-space:pre'>ba|z\nquu</div>");
LayoutObject* const div_text =
GetDocument().body()->firstChild()->firstChild()->GetLayoutObject();
NGInlineCursor cursor(*(div_text->ContainingNGBlockFlow()));
cursor.MoveTo(*div_text);
EXPECT_EQ(LayoutSelectionStatus(3u, 3u, SelectSoftLineBreak::kNotSelected),
Selection().ComputeLayoutSelectionStatus(cursor));
EXPECT_EQ(SelectionState::kNone,
Selection().ComputeLayoutSelectionStateForCursor(cursor.Current()));
// Move to 'bar' text.
cursor.MoveToNext();
cursor.MoveToNext();
cursor.MoveToNext();
EXPECT_EQ(LayoutSelectionStatus(6u, 7u, SelectSoftLineBreak::kSelected),
Selection().ComputeLayoutSelectionStatus(cursor));
EXPECT_EQ(SelectionState::kStart,
Selection().ComputeLayoutSelectionStateForCursor(cursor.Current()));
LayoutObject* const second_div_text =
GetDocument().body()->lastChild()->firstChild()->GetLayoutObject();
NGInlineCursor second_cursor(*(second_div_text->ContainingNGBlockFlow()));
second_cursor.MoveTo(*second_div_text);
EXPECT_EQ(LayoutSelectionStatus(0u, 2u, SelectSoftLineBreak::kNotSelected),
Selection().ComputeLayoutSelectionStatus(second_cursor));
EXPECT_EQ(SelectionState::kEnd,
Selection().ComputeLayoutSelectionStateForCursor(
second_cursor.Current()));
// Move to 'quu' text.
second_cursor.MoveToNext();
second_cursor.MoveToNext();
second_cursor.MoveToNext();
EXPECT_EQ(LayoutSelectionStatus(4u, 4u, SelectSoftLineBreak::kNotSelected),
Selection().ComputeLayoutSelectionStatus(second_cursor));
EXPECT_EQ(SelectionState::kNone,
Selection().ComputeLayoutSelectionStateForCursor(
second_cursor.Current()));
} }
// TODO(editing-dev): Once LayoutNG supports editing, we should change this // TODO(editing-dev): Once LayoutNG supports editing, we should change this
...@@ -1135,6 +1239,8 @@ TEST_F(NGLayoutSelectionTest, BRStatus) { ...@@ -1135,6 +1239,8 @@ TEST_F(NGLayoutSelectionTest, BRStatus) {
CHECK(layout_br->IsBR()); CHECK(layout_br->IsBR());
EXPECT_EQ(LayoutSelectionStatus(3u, 4u, SelectSoftLineBreak::kNotSelected), EXPECT_EQ(LayoutSelectionStatus(3u, 4u, SelectSoftLineBreak::kNotSelected),
ComputeLayoutSelectionStatus(*layout_br)); ComputeLayoutSelectionStatus(*layout_br));
EXPECT_EQ(SelectionState::kStartAndEnd,
ComputeLayoutSelectionStateForCursor(*layout_br));
} }
// https://crbug.com/907186 // https://crbug.com/907186
...@@ -1145,6 +1251,8 @@ TEST_F(NGLayoutSelectionTest, WBRStatus) { ...@@ -1145,6 +1251,8 @@ TEST_F(NGLayoutSelectionTest, WBRStatus) {
GetDocument().QuerySelector("wbr")->GetLayoutObject(); GetDocument().QuerySelector("wbr")->GetLayoutObject();
EXPECT_EQ(LayoutSelectionStatus(3u, 4u, SelectSoftLineBreak::kSelected), EXPECT_EQ(LayoutSelectionStatus(3u, 4u, SelectSoftLineBreak::kSelected),
ComputeLayoutSelectionStatus(*layout_wbr)); ComputeLayoutSelectionStatus(*layout_wbr));
EXPECT_EQ(SelectionState::kInside,
ComputeLayoutSelectionStateForCursor(*layout_wbr));
} }
} // namespace blink } // namespace blink
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment