Skip to content

Commit

Permalink
AX: Adopt non-blinking cursor API
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=268920
rdar://118550285

Reviewed by Tyler Wilcock.

This patch adopts a new accessibility API, _AXSPrefersNonBlinkingCursorIndicator, to
turns off caret blinking animations when enabled.

A new test was also added to verify this behavior.

* LayoutTests/accessibility/mac/prefers-non-blinking-cursor-expected.txt: Added.
* LayoutTests/accessibility/mac/prefers-non-blinking-cursor.html: Added.
* LayoutTests/platform/mac/TestExpectations:
* Source/WTF/wtf/PlatformEnable.h:
* Source/WebCore/editing/FrameSelection.cpp:
(WebCore::FrameSelection::setPrefersNonBlinkingCursor):
* Source/WebCore/editing/FrameSelection.h:
* Source/WebCore/page/Page.cpp:
(WebCore::Page::setPrefersNonBlinkingCursor):
* Source/WebCore/page/Page.h:
(WebCore::Page::prefersNonBlinkingCursor const):
* Source/WebCore/platform/CaretAnimator.cpp:
(WebCore::CaretAnimator::isBlinkingSuspended const):
* Source/WebCore/platform/CaretAnimator.h:
(WebCore::CaretAnimator::setPrefersNonBlinkingCursor):
(WebCore::CaretAnimator::prefersNonBlinkingCursor const):
(WebCore::CaretAnimator::CaretAnimator):
(WebCore::CaretAnimator::isBlinkingSuspended const): Deleted.
* Source/WebCore/platform/DictationCaretAnimator.cpp:
(WebCore::DictationCaretAnimator::expandedCaretRect const):
* Source/WebCore/testing/Internals.cpp:
(WebCore::Internals::setPrefersNonBlinkingCursor):
(WebCore::Internals::isCaretBlinkingSuspended):
* Source/WebCore/testing/Internals.h:
* Source/WebCore/testing/Internals.idl:
* Source/WebKit/Platform/spi/Cocoa/AccessibilitySupportSPI.h:
* Source/WebKit/Shared/AccessibilityPreferences.h:
* Source/WebKit/Shared/AccessibilityPreferences.serialization.in:
* Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm:
(WebKit::accessibilityPreferences):
(WebKit::WebProcessPool::registerNotificationObservers):
* Source/WebKit/WebProcess/WebPage/WebPage.cpp:
(WebKit::m_unifiedTextReplacementController):
(WebKit::WebPage::updatePrefersNonBlinkingCursor):
* Source/WebKit/WebProcess/WebPage/WebPage.h:
* Source/WebKit/WebProcess/WebProcess.h:
(WebKit::WebProcess::prefersNonBlinkingCursor const):
* Source/WebKit/WebProcess/cocoa/WebProcessCocoa.mm:
(WebKit::WebProcess::accessibilityPreferencesDidChange):
(WebKit::WebProcess::updatePageAccessibilitySettings):

Canonical link: https://commits.webkit.org/274792@main
  • Loading branch information
hoffmanjoshua committed Feb 16, 2024
1 parent ef44165 commit 322e588
Show file tree
Hide file tree
Showing 22 changed files with 211 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
This tests that the prefers non-blinking cursor setting stops and starts caret blinking as expected.

PASS: internals.isCaretBlinkingSuspended(document) === false
PASS: internals.isCaretBlinkingSuspended(document) === true
PASS: internals.isCaretBlinkingSuspended(document) === false
PASS: internals.isCaretBlinkingSuspended(iframe.contentDocument) === false
PASS: internals.isCaretBlinkingSuspended(iframe.contentDocument) === true
PASS: internals.isCaretBlinkingSuspended(iframe.contentDocument) === false

PASS successfullyParsed is true

TEST COMPLETE

43 changes: 43 additions & 0 deletions LayoutTests/accessibility/mac/prefers-non-blinking-cursor.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../../resources/accessibility-helper.js"></script>
<script src="../../resources/js-test.js"></script>
</head>
<body>

<input id="textinput" type="text" />
<iframe id="iframe" onload="startTest()" src="data:text/html,<body><input id='iframe-input' type='text' /></body>">
</iframe>

<script>
var output = "This tests that the prefers non-blinking cursor setting stops and starts caret blinking as expected.\n\n";
const iframe = document.getElementById("iframe");
window.jsTestIsAsync = true;

function startTest() {
document.getElementById("textinput").focus();
output += expect("internals.isCaretBlinkingSuspended(document)", "false");

internals.setPrefersNonBlinkingCursor(true);
output += expect("internals.isCaretBlinkingSuspended(document)", "true");

internals.setPrefersNonBlinkingCursor(false);
output += expect("internals.isCaretBlinkingSuspended(document)", "false");

iframe.contentDocument.getElementById("iframe-input").focus();
output += expect("internals.isCaretBlinkingSuspended(iframe.contentDocument)", "false");

internals.setPrefersNonBlinkingCursor(true);
output += expect("internals.isCaretBlinkingSuspended(iframe.contentDocument)", "true");

internals.setPrefersNonBlinkingCursor(false);
output += expect("internals.isCaretBlinkingSuspended(iframe.contentDocument)", "false");

debug(output);
finishJSTest();
}
</script>
</body>
</html>

3 changes: 3 additions & 0 deletions LayoutTests/platform/mac/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -2575,5 +2575,8 @@ webkit.org/b/267352 [ Monterey ] imported/w3c/web-platform-tests/mathml/relation

webkit.org/b/267612 [ Ventura+ Release x86_64 ] fast/canvas/image-buffer-backend-variants.html [ Pass Failure ]

# Requires (ACCESSIBILITY_NON_BLINKING_CURSOR)
accessibility/mac/prefers-non-blinking-cursor.html [ Skip ]

# Enable after rdar://120859525 is available on bots
http/tests/media/fairplay/fps-mse-unmuxed-mpts.html [ Skip ]
4 changes: 4 additions & 0 deletions Source/WTF/wtf/PlatformEnable.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@
#define ENABLE_ACCESSIBILITY_ANIMATION_CONTROL 0
#endif

#if !defined(ENABLE_ACCESSIBILITY_NON_BLINKING_CURSOR)
#define ENABLE_ACCESSIBILITY_NON_BLINKING_CURSOR 0
#endif

#if !defined(ENABLE_ADVANCED_PRIVACY_PROTECTIONS)
#define ENABLE_ADVANCED_PRIVACY_PROTECTIONS 0
#endif
Expand Down
7 changes: 7 additions & 0 deletions Source/WebCore/editing/FrameSelection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1973,6 +1973,13 @@ void FrameSelection::caretAnimationDidUpdate(CaretAnimator&)
invalidateCaretRect();
}

#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
void FrameSelection::setPrefersNonBlinkingCursor(bool enabled)
{
caretAnimator().setPrefersNonBlinkingCursor(enabled);
}
#endif

#if PLATFORM(MAC)
void FrameSelection::caretAnimatorInvalidated(CaretAnimatorType caretType)
{
Expand Down
4 changes: 4 additions & 0 deletions Source/WebCore/editing/FrameSelection.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ class FrameSelection final : private CaretBase, public CaretAnimationClient, pub
WEBCORE_EXPORT void setCaretBlinkingSuspended(bool);
WEBCORE_EXPORT bool isCaretBlinkingSuspended() const;

#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
WEBCORE_EXPORT void setPrefersNonBlinkingCursor(bool);
#endif

WEBCORE_EXPORT void setFocused(bool);
bool isFocused() const { return m_focused; }
WEBCORE_EXPORT bool isFocusedAndActive() const;
Expand Down
7 changes: 7 additions & 0 deletions Source/WebCore/page/Page.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2239,6 +2239,13 @@ void Page::setSystemAllowsAnimationControls(bool isAllowed)
}
#endif // ENABLE(ACCESSIBILITY_ANIMATION_CONTROL)

#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
void Page::setPrefersNonBlinkingCursor(bool enabled)
{
m_prefersNonBlinkingCursor = enabled;
}
#endif

void Page::suspendScriptedAnimations()
{
m_scriptedAnimationsSuspended = true;
Expand Down
8 changes: 8 additions & 0 deletions Source/WebCore/page/Page.h
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,11 @@ class Page : public RefCounted<Page>, public Supplementable<Page>, public CanMak
bool imageAnimationEnabled() const { return m_imageAnimationEnabled; }
bool systemAllowsAnimationControls() const { return m_systemAllowsAnimationControls; }

#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
WEBCORE_EXPORT void setPrefersNonBlinkingCursor(bool);
bool prefersNonBlinkingCursor() const { return m_prefersNonBlinkingCursor; };
#endif

void userStyleSheetLocationChanged();
const String& userStyleSheet() const;

Expand Down Expand Up @@ -1257,6 +1262,9 @@ class Page : public RefCounted<Page>, public Supplementable<Page>, public CanMak
bool m_systemAllowsAnimationControls { false };
// Elements containing animations that are individually playing (potentially overriding the page-wide m_imageAnimationEnabled state).
WeakHashSet<HTMLImageElement, WeakPtrImplWithEventTargetData> m_individuallyPlayingAnimationElements;
#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
bool m_prefersNonBlinkingCursor { false };
#endif

TimerThrottlingState m_timerThrottlingState { TimerThrottlingState::Disabled };
MonotonicTime m_timerThrottlingStateLastChangedTime;
Expand Down
9 changes: 9 additions & 0 deletions Source/WebCore/platform/CaretAnimator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@

namespace WebCore {

bool CaretAnimator::isBlinkingSuspended() const
{
#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
if (m_prefersNonBlinkingCursor)
return true;
#endif
return m_isBlinkingSuspended;
}

Page* CaretAnimator::page() const
{
if (auto* document = m_client.document())
Expand Down
16 changes: 14 additions & 2 deletions Source/WebCore/platform/CaretAnimator.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,12 @@ class CaretAnimator {
virtual String debugDescription() const = 0;

virtual void setBlinkingSuspended(bool suspended) { m_isBlinkingSuspended = suspended; }
bool isBlinkingSuspended() const { return m_isBlinkingSuspended; }
bool isBlinkingSuspended() const;

#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
void setPrefersNonBlinkingCursor(bool enabled) { m_prefersNonBlinkingCursor = enabled; }
bool prefersNonBlinkingCursor() const { return m_prefersNonBlinkingCursor; }
#endif

virtual void setVisible(bool) = 0;

Expand All @@ -110,7 +115,11 @@ class CaretAnimator {
explicit CaretAnimator(CaretAnimationClient& client)
: m_client(client)
, m_blinkTimer(*this, &CaretAnimator::scheduleAnimation)
{ }
{
#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
m_prefersNonBlinkingCursor = page() && page()->prefersNonBlinkingCursor();
#endif
}

virtual void updateAnimationProperties() = 0;

Expand Down Expand Up @@ -141,6 +150,9 @@ class CaretAnimator {

bool m_isActive { false };
bool m_isBlinkingSuspended { false };
#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
bool m_prefersNonBlinkingCursor { false };
#endif
};

static inline CaretAnimator::PresentationProperties::BlinkState operator!(CaretAnimator::PresentationProperties::BlinkState blinkState)
Expand Down
4 changes: 4 additions & 0 deletions Source/WebCore/platform/DictationCaretAnimator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,11 @@ FloatRoundedRect DictationCaretAnimator::expandedCaretRect(const FloatRect& rect
auto pulseExpansion = 1.f;
if (m_initialScale > 0.f)
extraScaleFactor = 1.f + 1.4f * sinf(2.f * m_initialScale);
#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
else if (!prefersNonBlinkingCursor())
#else
else
#endif
pulseExpansion = 0.75f * m_presentationProperties.opacity * extraScaleFactor;

float horizontalPulseExpansion = 0.5f * pulseExpansion;
Expand Down
27 changes: 24 additions & 3 deletions Source/WebCore/testing/Internals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,19 @@ void Internals::pauseImageAnimation(HTMLImageElement& element)
}
#endif // ENABLE(ACCESSIBILITY_ANIMATION_CONTROL)

#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
void Internals::setPrefersNonBlinkingCursor(bool enabled)
{
auto* document = contextDocument();
if (RefPtr page = document ? document->page() : nullptr) {
page->setPrefersNonBlinkingCursor(enabled);
page->forEachDocument([&](auto& document) {
document.selection().setPrefersNonBlinkingCursor(enabled);
});
}
}
#endif

unsigned Internals::imagePendingDecodePromisesCountForTesting(HTMLImageElement& element)
{
return element.pendingDecodePromisesCountForTesting();
Expand Down Expand Up @@ -1876,11 +1889,19 @@ ExceptionOr<Ref<DOMRect>> Internals::absoluteCaretBounds()

ExceptionOr<bool> Internals::isCaretBlinkingSuspended()
{
Document* document = contextDocument();
if (!document || !document->frame())
auto* document = contextDocument();
if (!document)
return Exception { ExceptionCode::InvalidAccessError };

return document->frame()->selection().isCaretBlinkingSuspended();
return isCaretBlinkingSuspended(*document);
}

ExceptionOr<bool> Internals::isCaretBlinkingSuspended(Document& document)
{
if (!document.frame())
return Exception { ExceptionCode::InvalidAccessError };

return document.frame()->selection().isCaretBlinkingSuspended();
}

Ref<DOMRect> Internals::boundingBox(Element& element)
Expand Down
5 changes: 5 additions & 0 deletions Source/WebCore/testing/Internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,11 @@ class Internals final : public RefCounted<Internals>, private ContextDestruction

ExceptionOr<Ref<DOMRect>> absoluteCaretBounds();
ExceptionOr<bool> isCaretBlinkingSuspended();
ExceptionOr<bool> isCaretBlinkingSuspended(Document&);

#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
void setPrefersNonBlinkingCursor(bool);
#endif

Ref<DOMRect> boundingBox(Element&);

Expand Down
6 changes: 6 additions & 0 deletions Source/WebCore/testing/Internals.idl
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,13 @@ typedef (FetchRequest or FetchResponse) FetchObject;
DOMRect absoluteLineRectFromPoint(long x, long y);

DOMRect absoluteCaretBounds();

// isCaretBlinkingSuspended() returns whether the frame selection of the context document
// is suspended, while the parameterized method returns the state for a particular document
// (such as an iFrame, for example).
boolean isCaretBlinkingSuspended();
boolean isCaretBlinkingSuspended(Document document);
[Conditional=ACCESSIBILITY_NON_BLINKING_CURSOR] undefined setPrefersNonBlinkingCursor(boolean enabled);

DOMRect boundingBox(Element element);

Expand Down
3 changes: 3 additions & 0 deletions Source/WebKit/Platform/spi/Cocoa/AccessibilitySupportSPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ void _AXSInvertColorsSetEnabledApp(AXValueState enabled, CFStringRef appID);
extern CFStringRef kAXSReduceMotionAutoplayAnimatedImagesChangedNotification;
extern Boolean _AXSReduceMotionAutoplayAnimatedImagesEnabled(void);

extern CFStringRef kAXSPrefersNonBlinkingCursorIndicatorDidChangeNotification;
extern Boolean _AXSPrefersNonBlinkingCursorIndicator(void);

extern CFStringRef kAXSFullKeyboardAccessEnabledNotification;
Boolean _AXSFullKeyboardAccessEnabled();

Expand Down
3 changes: 3 additions & 0 deletions Source/WebKit/Shared/AccessibilityPreferences.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ struct AccessibilityPreferences {
#endif
bool imageAnimationEnabled { true };
bool enhanceTextLegibilityOverall { false };
#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
bool prefersNonBlinkingCursor { false };
#endif
};

} // namespace WebKit
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,8 @@ struct WebKit::AccessibilityPreferences {
#endif
bool imageAnimationEnabled;
bool enhanceTextLegibilityOverall;
#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
bool prefersNonBlinkingCursor;
#endif
};

6 changes: 6 additions & 0 deletions Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ static AccessibilityPreferences accessibilityPreferences()
#if ENABLE(ACCESSIBILITY_ANIMATION_CONTROL)
if (auto* functionPointer = _AXSReduceMotionAutoplayAnimatedImagesEnabledPtr())
preferences.imageAnimationEnabled = functionPointer();
#endif
#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
preferences.prefersNonBlinkingCursor = _AXSPrefersNonBlinkingCursorIndicator();
#endif
return preferences;
}
Expand Down Expand Up @@ -866,6 +869,9 @@ static void logProcessPoolState(const WebProcessPool& pool)
if (canLoadkAXSReduceMotionAutoplayAnimatedImagesChangedNotification())
addCFNotificationObserver(accessibilityPreferencesChangedCallback, getkAXSReduceMotionAutoplayAnimatedImagesChangedNotification());
#endif
#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
addCFNotificationObserver(accessibilityPreferencesChangedCallback, kAXSPrefersNonBlinkingCursorIndicatorDidChangeNotification);
#endif
#if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
addCFNotificationObserver(mediaAccessibilityPreferencesChangedCallback, kMAXCaptionAppearanceSettingsChangedNotification);
#endif
Expand Down
15 changes: 15 additions & 0 deletions Source/WebKit/WebProcess/WebPage/WebPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,9 @@ WebPage::WebPage(PageIdentifier pageID, WebPageCreationParameters&& parameters)
#if ENABLE(ACCESSIBILITY_ANIMATION_CONTROL)
updateImageAnimationEnabled();
#endif
#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
updatePrefersNonBlinkingCursor();
#endif
#if ENABLE(ADVANCED_PRIVACY_PROTECTIONS)
setLinkDecorationFilteringData(WTFMove(parameters.linkDecorationFilteringData));
setAllowedQueryParametersForAdvancedPrivacyProtections(WTFMove(parameters.allowedQueryParametersForAdvancedPrivacyProtections));
Expand Down Expand Up @@ -8968,6 +8971,18 @@ void WebPage::playAllAnimations(CompletionHandler<void()>&& completionHandler)
}
#endif // ENABLE(ACCESSIBILITY_ANIMATION_CONTROL)

#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
void WebPage::updatePrefersNonBlinkingCursor()
{
if (RefPtr page = corePage()) {
page->setPrefersNonBlinkingCursor(WebProcess::singleton().prefersNonBlinkingCursor());
page->forEachDocument([&](auto& document) {
document.selection().setPrefersNonBlinkingCursor(WebProcess::singleton().prefersNonBlinkingCursor());
});
}
}
#endif

bool WebPage::isUsingUISideCompositing() const
{
#if PLATFORM(COCOA)
Expand Down
4 changes: 4 additions & 0 deletions Source/WebKit/WebProcess/WebPage/WebPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,10 @@ class WebPage : public API::ObjectImpl<API::Object::Type::BundlePage>, public IP
void isAnyAnimationAllowedToPlayDidChange(bool /* anyAnimationCanPlay */);
#endif

#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
void updatePrefersNonBlinkingCursor();
#endif

bool shouldSkipDecidePolicyForResponse(const WebCore::ResourceResponse&) const;
void setSkipDecidePolicyForResponseIfPossible(bool value) { m_skipDecidePolicyForResponseIfPossible = value; }

Expand Down
7 changes: 7 additions & 0 deletions Source/WebKit/WebProcess/WebProcess.h
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,9 @@ class WebProcess : public AuxiliaryProcess

bool isLockdownModeEnabled() const { return m_isLockdownModeEnabled; }
bool imageAnimationEnabled() const { return m_imageAnimationEnabled; }
#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
bool prefersNonBlinkingCursor() const { return m_prefersNonBlinkingCursor; }
#endif

void setHadMainFrameMainResourcePrivateRelayed() { m_hadMainFrameMainResourcePrivateRelayed = true; }
bool hadMainFrameMainResourcePrivateRelayed() const { return m_hadMainFrameMainResourcePrivateRelayed; }
Expand Down Expand Up @@ -629,6 +632,7 @@ class WebProcess : public AuxiliaryProcess
#endif

void accessibilityPreferencesDidChange(const AccessibilityPreferences&);
void updatePageAccessibilitySettings();
#if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
void setMediaAccessibilityPreferences(WebCore::CaptionUserPreferences::CaptionDisplayMode, const Vector<String>&);
#endif
Expand Down Expand Up @@ -844,6 +848,9 @@ class WebProcess : public AuxiliaryProcess
bool m_imageAnimationEnabled { true };
bool m_hasEverHadAnyWebPages { false };
bool m_hasPendingAccessibilityUnsuspension { false };
#if ENABLE(ACCESSIBILITY_NON_BLINKING_CURSOR)
bool m_prefersNonBlinkingCursor { false };
#endif

HashSet<WebCore::RegistrableDomain> m_allowedFirstPartiesForCookies;
String m_mediaKeysStorageDirectory;
Expand Down
Loading

0 comments on commit 322e588

Please sign in to comment.