Skip to content

Commit

Permalink
Refactor caret animation out of FrameSelection
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=248112
rdar://102535758

Reviewed by Simon Fraser and Darin Adler.

Simplifies `FrameSelection` by factoring out the animation logic into its own
class, to fit with how we handle other animations. Instead of `FrameSelection`
owning its own caret blinking timer, the caret is now subject to the repaint cycle
of `Page::updateRendering`.

This PR introduces a new `CaretAnimation` virtual class, with a `CaretAnimationSimple`
subclass to model how the current caret blinking works. This inheritence
hierarchy is modelled after how `ScrollAnimation` works.

The CaretAnimation now owns a timer, which when fired, schedules a rendering
update for the caret to update its appearance. When the rendering update happens,
`CaretAnimation::serviceAnimation` is triggered, which performs the actual
adjustment to the caret's presentation's properties. `FrameSelection::invalidateCaretRect`
is then triggered which causes the actual painting of the care to reflect its
new appearance.

Note that because the animation is now tied to `Page::updateRendering`,
`CaretAnimation::serviceAnimation` may be called at any given time, and so the
animation must track when the last time it was called was and account for this.

* Source/WebCore/Headers.cmake:
* Source/WebCore/Sources.txt:
* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebCore/dom/Document.cpp:
(WebCore::Document::serviceCaretAnimation):
* Source/WebCore/dom/Document.h:
* Source/WebCore/editing/FrameSelection.cpp:
(WebCore::FrameSelection::FrameSelection):
(WebCore::FrameSelection::willBeRemovedFromFrame):
(WebCore::FrameSelection::paintCaret):
(WebCore::FrameSelection::setCaretBlinkingSuspended):
(WebCore::FrameSelection::isCaretBlinkingSuspended const):
(WebCore::FrameSelection::caretAnimationDidUpdate):
(WebCore::FrameSelection::document):
(WebCore::FrameSelection::updateAppearance):
(WebCore::FrameSelection::setCaretVisibility):
(WebCore::FrameSelection::setCaretColor):
(WebCore::FrameSelection::caretBlinkTimerFired): Deleted.
(WebCore::FrameSelection::setCaretBlinks): Deleted.
* Source/WebCore/editing/FrameSelection.h:
(WebCore::FrameSelection::currentCaretAnimator):
(WebCore::FrameSelection::currentCaretAnimator const):
(WebCore::FrameSelection::setCaretBlinkingSuspended): Deleted.
(WebCore::FrameSelection::isCaretBlinkingSuspended const): Deleted.
* Source/WebCore/page/Page.cpp:
(WebCore::Page::updateRendering):
(WebCore::Page::finalizeRenderingUpdate):
(WebCore::operator<<):
* Source/WebCore/page/Page.h:
* Source/WebCore/platform/CaretAnimator.cpp: Added.
(WebCore::CaretAnimator::page const):
(WebCore::CaretAnimator::serviceCaretAnimation):
(WebCore::CaretAnimator::scheduleAnimation):
* Source/WebCore/platform/CaretAnimator.h: Added.
(WebCore::CaretAnimationClient::caretAnimationDidUpdate):
(WebCore::CaretAnimationClient::caretAnimationWillStart):
(WebCore::CaretAnimationClient::caretAnimationDidEnd):
(WebCore::CaretAnimator::CaretAnimator):
(WebCore::CaretAnimator::stop):
(WebCore::CaretAnimator::isActive const):
(WebCore::CaretAnimator::setCaretBlinkingSuspended):
(WebCore::CaretAnimator::isCaretBlinkingSuspended const):
(WebCore::CaretAnimator::didStart):
(WebCore::CaretAnimator::didEnd):
(WebCore::CaretAnimator::timeSinceStart const):
* Source/WebCore/platform/SimpleCaretAnimator.cpp: Added.
(WebCore::SimpleCaretAnimator::SimpleCaretAnimator):
(WebCore::SimpleCaretAnimator::serviceAnimation):
(WebCore::SimpleCaretAnimator::start):
(WebCore::SimpleCaretAnimator::debugDescription const):
* Source/WebCore/platform/SimpleCaretAnimator.h: Added.

Canonical link: https://commits.webkit.org/257219@main
  • Loading branch information
rr-codes committed Dec 1, 2022
1 parent 3e0b8aa commit 6063669
Show file tree
Hide file tree
Showing 13 changed files with 378 additions and 65 deletions.
2 changes: 2 additions & 0 deletions Source/WebCore/Headers.cmake
Expand Up @@ -1331,6 +1331,7 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS
page/scrolling/ThreadedScrollingTree.h

platform/AbortableTaskQueue.h
platform/CaretAnimator.h
platform/CPUMonitor.h
platform/ColorChooser.h
platform/ColorChooserClient.h
Expand Down Expand Up @@ -1454,6 +1455,7 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS
platform/SharedBuffer.h
platform/SharedBufferChunkReader.h
platform/SharedStringHash.h
platform/SimpleCaretAnimator.h
platform/SleepDisabler.h
platform/SleepDisablerClient.h
platform/SleepDisablerIdentifier.h
Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/Sources.txt
Expand Up @@ -1927,6 +1927,7 @@ page/scrolling/ScrollingTreeStickyNode.cpp
page/scrolling/ThreadedScrollingCoordinator.cpp
page/scrolling/ThreadedScrollingTree.cpp
page/scrolling/ThreadedScrollingTreeScrollingNodeDelegate.cpp
platform/CaretAnimator.cpp
platform/CommonAtomStrings.cpp
platform/ContentType.cpp
platform/ContextMenu.cpp
Expand Down Expand Up @@ -2000,6 +2001,7 @@ platform/SerializedPlatformDataCue.cpp
platform/SharedBuffer.cpp
platform/SharedBufferChunkReader.cpp
platform/SharedStringHash.cpp
platform/SimpleCaretAnimator.cpp
platform/SleepDisabler.cpp
platform/SleepDisablerClient.cpp
platform/SystemSoundManager.cpp
Expand Down
12 changes: 12 additions & 0 deletions Source/WebCore/WebCore.xcodeproj/project.pbxproj
Expand Up @@ -122,6 +122,8 @@
073B87671E4385AC0071C0EC /* AudioSampleBufferList.h in Headers */ = {isa = PBXBuildFile; fileRef = 073B87631E43859D0071C0EC /* AudioSampleBufferList.h */; settings = {ATTRIBUTES = (Private, ); }; };
073B87691E4385AC0071C0EC /* AudioSampleDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 073B87651E43859D0071C0EC /* AudioSampleDataSource.h */; settings = {ATTRIBUTES = (Private, ); }; };
0740B14425DE31F800E38DBA /* MockMediaSessionCoordinator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0740B14325DE31A900E38DBA /* MockMediaSessionCoordinator.cpp */; };
0742C4FD2926C73C00D26990 /* CaretAnimator.h in Headers */ = {isa = PBXBuildFile; fileRef = 0742C4FB2926C73C00D26990 /* CaretAnimator.h */; settings = {ATTRIBUTES = (Private, ); }; };
0742C5052926E29500D26990 /* SimpleCaretAnimator.h in Headers */ = {isa = PBXBuildFile; fileRef = 0742C5032926E29500D26990 /* SimpleCaretAnimator.h */; settings = {ATTRIBUTES = (Private, ); }; };
074E82BB18A69F0E007EF54C /* PlatformTimeRanges.h in Headers */ = {isa = PBXBuildFile; fileRef = 074E82B918A69F0E007EF54C /* PlatformTimeRanges.h */; settings = {ATTRIBUTES = (Private, ); }; };
075033A8252BD36800F70CE3 /* VideoPlaybackQualityMetrics.h in Headers */ = {isa = PBXBuildFile; fileRef = 075033A6252BD36800F70CE3 /* VideoPlaybackQualityMetrics.h */; settings = {ATTRIBUTES = (Private, ); }; };
0753860314489E9800B78452 /* CachedTextTrack.h in Headers */ = {isa = PBXBuildFile; fileRef = 0753860114489E9800B78452 /* CachedTextTrack.h */; };
Expand Down Expand Up @@ -6258,6 +6260,10 @@
073B87651E43859D0071C0EC /* AudioSampleDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioSampleDataSource.h; sourceTree = "<group>"; };
0740B14125DE31A800E38DBA /* MockMediaSessionCoordinator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockMediaSessionCoordinator.h; sourceTree = "<group>"; };
0740B14325DE31A900E38DBA /* MockMediaSessionCoordinator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MockMediaSessionCoordinator.cpp; sourceTree = "<group>"; };
0742C4FA2926C73C00D26990 /* CaretAnimator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CaretAnimator.cpp; sourceTree = "<group>"; };
0742C4FB2926C73C00D26990 /* CaretAnimator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CaretAnimator.h; sourceTree = "<group>"; };
0742C5022926E29500D26990 /* SimpleCaretAnimator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SimpleCaretAnimator.cpp; sourceTree = "<group>"; };
0742C5032926E29500D26990 /* SimpleCaretAnimator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SimpleCaretAnimator.h; sourceTree = "<group>"; };
074471FD25E180050054B231 /* MediaSessionReadyState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaSessionReadyState.h; sourceTree = "<group>"; };
074471FF25E180050054B231 /* MediaSessionReadyState.idl */ = {isa = PBXFileReference; lastKnownFileType = text; path = MediaSessionReadyState.idl; sourceTree = "<group>"; };
0744ECEB1E0C4AE5000D0944 /* MockAudioSharedUnit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MockAudioSharedUnit.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -31022,6 +31028,8 @@
E1EE8B6B2412B2A700E794D6 /* xr */,
DFDB912CF8E88A6DA1AD264F /* AbortableTaskQueue.h */,
E3A86FC12695EB480059264D /* CaptionPreferencesDelegate.h */,
0742C4FA2926C73C00D26990 /* CaretAnimator.cpp */,
0742C4FB2926C73C00D26990 /* CaretAnimator.h */,
C330A22113EC196B0000B45B /* ColorChooser.h */,
C37CDEBC149EF2030042090D /* ColorChooserClient.h */,
BCC8CFCA0986CD2400140BF2 /* ColorData.gperf */,
Expand Down Expand Up @@ -31232,6 +31240,8 @@
834DFACE1F7DAE5700C2725B /* SharedStringHash.cpp */,
834DFACC1F7DAE5600C2725B /* SharedStringHash.h */,
93309EA0099EB78C0056E581 /* SharedTimer.h */,
0742C5022926E29500D26990 /* SimpleCaretAnimator.cpp */,
0742C5032926E29500D26990 /* SimpleCaretAnimator.h */,
C149D559242EA4F8003EBB12 /* SleepDisabler.cpp */,
C149D55A242EA4F9003EBB12 /* SleepDisabler.h */,
C18FB518242F9382007E9875 /* SleepDisablerClient.cpp */,
Expand Down Expand Up @@ -35009,6 +35019,7 @@
079D086B162F21F900DB8658 /* CaptionUserPreferencesMediaAF.h in Headers */,
07B7116D1D899E63009F0FFB /* CaptureDevice.h in Headers */,
07B7116F1D899E63009F0FFB /* CaptureDeviceManager.h in Headers */,
0742C4FD2926C73C00D26990 /* CaretAnimator.h in Headers */,
E4F0BE3125712F6E009E7431 /* CaretRectComputation.h in Headers */,
CDC734151977896D0046BFC5 /* CARingBuffer.h in Headers */,
E4ABABF52368C6EF00FA4345 /* CascadeLevel.h in Headers */,
Expand Down Expand Up @@ -38636,6 +38647,7 @@
1C4DB02627339FE0007B0AD1 /* ShouldLocalizeAxisNames.h in Headers */,
DF19E2AC24772BC1007BDACB /* ShouldRelaxThirdPartyCookieBlocking.h in Headers */,
8362E8C120CEF9CB00245886 /* ShouldTreatAsContinuingLoad.h in Headers */,
0742C5052926E29500D26990 /* SimpleCaretAnimator.h in Headers */,
9316DDFB240C64B4009340AA /* SimpleRange.h in Headers */,
C5A1EA7D152BCF08004D00B6 /* SimplifyMarkupCommand.h in Headers */,
572A7F211C6E5719009C6149 /* SimulatedClick.h in Headers */,
Expand Down
6 changes: 6 additions & 0 deletions Source/WebCore/dom/Document.cpp
Expand Up @@ -6904,6 +6904,12 @@ void Document::serviceRequestAnimationFrameCallbacks()
m_scriptedAnimationController->serviceRequestAnimationFrameCallbacks(domWindow()->frozenNowTimestamp());
}

void Document::serviceCaretAnimation()
{
if (auto* window = domWindow())
selection().caretAnimator().serviceCaretAnimation(window->frozenNowTimestamp());
}

void Document::serviceRequestVideoFrameCallbacks()
{
#if ENABLE(VIDEO)
Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/dom/Document.h
Expand Up @@ -1151,6 +1151,8 @@ class Document
void serviceRequestAnimationFrameCallbacks();
void serviceRequestVideoFrameCallbacks();

void serviceCaretAnimation();

void windowScreenDidChange(PlatformDisplayID);

void finishedParsing();
Expand Down
88 changes: 36 additions & 52 deletions Source/WebCore/editing/FrameSelection.cpp
Expand Up @@ -27,8 +27,10 @@
#include "FrameSelection.h"

#include "AXObjectCache.h"
#include "CaretAnimator.h"
#include "CharacterData.h"
#include "ColorBlending.h"
#include "DOMWindow.h"
#include "DeleteSelectionCommand.h"
#include "DocumentInlines.h"
#include "Editing.h"
Expand Down Expand Up @@ -69,6 +71,7 @@
#include "RenderedPosition.h"
#include "ScriptDisallowedScope.h"
#include "Settings.h"
#include "SimpleCaretAnimator.h"
#include "SimpleRange.h"
#include "SpatialNavigation.h"
#include "StyleProperties.h"
Expand Down Expand Up @@ -163,22 +166,17 @@ static inline bool isPageActive(Document* document)
FrameSelection::FrameSelection(Document* document)
: m_document(document)
, m_granularity(TextGranularity::CharacterGranularity)
#if ENABLE(TEXT_CARET)
, m_caretBlinkTimer(*this, &FrameSelection::caretBlinkTimerFired)
#endif
, m_appearanceUpdateTimer(*this, &FrameSelection::appearanceUpdateTimerFired)
, m_caretAnimator(makeUniqueRef<SimpleCaretAnimator>(*this))
, m_caretInsidePositionFixed(false)
, m_absCaretBoundsDirty(true)
, m_caretPaint(true)
, m_isCaretBlinkingSuspended(false)
, m_focused(document && document->frame() && document->page() && document->page()->focusController().focusedFrame() == document->frame())
, m_isActive(isPageActive(document))
, m_shouldShowBlockCursor(false)
, m_pendingSelectionUpdate(false)
, m_alwaysAlignCursorOnScrollWhenRevealingSelection(false)
#if PLATFORM(IOS_FAMILY)
, m_updateAppearanceEnabled(false)
, m_caretBlinks(true)
#endif
{
if (shouldAlwaysUseDirectionalSelection(m_document.get()))
Expand All @@ -195,6 +193,8 @@ FrameSelection::FrameSelection(Document* document)
#endif
}

FrameSelection::~FrameSelection() = default;

Element* FrameSelection::rootEditableElementOrDocumentElement() const
{
Element* selectionRoot = m_selection.rootEditableElement();
Expand Down Expand Up @@ -1641,7 +1641,7 @@ void FrameSelection::willBeRemovedFromFrame()
m_granularity = TextGranularity::CharacterGranularity;

#if ENABLE(TEXT_CARET)
m_caretBlinkTimer.stop();
caretAnimator().stop();
#endif

if (auto* view = m_document->renderView())
Expand Down Expand Up @@ -1839,7 +1839,7 @@ void CaretBase::invalidateCaretRect(Node* node, bool caretRectChanged)

void FrameSelection::paintCaret(GraphicsContext& context, const LayoutPoint& paintOffset, const LayoutRect& clipRect)
{
if (m_selection.isCaret() && m_caretPaint && m_selection.start().deprecatedNode())
if (m_selection.isCaret() && caretAnimator().isVisible() && m_selection.start().deprecatedNode())
CaretBase::paintCaret(*m_selection.start().deprecatedNode(), context, paintOffset, clipRect);
}

Expand Down Expand Up @@ -1895,6 +1895,26 @@ void CaretBase::paintCaret(const Node& node, GraphicsContext& context, const Lay
#endif
}

void FrameSelection::setCaretBlinkingSuspended(bool suspended)
{
caretAnimator().setBlinkingSuspended(suspended);
}

bool FrameSelection::isCaretBlinkingSuspended() const
{
return caretAnimator().isBlinkingSuspended();
}

void FrameSelection::caretAnimationDidUpdate(CaretAnimator&)
{
invalidateCaretRect();
}

Document* FrameSelection::document()
{
return m_document.get();
}

void FrameSelection::debugRenderer(RenderObject* renderer, bool selected) const
{
if (is<Element>(*renderer->node())) {
Expand Down Expand Up @@ -2229,18 +2249,15 @@ void FrameSelection::updateAppearance()
// If the caret moved, stop the blink timer so we can restart with a
// black caret in the new location.
if (caretRectChangedOrCleared || !shouldBlink || shouldStopBlinkingDueToTypingCommand(m_document.get()))
m_caretBlinkTimer.stop();
caretAnimator().stop();

// Start blinking with a black caret. Be sure not to restart if we're
// already blinking in the right location.
if (shouldBlink && !m_caretBlinkTimer.isActive()) {
if (Seconds blinkInterval = RenderTheme::singleton().caretBlinkInterval())
m_caretBlinkTimer.startRepeating(blinkInterval);
if (shouldBlink && !caretAnimator().isActive()) {
if (m_document && m_document->domWindow())
caretAnimator().start(m_document->domWindow()->nowTimestamp());

if (!m_caretPaint) {
m_caretPaint = true;
invalidateCaretRect();
}
caretAnimator().setVisible(true);
}
#endif

Expand Down Expand Up @@ -2299,31 +2316,15 @@ void FrameSelection::setCaretVisibility(CaretVisibility visibility, ShouldUpdate
updateSelectionAppearanceNow();

#if ENABLE(TEXT_CARET)
if (m_caretPaint) {
m_caretPaint = false;
invalidateCaretRect();
}
caretAnimator().setVisible(false);

CaretBase::setCaretVisibility(visibility);
#endif

if (doAppearanceUpdate == ShouldUpdateAppearance::Yes)
updateAppearance();
}

void FrameSelection::caretBlinkTimerFired()
{
#if ENABLE(TEXT_CARET)
if (!isCaret())
return;
ASSERT(caretIsVisible());
bool caretPaint = m_caretPaint;
if (isCaretBlinkingSuspended() && caretPaint)
return;
m_caretPaint = !caretPaint;
invalidateCaretRect();
#endif
}

// Helper function that tells whether a particular node is an element that has an entire
// Frame and FrameView, a <frame>, <iframe>, or <object>.
static bool isFrameElement(const Node* n)
Expand Down Expand Up @@ -2870,28 +2871,11 @@ void FrameSelection::clearCurrentSelection()
setSelection(VisibleSelection());
}

void FrameSelection::setCaretBlinks(bool caretBlinks)
{
if (m_caretBlinks == caretBlinks)
return;
#if ENABLE(TEXT_CARET)
m_document->updateLayoutIgnorePendingStylesheets();
if (m_caretPaint) {
m_caretPaint = false;
invalidateCaretRect();
}
#endif
if (caretBlinks)
setFocusedElementIfNeeded();
m_caretBlinks = caretBlinks;
updateAppearance();
}

void FrameSelection::setCaretColor(const Color& caretColor)
{
if (m_caretColor != caretColor) {
m_caretColor = caretColor;
if (caretIsVisible() && m_caretBlinks && isCaret())
if (caretIsVisible() && isCaret())
invalidateCaretRect();
}
}
Expand Down

0 comments on commit 6063669

Please sign in to comment.