← WebKit — 2026-W37

436386a065b7f98c6935d6e48068bcb8cef2560e  [Writing Tools] A final text animation with no unanimated range crashes the UI process

severity medium class InputValidation confidence 0.5 signal score 8.76 Source/WebKit/UIProcess
Primitive: WebContent sending invalid/unset animation UUIDs in TextAnimationData to WebPageProxy::addTextAnimationForAnimationID
Triage note: Adds MESSAGE_CHECK(isValidTextAnimationData) requiring a valid destinationAnimationUUID for Source animations and makes unanimatedRangeUUID a Markable guarded before deref (was *data.unanimatedRangeUUID). IPC-validation hardening of Writing Tools text-animation data from the web process.
Structural signals: smartptr_member_added (+4.87), null_guard (+2.59), surgical (+1.3)

Commit message

[Writing Tools] A final text animation with no unanimated range crashes the UI process
https://bugs.webkit.org/show_bug.cgi?id=323917
rdar://185195580

Reviewed by Chris Dumez.

Markable<T> uses one value of T as its empty marker rather than a separate bool,
and MarkableTraits<UUID>::isEmptyValue is !uuid. An engaged Markable<WTF::UUID>
holding UUID::emptyValue is therefore indistinguishable from a disengaged one.
TextAnimationController sends exactly that value for a Final animation whenever
it cannot resolve an end-of-editable-content boundary for the session range:
endOfEditableContent() yields a null VisiblePosition once highestEditableRoot()
is null, which leaves unanimatedRangeUUID at its UUID::emptyValue initializer. A
page that drops contenteditable while the asynchronous replacement is in flight
gets there. The two blocks the Mac text animation manager builds then captured
that value with *data.unanimatedRangeUUID and hit the RELEASE_ASSERT in
Markable::operator*() — at block construction, before any effect ran. The
receiver was converted to Markable in 293636@main; the sender was not.

This commit handles the empty range instead of asserting on it:

- WKTextAnimationManagerMac — capture the Markable and skip
  updateUnderlyingTextVisibilityForTextAnimationID when it is empty. Sending it
  would not have worked in any case: the message takes a bare WTF::UUID, whose
  decoder has rejected emptyValue since 293636@main, so it would have been an
  invalid message rather than the no-op the web process side would have made
  of it, in either direction.
- WKWebView — _didEndPartialIntelligenceTextAnimation no longer asserts when the
  count is zero. A Restart action resets it while animations are in flight, so
  their ends need not pair up with an increment. The web process reaches this
  directly too, with no animation running at all, on every early return in
  addDestinationTextAnimationForActiveWritingToolsSession. The early return was
  already correct, so Release was fine.
- WebPageProxy — a Source animation's destination UUID is dereferenced as a hash
  map key on iOS and is always generated by WritingToolsController, so an empty
  one only comes from a malformed message and is rejected with MESSAGE_CHECK.
  The check is not conditioned on the platform or the run mode, since a Source
  animation without a destination is malformed everywhere.

Tests: ipc/add-text-animation-missing-uuid-crash.html
       ipc/did-end-partial-intelligence-text-animation-crash.html

* LayoutTests/ipc/add-text-animation-missing-uuid-crash-expected.txt: Added.
* LayoutTests/ipc/add-text-animation-missing-uuid-crash.html: Added.
* LayoutTests/ipc/did-end-partial-intelligence-text-animation-crash-expected.txt: Added.
* LayoutTests/ipc/did-end-partial-intelligence-text-animation-crash.html: Added.
* Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _didEndPartialIntelligenceTextAnimation]):
* Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm:
(WebKit::isValidTextAnimationData):
(WebKit::WebPageProxy::addTextAnimationForAnimationIDWithCompletionHandler):
* Source/WebKit/UIProcess/mac/WKTextAnimationManagerMac.mm:
(-[WKTextAnimationManager addTextAnimationForAnimationID:withData:]):

Canonical link: https://commits.webkit.org/320997@main

Before / after

Loading diff…