Commit 248922f1 authored by Daniel Libby's avatar Daniel Libby Committed by Chromium LUCI CQ

Implementation of non-ng painted selection

As not all layouts have been converted to NG, we also need to record
painted selection bounds in the non-ng text painting codepaths.

This CL adds the ability to compute SelectionState from InlineTextBox,
similar to crrev.com/c/2567021 did for NGInlineCursorPosition. This
state is then used by SelectionBoundsRecorder along with the selection
rect to record the painted selection bounds.

Bug: 1065049

Change-Id: I6915783f0c1ffdddd269fe43f729872e1f13e219
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2597884Reviewed-by: default avatarXiaocheng Hu <xiaochengh@chromium.org>
Reviewed-by: default avatarXianzhu Wang <wangxianzhu@chromium.org>
Commit-Queue: Daniel Libby <dlibby@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#842189}
parent dd7a886d
...@@ -1293,6 +1293,11 @@ SelectionState FrameSelection::ComputeLayoutSelectionStateForCursor( ...@@ -1293,6 +1293,11 @@ SelectionState FrameSelection::ComputeLayoutSelectionStateForCursor(
return layout_selection_->ComputeSelectionStateForCursor(position); return layout_selection_->ComputeSelectionStateForCursor(position);
} }
SelectionState FrameSelection::ComputeLayoutSelectionStateForInlineTextBox(
const InlineTextBox& text_box) const {
return layout_selection_->ComputeSelectionStateForInlineTextBox(text_box);
}
bool FrameSelection::IsDirectional() const { bool FrameSelection::IsDirectional() const {
return is_directional_; return is_directional_;
} }
......
...@@ -44,6 +44,7 @@ namespace blink { ...@@ -44,6 +44,7 @@ namespace blink {
class CaretDisplayItemClient; class CaretDisplayItemClient;
class Element; class Element;
class InlineTextBox;
class LayoutBlock; class LayoutBlock;
class LayoutText; class LayoutText;
class LocalFrame; class LocalFrame;
...@@ -240,6 +241,7 @@ class CORE_EXPORT FrameSelection final ...@@ -240,6 +241,7 @@ class CORE_EXPORT FrameSelection final
void PageActivationChanged(); void PageActivationChanged();
bool IsHandleVisible() const { return is_handle_visible_; } bool IsHandleVisible() const { return is_handle_visible_; }
void SetHandleVisibleForTesting() { is_handle_visible_ = true; }
bool ShouldShrinkNextTap() const { return should_shrink_next_tap_; } bool ShouldShrinkNextTap() const { return should_shrink_next_tap_; }
// Returns true if a word is selected. // Returns true if a word is selected.
...@@ -288,6 +290,8 @@ class CORE_EXPORT FrameSelection final ...@@ -288,6 +290,8 @@ class CORE_EXPORT FrameSelection final
const NGInlineCursor& cursor) const; const NGInlineCursor& cursor) const;
SelectionState ComputeLayoutSelectionStateForCursor( SelectionState ComputeLayoutSelectionStateForCursor(
const NGInlineCursorPosition& position) const; const NGInlineCursorPosition& position) const;
SelectionState ComputeLayoutSelectionStateForInlineTextBox(
const InlineTextBox& text_box) const;
void Trace(Visitor*) const override; void Trace(Visitor*) const override;
......
...@@ -30,9 +30,11 @@ ...@@ -30,9 +30,11 @@
#include "third_party/blink/renderer/core/editing/visible_position.h" #include "third_party/blink/renderer/core/editing/visible_position.h"
#include "third_party/blink/renderer/core/editing/visible_units.h" #include "third_party/blink/renderer/core/editing/visible_units.h"
#include "third_party/blink/renderer/core/html/forms/text_control_element.h" #include "third_party/blink/renderer/core/html/forms/text_control_element.h"
#include "third_party/blink/renderer/core/layout/api/line_layout_api_shim.h"
#include "third_party/blink/renderer/core/layout/layout_text.h" #include "third_party/blink/renderer/core/layout/layout_text.h"
#include "third_party/blink/renderer/core/layout/layout_text_fragment.h" #include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
#include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/layout/line/inline_text_box.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
...@@ -708,40 +710,34 @@ LayoutSelectionStatus LayoutSelection::ComputeSelectionStatus( ...@@ -708,40 +710,34 @@ LayoutSelectionStatus LayoutSelection::ComputeSelectionStatus(
} }
} }
SelectionState LayoutSelection::ComputeSelectionStateForCursor( // Given |state| that describes the provided offsets relationship to the
const NGInlineCursorPosition& position) const { // |paint_range_| (and thus which comparisons are valid), returns a
if (!position) // SelectionState that reflects where the endpoints of the selection fall,
return SelectionState::kNone; // relative to the range expressed by the offsets.
SelectionState LayoutSelection::ComputeSelectionStateFromOffsets(
DCHECK(position.IsText()); SelectionState state,
unsigned start_offset,
// Selection on ellipsis is not supported. unsigned end_offset) const {
if (position.IsEllipsis()) switch (state) {
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: { case SelectionState::kStart: {
const unsigned start_in_block = paint_range_->start_offset.value(); const unsigned start_in_block =
paint_range_->start_offset.value_or(start_offset);
return start_offset <= start_in_block && start_in_block <= end_offset return start_offset <= start_in_block && start_in_block <= end_offset
? SelectionState::kStart ? SelectionState::kStart
: SelectionState::kNone; : SelectionState::kNone;
} }
case SelectionState::kEnd: { case SelectionState::kEnd: {
const unsigned end_in_block = paint_range_->end_offset.value(); const unsigned end_in_block =
paint_range_->end_offset.value_or(end_offset);
return start_offset <= end_in_block && end_in_block <= end_offset return start_offset <= end_in_block && end_in_block <= end_offset
? SelectionState::kEnd ? SelectionState::kEnd
: SelectionState::kNone; : SelectionState::kNone;
} }
case SelectionState::kStartAndEnd: { case SelectionState::kStartAndEnd: {
const unsigned start_in_block = paint_range_->start_offset.value(); const unsigned start_in_block =
const unsigned end_in_block = paint_range_->end_offset.value(); paint_range_->start_offset.value_or(start_offset);
const unsigned end_in_block =
paint_range_->end_offset.value_or(end_offset);
const bool is_start_in_current_cursor = const bool is_start_in_current_cursor =
start_offset <= start_in_block && start_in_block <= end_offset; start_offset <= start_in_block && start_in_block <= end_offset;
const bool is_end_in_current_cursor = const bool is_end_in_current_cursor =
...@@ -763,6 +759,42 @@ SelectionState LayoutSelection::ComputeSelectionStateForCursor( ...@@ -763,6 +759,42 @@ SelectionState LayoutSelection::ComputeSelectionStateForCursor(
} }
} }
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.
SelectionState state = GetSelectionStateFor(position);
return ComputeSelectionStateFromOffsets(state, start_offset, end_offset);
}
SelectionState LayoutSelection::ComputeSelectionStateForInlineTextBox(
const InlineTextBox& text_box) const {
AssertIsValid();
unsigned start_offset = static_cast<unsigned>(text_box.CaretMinOffset());
unsigned end_offset = static_cast<unsigned>(text_box.CaretMaxOffset());
// Determine the state of the overall selection, relative to the
// InlineTextBox. This state will allow us know which offset comparisons are
// valid, and determine if the selection endpoints fall within InlineTextBox.
const LayoutText* text = To<LayoutText>(
LineLayoutAPIShim::ConstLayoutObjectFrom(text_box.GetLineLayoutItem()));
SelectionState state = GetSelectionStateFor(*text);
return ComputeSelectionStateFromOffsets(state, start_offset, end_offset);
}
static NewPaintRangeAndSelectedNodes CalcSelectionRangeAndSetSelectionState( static NewPaintRangeAndSelectedNodes CalcSelectionRangeAndSetSelectionState(
const FrameSelection& frame_selection) { const FrameSelection& frame_selection) {
const SelectionInDOMTree& selection_in_dom = const SelectionInDOMTree& selection_in_dom =
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
namespace blink { namespace blink {
class InlineTextBox;
class IntRect; class IntRect;
class LayoutObject; class LayoutObject;
class LayoutText; class LayoutText;
...@@ -61,6 +62,14 @@ class LayoutSelection final : public GarbageCollected<LayoutSelection> { ...@@ -61,6 +62,14 @@ class LayoutSelection final : public GarbageCollected<LayoutSelection> {
SelectionState ComputeSelectionStateForCursor( SelectionState ComputeSelectionStateForCursor(
const NGInlineCursorPosition&) const; const NGInlineCursorPosition&) const;
// Compute the layout selection state relative to the InlineTextBox.
// E.g. a state of kStart means that the selection starts within the line
// (and ends elsewhere), where kStartAndEnd means the selection both starts
// and ends within the line. This information is used at paint time to
// determine the edges of the layout selection.
SelectionState ComputeSelectionStateForInlineTextBox(
const InlineTextBox&) const;
static bool IsSelected(const LayoutObject&); static bool IsSelected(const LayoutObject&);
void ContextDestroyed(); void ContextDestroyed();
...@@ -68,6 +77,10 @@ class LayoutSelection final : public GarbageCollected<LayoutSelection> { ...@@ -68,6 +77,10 @@ class LayoutSelection final : public GarbageCollected<LayoutSelection> {
void Trace(Visitor*) const; void Trace(Visitor*) const;
private: private:
SelectionState ComputeSelectionStateFromOffsets(SelectionState state,
unsigned start_offset,
unsigned end_offset) const;
void AssertIsValid() const; void AssertIsValid() const;
Member<FrameSelection> frame_selection_; Member<FrameSelection> frame_selection_;
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_text.h" #include "third_party/blink/renderer/core/layout/layout_text.h"
#include "third_party/blink/renderer/core/layout/layout_text_fragment.h" #include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
#include "third_party/blink/renderer/core/layout/line/inline_text_box.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h" #include "third_party/blink/renderer/platform/wtf/assertions.h"
...@@ -943,6 +944,69 @@ TEST_P(LayoutSelectionTest, InvalidateSlot) { ...@@ -943,6 +944,69 @@ TEST_P(LayoutSelectionTest, InvalidateSlot) {
DumpSelectionInfo()); DumpSelectionInfo());
} }
TEST_P(LayoutSelectionTest, StartAndEndSelectionState) {
if (LayoutNGEnabled())
return;
Selection().SetSelectionAndEndTyping(
SetSelectionTextToBody("<div>f^oo|</div><div>bar</div>"));
UpdateAllLifecyclePhasesForTest();
Node* foo_div = GetDocument().body()->firstChild();
auto& foo_text = *To<LayoutText>(foo_div->firstChild()->GetLayoutObject());
InlineTextBox* text_box = foo_text.FirstTextBox();
EXPECT_EQ(SelectionState::kStartAndEnd,
Selection().ComputeLayoutSelectionStateForInlineTextBox(*text_box));
Node* bar_div = GetDocument().body()->lastChild();
auto& bar_text = *To<LayoutText>(bar_div->firstChild()->GetLayoutObject());
text_box = bar_text.FirstTextBox();
EXPECT_EQ(SelectionState::kNone,
Selection().ComputeLayoutSelectionStateForInlineTextBox(*text_box));
}
TEST_P(LayoutSelectionTest, StartAndEndMultilineSelectionState) {
if (LayoutNGEnabled())
return;
Selection().SetSelectionAndEndTyping(SetSelectionTextToBody(
"<div style='white-space:pre'>f^oo\nbar\nba|z</div>"));
UpdateAllLifecyclePhasesForTest();
auto& div_text = *To<LayoutText>(
GetDocument().body()->firstChild()->firstChild()->GetLayoutObject());
for (const InlineTextBox* box : div_text.TextBoxes()) {
SelectionState state =
Selection().ComputeLayoutSelectionStateForInlineTextBox(*box);
if (box == div_text.FirstTextBox())
EXPECT_EQ(SelectionState::kStart, state);
else if (box == div_text.LastTextBox())
EXPECT_EQ(SelectionState::kEnd, state);
else
EXPECT_EQ(SelectionState::kInside, state);
}
}
TEST_P(LayoutSelectionTest, StartAndEndBR) {
if (LayoutNGEnabled())
return;
Selection().SetSelectionAndEndTyping(SetSelectionTextToBody(
"<div style='white-space:pre'>^<br>foo<br>|</div>"));
UpdateAllLifecyclePhasesForTest();
auto& first_br_text = *To<LayoutText>(
GetDocument().body()->firstChild()->firstChild()->GetLayoutObject());
const InlineTextBox* box = first_br_text.FirstTextBox();
SelectionState state =
Selection().ComputeLayoutSelectionStateForInlineTextBox(*box);
EXPECT_EQ(SelectionState::kStart, state);
auto& last_br_text = *To<LayoutText>(
GetDocument().body()->firstChild()->lastChild()->GetLayoutObject());
box = last_br_text.FirstTextBox();
state = Selection().ComputeLayoutSelectionStateForInlineTextBox(*box);
EXPECT_EQ(SelectionState::kEnd, state);
}
class NGLayoutSelectionTest class NGLayoutSelectionTest
: public LayoutSelectionTestBase, : public LayoutSelectionTestBase,
private ScopedLayoutNGForTest, private ScopedLayoutNGForTest,
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "third_party/blink/renderer/core/paint/highlight_painting_utils.h" #include "third_party/blink/renderer/core/paint/highlight_painting_utils.h"
#include "third_party/blink/renderer/core/paint/paint_info.h" #include "third_party/blink/renderer/core/paint/paint_info.h"
#include "third_party/blink/renderer/core/paint/paint_timing_detector.h" #include "third_party/blink/renderer/core/paint/paint_timing_detector.h"
#include "third_party/blink/renderer/core/paint/selection_bounds_recorder.h"
#include "third_party/blink/renderer/core/paint/text_decoration_info.h" #include "third_party/blink/renderer/core/paint/text_decoration_info.h"
#include "third_party/blink/renderer/core/paint/text_painter.h" #include "third_party/blink/renderer/core/paint/text_painter.h"
#include "third_party/blink/renderer/platform/graphics/dom_node_id.h" #include "third_party/blink/renderer/platform/graphics/dom_node_id.h"
...@@ -153,18 +154,6 @@ void InlineTextBoxPainter::Paint(const PaintInfo& paint_info, ...@@ -153,18 +154,6 @@ void InlineTextBoxPainter::Paint(const PaintInfo& paint_info,
physical_overflow.Move(paint_offset); physical_overflow.Move(paint_offset);
IntRect visual_rect = EnclosingIntRect(physical_overflow); IntRect visual_rect = EnclosingIntRect(physical_overflow);
// The text clip phase already has a DrawingRecorder. Text clips are initiated
// only in BoxPainter::PaintFillLayer, which is already within a
// DrawingRecorder.
base::Optional<DrawingRecorder> recorder;
if (paint_info.phase != PaintPhase::kTextClip) {
if (DrawingRecorder::UseCachedDrawingIfPossible(
paint_info.context, inline_text_box_, paint_info.phase))
return;
recorder.emplace(paint_info.context, inline_text_box_, paint_info.phase,
visual_rect);
}
GraphicsContext& context = paint_info.context; GraphicsContext& context = paint_info.context;
PhysicalOffset box_origin = PhysicalOffset box_origin =
inline_text_box_.PhysicalLocation() + paint_offset; inline_text_box_.PhysicalLocation() + paint_offset;
...@@ -182,6 +171,35 @@ void InlineTextBoxPainter::Paint(const PaintInfo& paint_info, ...@@ -182,6 +171,35 @@ void InlineTextBoxPainter::Paint(const PaintInfo& paint_info,
PhysicalSize(inline_text_box_.LogicalWidth(), PhysicalSize(inline_text_box_.LogicalWidth(),
inline_text_box_.LogicalHeight())); inline_text_box_.LogicalHeight()));
base::Optional<SelectionBoundsRecorder> selection_recorder;
if (have_selection && paint_info.phase == PaintPhase::kForeground &&
!is_printing) {
const FrameSelection& frame_selection =
InlineLayoutObject().GetFrame()->Selection();
SelectionState selection_state =
frame_selection.ComputeLayoutSelectionStateForInlineTextBox(
inline_text_box_);
if (SelectionBoundsRecorder::ShouldRecordSelection(frame_selection,
selection_state)) {
PhysicalRect selection_rect =
GetSelectionRect<InlineTextBoxPainter::PaintOptions::kNormal>(
context, box_rect, style_to_use, style_to_use.GetFont());
selection_recorder.emplace(selection_state, selection_rect,
context.GetPaintController());
}
}
// The text clip phase already has a DrawingRecorder. Text clips are initiated
// only in BoxPainter::PaintFillLayer, which is already within a
// DrawingRecorder.
base::Optional<DrawingRecorder> recorder;
if (paint_info.phase != PaintPhase::kTextClip) {
if (DrawingRecorder::UseCachedDrawingIfPossible(context, inline_text_box_,
paint_info.phase))
return;
recorder.emplace(context, inline_text_box_, paint_info.phase, visual_rect);
}
unsigned length = inline_text_box_.Len(); unsigned length = inline_text_box_.Len();
const String& layout_item_string = const String& layout_item_string =
inline_text_box_.GetLineLayoutItem().GetText(); inline_text_box_.GetLineLayoutItem().GetText();
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/editing/testing/selection_sample.h"
#include "third_party/blink/renderer/core/paint/paint_controller_paint_test.h" #include "third_party/blink/renderer/core/paint/paint_controller_paint_test.h"
using testing::ElementsAre; using testing::ElementsAre;
...@@ -27,4 +28,64 @@ TEST_P(InlineTextBoxPainterTest, LineBreak) { ...@@ -27,4 +28,64 @@ TEST_P(InlineTextBoxPainterTest, LineBreak) {
EXPECT_EQ(6u, ContentDisplayItems().size()); EXPECT_EQ(6u, ContentDisplayItems().size());
} }
class InlineTextBoxPainterNonNGTest : public PaintControllerPaintTest,
public ScopedLayoutNGForTest {
public:
InlineTextBoxPainterNonNGTest() : ScopedLayoutNGForTest(false) {}
};
INSTANTIATE_PAINT_TEST_SUITE_P(InlineTextBoxPainterNonNGTest);
TEST_P(InlineTextBoxPainterNonNGTest, RecordedSelectionAll) {
if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
SetBodyInnerHTML("<span>A<br>B<br>C</span>");
GetDocument().GetFrame()->Selection().SetHandleVisibleForTesting();
GetDocument().GetFrame()->Selection().SelectAll();
UpdateAllLifecyclePhasesForTest();
auto chunks = ContentPaintChunks();
EXPECT_EQ(chunks.size(), 1u);
EXPECT_TRUE(chunks.begin()->layer_selection_data->start.has_value());
EXPECT_TRUE(chunks.begin()->layer_selection_data->end.has_value());
PaintedSelectionBound start =
chunks.begin()->layer_selection_data->start.value();
EXPECT_EQ(start.type, gfx::SelectionBound::LEFT);
EXPECT_EQ(start.edge_start, IntPoint(8, 8));
EXPECT_EQ(start.edge_end, IntPoint(8, 9));
PaintedSelectionBound end = chunks.begin()->layer_selection_data->end.value();
EXPECT_EQ(end.type, gfx::SelectionBound::RIGHT);
EXPECT_EQ(end.edge_start, IntPoint(9, 10));
EXPECT_EQ(end.edge_end, IntPoint(9, 11));
}
TEST_P(InlineTextBoxPainterNonNGTest, RecordedSelectionMultiline) {
if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
SelectionSample::SetSelectionText(
GetDocument().body(),
"<div style='white-space:pre'>f^oo\nbar\nb|az</div>"));
GetDocument().GetFrame()->Selection().SetHandleVisibleForTesting();
UpdateAllLifecyclePhasesForTest();
auto chunks = ContentPaintChunks();
EXPECT_EQ(chunks.size(), 1u);
EXPECT_TRUE(chunks.begin()->layer_selection_data->start.has_value());
EXPECT_TRUE(chunks.begin()->layer_selection_data->end.has_value());
PaintedSelectionBound start =
chunks.begin()->layer_selection_data->start.value();
EXPECT_EQ(start.type, gfx::SelectionBound::LEFT);
EXPECT_EQ(start.edge_start, IntPoint(8, 8));
EXPECT_EQ(start.edge_end, IntPoint(8, 9));
PaintedSelectionBound end = chunks.begin()->layer_selection_data->end.value();
EXPECT_EQ(end.type, gfx::SelectionBound::RIGHT);
EXPECT_EQ(end.edge_start, IntPoint(9, 10));
EXPECT_EQ(end.edge_end, IntPoint(9, 11));
}
} // 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