Commit 6c138885 authored by Xianzhu Wang's avatar Xianzhu Wang Committed by Commit Bot

Always paint a caret when it's active

When a caret is active, now we always paint it in either the desired
color or the transparent color according to the blinking state.

This prepares for composited caret which will use an opacity effect
node to control the visibility during blinking.

The always painted display item for the active caret won't cause
performance issue because the paint opearation is trivial. It may
actually make PaintController and RasterInvalidator more efficient when
matching display items sequentially (without appearing/disappearing
display items).

Bug: 1123630
Change-Id: I507e1f5014040e8aecceb83346e5f28973f16c4f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2391002Reviewed-by: default avatarYoshifumi Inoue <yosin@chromium.org>
Commit-Queue: Xianzhu Wang <wangxianzhu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#804064}
parent 7b62b986
......@@ -169,6 +169,13 @@ void CaretDisplayItemClient::UpdateStyleAndLayoutIfNeeded(
new_layout_block->SetShouldCheckForPaintInvalidation();
}
void CaretDisplayItemClient::SetVisibleIfActive(bool visible) {
if (visible == is_visible_if_active_)
return;
is_visible_if_active_ = visible;
needs_paint_invalidation_ = true;
}
void CaretDisplayItemClient::InvalidatePaint(
const LayoutBlock& block,
const PaintInvalidatorContext& context) {
......@@ -227,7 +234,8 @@ void CaretDisplayItemClient::PaintCaret(
DrawingRecorder recorder(context, *this, display_item_type,
EnclosingIntRect(drawing_rect));
IntRect paint_rect = PixelSnappedIntRect(drawing_rect);
context.FillRect(paint_rect, color_, DarkModeFilter::ElementRole::kText);
context.FillRect(paint_rect, is_visible_if_active_ ? color_ : Color(),
DarkModeFilter::ElementRole::kText);
}
String CaretDisplayItemClient::DebugName() const {
......
......@@ -52,12 +52,16 @@ class CORE_EXPORT CaretDisplayItemClient final : public DisplayItemClient {
// caret for paint invalidation and painting.
void UpdateStyleAndLayoutIfNeeded(const PositionWithAffinity& caret_position);
bool IsVisibleIfActive() const { return is_visible_if_active_; }
void SetVisibleIfActive(bool visible);
// Called during LayoutBlock paint invalidation.
void InvalidatePaint(const LayoutBlock&, const PaintInvalidatorContext&);
bool ShouldPaintCaret(const LayoutBlock& block) const {
return &block == layout_block_;
}
void PaintCaret(GraphicsContext&,
const PhysicalOffset& paint_offset,
DisplayItem::Type) const;
......@@ -97,6 +101,7 @@ class CORE_EXPORT CaretDisplayItemClient final : public DisplayItemClient {
const LayoutBlock* previous_layout_block_ = nullptr;
bool needs_paint_invalidation_ = false;
bool is_visible_if_active_ = true;
DISALLOW_COPY_AND_ASSIGN(CaretDisplayItemClient);
};
......
......@@ -317,12 +317,12 @@ TEST_P(CaretDisplayItemClientTest, CaretHideMoveAndShow) {
GetDocument().View()->SetTracksRasterInvalidations(true);
// Simulate that the blinking cursor becomes invisible.
Selection().SetCaretVisible(false);
Selection().SetCaretEnabled(false);
// Move the caret to the end of the text.
Selection().SetSelectionAndEndTyping(
SelectionInDOMTree::Builder().Collapse(Position(text, 5)).Build());
// Simulate that the cursor blinking is restarted.
Selection().SetCaretVisible(true);
Selection().SetCaretEnabled(true);
EXPECT_TRUE(GetCaretDisplayItemClient().IsValid());
GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
......
......@@ -50,14 +50,10 @@ FrameCaret::FrameCaret(LocalFrame& frame,
: selection_editor_(&selection_editor),
frame_(frame),
display_item_client_(new CaretDisplayItemClient()),
caret_visibility_(CaretVisibility::kHidden),
caret_blink_timer_(new TaskRunnerTimer<FrameCaret>(
frame.GetTaskRunner(TaskType::kInternalDefault),
this,
&FrameCaret::CaretBlinkTimerFired)),
should_paint_caret_(true),
is_caret_blinking_suspended_(false),
should_show_block_cursor_(false) {}
&FrameCaret::CaretBlinkTimerFired)) {}
FrameCaret::~FrameCaret() = default;
......@@ -89,7 +85,13 @@ void FrameCaret::UpdateAppearance() {
should_show_block_cursor_ && IsActive() &&
!IsLogicalEndOfLine(CreateVisiblePosition(CaretPosition()));
bool should_blink = !paint_block_cursor && ShouldBlinkCaret();
bool new_should_show_caret = ShouldShowCaret();
if (new_should_show_caret != should_show_caret_) {
should_show_caret_ = new_should_show_caret;
ScheduleVisualUpdateForPaintInvalidationIfNeeded();
}
bool should_blink = !paint_block_cursor && should_show_caret_;
if (!should_blink) {
StopCaretBlinkTimer();
return;
......@@ -100,9 +102,10 @@ void FrameCaret::UpdateAppearance() {
}
void FrameCaret::StopCaretBlinkTimer() {
if (caret_blink_timer_->IsActive() || should_paint_caret_)
if (caret_blink_timer_->IsActive() ||
display_item_client_->IsVisibleIfActive())
ScheduleVisualUpdateForPaintInvalidationIfNeeded();
should_paint_caret_ = false;
display_item_client_->SetVisibleIfActive(false);
caret_blink_timer_->Stop();
}
......@@ -116,17 +119,17 @@ void FrameCaret::StartBlinkCaret() {
if (!blink_interval.is_zero())
caret_blink_timer_->StartRepeating(blink_interval, FROM_HERE);
should_paint_caret_ = true;
display_item_client_->SetVisibleIfActive(true);
ScheduleVisualUpdateForPaintInvalidationIfNeeded();
}
void FrameCaret::SetCaretVisibility(CaretVisibility visibility) {
if (caret_visibility_ == visibility)
void FrameCaret::SetCaretEnabled(bool enabled) {
if (is_caret_enabled_ == enabled)
return;
caret_visibility_ = visibility;
is_caret_enabled_ = enabled;
if (visibility == CaretVisibility::kHidden)
if (!is_caret_enabled_)
StopCaretBlinkTimer();
ScheduleVisualUpdateForPaintInvalidationIfNeeded();
}
......@@ -139,15 +142,8 @@ void FrameCaret::UpdateStyleAndLayoutIfNeeded() {
DCHECK_GE(frame_->GetDocument()->Lifecycle().GetState(),
DocumentLifecycle::kLayoutClean);
UpdateAppearance();
bool should_paint_caret =
should_paint_caret_ && IsActive() &&
caret_visibility_ == CaretVisibility::kVisible &&
(IsEditablePosition(
selection_editor_->ComputeVisibleSelectionInDOMTree().Start()) ||
frame_->IsCaretBrowsingEnabled());
display_item_client_->UpdateStyleAndLayoutIfNeeded(
should_paint_caret ? CaretPosition() : PositionWithAffinity());
should_show_caret_ ? CaretPosition() : PositionWithAffinity());
}
void FrameCaret::InvalidatePaint(const LayoutBlock& block,
......@@ -179,34 +175,40 @@ void FrameCaret::PaintCaret(GraphicsContext& context,
display_item_client_->PaintCaret(context, paint_offset, DisplayItem::kCaret);
}
bool FrameCaret::ShouldBlinkCaret() const {
// Don't blink the caret if it isn't visible or positioned.
if (caret_visibility_ != CaretVisibility::kVisible || !IsActive())
bool FrameCaret::ShouldShowCaret() const {
// Don't show the caret if it isn't visible or positioned.
if (!is_caret_enabled_ || !IsActive())
return false;
Element* root = RootEditableElementOf(CaretPosition().GetPosition());
if (root) {
// Caret is contained in editable content. If there is no focused element,
// don't blink the caret.
// don't show the caret.
Element* focused_element = root->GetDocument().FocusedElement();
if (!focused_element)
return false;
} else {
// Caret is not contained in editable content--see if caret browsing is
// enabled. If it isn't, don't blink the caret.
// enabled. If it isn't, don't show the caret.
if (!frame_->IsCaretBrowsingEnabled())
return false;
}
// Only blink the caret if the selection has focus.
if (!IsEditablePosition(
selection_editor_->ComputeVisibleSelectionInDOMTree().Start()) &&
!frame_->IsCaretBrowsingEnabled())
return false;
// Only show the caret if the selection has focus.
return frame_->Selection().SelectionHasFocus();
}
void FrameCaret::CaretBlinkTimerFired(TimerBase*) {
DCHECK_EQ(caret_visibility_, CaretVisibility::kVisible);
if (IsCaretBlinkingSuspended() && should_paint_caret_)
DCHECK(is_caret_enabled_);
if (IsCaretBlinkingSuspended() && display_item_client_->IsVisibleIfActive())
return;
should_paint_caret_ = !should_paint_caret_;
display_item_client_->SetVisibleIfActive(
!display_item_client_->IsVisibleIfActive());
ScheduleVisualUpdateForPaintInvalidationIfNeeded();
}
......@@ -221,4 +223,8 @@ void FrameCaret::RecreateCaretBlinkTimerForTesting(
std::move(task_runner), this, &FrameCaret::CaretBlinkTimerFired));
}
bool FrameCaret::IsVisibleIfActiveForTesting() const {
return display_item_client_->IsVisibleIfActive();
}
} // namespace blink
......@@ -47,8 +47,6 @@ class SelectionEditor;
struct PaintInvalidatorContext;
struct PhysicalOffset;
enum class CaretVisibility { kVisible, kHidden };
class CORE_EXPORT FrameCaret final : public GarbageCollected<FrameCaret> {
public:
FrameCaret(LocalFrame&, const SelectionEditor&);
......@@ -65,7 +63,7 @@ class CORE_EXPORT FrameCaret final : public GarbageCollected<FrameCaret> {
bool IsCaretBlinkingSuspended() const { return is_caret_blinking_suspended_; }
void StopCaretBlinkTimer();
void StartBlinkCaret();
void SetCaretVisibility(CaretVisibility);
void SetCaretEnabled(bool);
IntRect AbsoluteCaretBounds() const;
bool ShouldShowBlockCursor() const { return should_show_block_cursor_; }
......@@ -83,7 +81,7 @@ class CORE_EXPORT FrameCaret final : public GarbageCollected<FrameCaret> {
const CaretDisplayItemClient& CaretDisplayItemClientForTesting() const {
return *display_item_client_;
}
bool ShouldPaintCaretForTesting() const { return should_paint_caret_; }
bool IsVisibleIfActiveForTesting() const;
void RecreateCaretBlinkTimerForTesting(
scoped_refptr<base::SingleThreadTaskRunner>);
......@@ -95,19 +93,19 @@ class CORE_EXPORT FrameCaret final : public GarbageCollected<FrameCaret> {
const PositionWithAffinity CaretPosition() const;
bool ShouldBlinkCaret() const;
bool ShouldShowCaret() const;
void CaretBlinkTimerFired(TimerBase*);
void UpdateAppearance();
const Member<const SelectionEditor> selection_editor_;
const Member<LocalFrame> frame_;
const std::unique_ptr<CaretDisplayItemClient> display_item_client_;
CaretVisibility caret_visibility_;
// TODO(https://crbug.com/668758): Consider using BeginFrame update for this.
std::unique_ptr<TaskRunnerTimer<FrameCaret>> caret_blink_timer_;
bool should_paint_caret_ : 1;
bool is_caret_blinking_suspended_ : 1;
bool should_show_block_cursor_ : 1;
bool is_caret_enabled_ = false;
bool should_show_caret_ = false;
bool is_caret_blinking_suspended_ = false;
bool should_show_block_cursor_ = false;
DISALLOW_COPY_AND_ASSIGN(FrameCaret);
};
......
......@@ -29,8 +29,8 @@ class FrameCaretTest : public EditingTestBase {
WebTestSupport::SetIsRunningWebTest(was_running_web_test_);
}
static bool ShouldBlinkCaret(const FrameCaret& caret) {
return caret.ShouldBlinkCaret();
static bool ShouldShowCaret(const FrameCaret& caret) {
return caret.ShouldShowCaret();
}
private:
......@@ -55,26 +55,26 @@ TEST_F(FrameCaretTest, BlinkAfterTyping) {
EXPECT_TRUE(caret.IsActive());
EXPECT_FALSE(caret.ShouldShowBlockCursor());
EXPECT_TRUE(caret.ShouldPaintCaretForTesting())
EXPECT_TRUE(caret.IsVisibleIfActiveForTesting())
<< "Initially a caret should be in visible cycle.";
task_runner->AdvanceTimeAndRun(kInterval);
EXPECT_FALSE(caret.ShouldPaintCaretForTesting())
EXPECT_FALSE(caret.IsVisibleIfActiveForTesting())
<< "The caret blinks normally.";
TypingCommand::InsertLineBreak(GetDocument());
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(caret.ShouldPaintCaretForTesting())
EXPECT_TRUE(caret.IsVisibleIfActiveForTesting())
<< "The caret should be in visible cycle just after a typing command.";
task_runner->AdvanceTimeAndRun(kInterval - 1);
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(caret.ShouldPaintCaretForTesting())
EXPECT_TRUE(caret.IsVisibleIfActiveForTesting())
<< "The typing command reset the timer. The caret is still visible.";
task_runner->AdvanceTimeAndRun(1);
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(caret.ShouldPaintCaretForTesting())
EXPECT_FALSE(caret.IsVisibleIfActiveForTesting())
<< "The caret should blink after the typing command.";
}
......@@ -94,18 +94,18 @@ TEST_F(FrameCaretTest, ShouldNotBlinkWhenSelectionLooseFocus) {
const SelectionInDOMTree& selection = Selection().GetSelectionInDOMTree();
EXPECT_EQ(selection.Base(),
Position(input, PositionAnchorType::kBeforeChildren));
EXPECT_FALSE(ShouldBlinkCaret(caret));
EXPECT_FALSE(ShouldShowCaret(caret));
}
TEST_F(FrameCaretTest, ShouldBlinkCaretWhileCaretBrowsing) {
FrameCaret& caret = Selection().FrameCaretForTesting();
Selection().SetSelection(SetSelectionTextToBody("<div>a|b</div>"),
SetSelectionOptions());
Selection().SetCaretVisible(true);
EXPECT_FALSE(ShouldBlinkCaret(caret));
Selection().SetCaretEnabled(true);
EXPECT_FALSE(ShouldShowCaret(caret));
GetDocument().GetFrame()->GetSettings()->SetCaretBrowsingEnabled(true);
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(ShouldBlinkCaret(caret));
EXPECT_TRUE(ShouldShowCaret(caret));
}
} // namespace blink
......@@ -874,9 +874,7 @@ void FrameSelection::FocusedOrActiveStateChanged() {
// Caret appears in the active frame.
if (active_and_focused)
SetSelectionFromNone();
frame_caret_->SetCaretVisibility(active_and_focused
? CaretVisibility::kVisible
: CaretVisibility::kHidden);
frame_caret_->SetCaretEnabled(active_and_focused);
// Update for caps lock state
frame_->GetEventHandler().CapsLockStateMayHaveChanged();
......@@ -1268,9 +1266,8 @@ void FrameSelection::MoveRangeSelectionInternal(
.Build());
}
void FrameSelection::SetCaretVisible(bool caret_is_visible) {
frame_caret_->SetCaretVisibility(caret_is_visible ? CaretVisibility::kVisible
: CaretVisibility::kHidden);
void FrameSelection::SetCaretEnabled(bool enabled) {
frame_caret_->SetCaretEnabled(enabled);
}
void FrameSelection::SetCaretBlinkingSuspended(bool suspended) {
......
......@@ -216,7 +216,7 @@ class CORE_EXPORT FrameSelection final
void DidLayout();
void CommitAppearanceIfNeeded();
void SetCaretVisible(bool caret_is_visible);
void SetCaretEnabled(bool caret_is_visible);
void ScheduleVisualUpdate() const;
void ScheduleVisualUpdateForPaintInvalidationIfNeeded() const;
......
......@@ -126,7 +126,7 @@ TEST_F(FrameSelectionTest, PaintCaretShouldNotLayout) {
GetDocument().body()->focus();
EXPECT_TRUE(GetDocument().body()->IsFocused());
Selection().SetCaretVisible(true);
Selection().SetCaretEnabled(true);
Selection().SetSelectionAndEndTyping(
SelectionInDOMTree::Builder().Collapse(Position(text, 0)).Build());
UpdateAllLifecyclePhasesForTest();
......
......@@ -370,7 +370,7 @@ void TextSuggestionController::OnSuggestionMenuClosed() {
GetDocument().Markers().RemoveMarkersOfTypes(
DocumentMarker::MarkerTypes::ActiveSuggestion());
GetFrame().Selection().SetCaretVisible(true);
GetFrame().Selection().SetCaretEnabled(true);
is_suggestion_menu_open_ = false;
}
......@@ -425,7 +425,7 @@ void TextSuggestionController::ShowSpellCheckMenu(
const String& description = marker->Description();
is_suggestion_menu_open_ = true;
GetFrame().Selection().SetCaretVisible(false);
GetFrame().Selection().SetCaretEnabled(false);
GetDocument().Markers().AddActiveSuggestionMarker(
active_suggestion_range, SK_ColorTRANSPARENT,
ui::mojom::ImeTextSpanThickness::kNone,
......@@ -499,7 +499,7 @@ void TextSuggestionController::ShowSuggestionMenu(
suggestion_infos_with_node_and_highlight_color.highlight_color);
is_suggestion_menu_open_ = true;
GetFrame().Selection().SetCaretVisible(false);
GetFrame().Selection().SetCaretEnabled(false);
const String& misspelled_word = PlainText(marker_range);
CallMojoShowTextSuggestionMenu(
......
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