Branch: refs/heads/main
  Home:   https://github.com/WebKit/WebKit
  Commit: 527353138b7cb1b6d6f8b5c8c9fe5bf52c63c266
      
https://github.com/WebKit/WebKit/commit/527353138b7cb1b6d6f8b5c8c9fe5bf52c63c266
  Author: Kristian Monsen <[email protected]>
  Date:   2026-08-20 (Thu, 20 Aug 2026)

  Changed paths:
    M LayoutTests/fast/speechsynthesis/speech-synthesis-speak-fuzzer-crash.html
    A 
LayoutTests/fast/speechsynthesis/speech-synthesis-stop-cross-document-utterance-crash-expected.txt
    A 
LayoutTests/fast/speechsynthesis/speech-synthesis-stop-cross-document-utterance-crash.html
    A 
LayoutTests/platform/mac/fast/speechsynthesis/speech-synthesis-speak-fuzzer-crash-expected.png
    M Source/WebCore/Modules/speech/SpeechSynthesis.cpp
    M Source/WebCore/Modules/speech/SpeechSynthesis.h

  Log Message:
  -----------
  SpeechSynthesis::stop() must not synchronously dispatch error events; fix 
crash when platform cancel() calls back synchronously
https://bugs.webkit.org/show_bug.cgi?id=315529
rdar://177692338

Reviewed by Per Arne Vollan, Zak Ridouh, and Chris Dumez.

ActiveDOMObject::stop() and suspend() are called from within 
ScriptExecutionContext::forEachActiveDOMObject, which holds a
ScriptDisallowedScope that prohibits JavaScript from running. The previous 
SpeechSynthesis::stop() and suspend() implementations both called
cancel(), which synchronously fires error events on every queued utterance. In 
a debug build this trips the ScriptDisallowedScope assertion;
in a release build it silently executes JS listeners during a scope that is 
supposed to forbid it.

The fix introduces stopPlatformSpeech(), called by both stop() and suspend(), 
which clears the utterance queue and nulls
m_currentSpeechUtterance without firing any events, then tells the 
platform/client to cancel. Any subsequent completion callbacks from the
platform find m_currentSpeechUtterance null and return early in 
handleSpeakingCompleted().

A secondary crash was found where PlatformSpeechSynthesizerMock::cancel() calls 
speakingErrorOccurred() synchronously, re-entering
handleSpeakingCompleted() before the caller has returned. Since 
stopPlatformSpeech() nulls m_currentSpeechUtterance before calling cancel, the
re-entrant call arrived with a null current utterance and hit the 
ASSERT(m_currentSpeechUtterance) in handleSpeakingCompleted(). The assert is
replaced with an early return for the null case.

Two layout tests are added: one verifying that speech is silently cancelled 
when a page enters the back/forward cache (triggering suspend()), and
one verifying no crash when stop() is called on a SpeechSynthesis that holds an 
utterance created in a different document. An existing fuzzer-crash
test is also fixed: it was previously passing only because a teardown crash 
happened to occur after notifyDone() was already called, masking the
crash. It is rewritten to wait for the utterance's end or error event before 
calling notifyDone(), so any crash in the speech completion path is
caught as a real failure.

Test: 
fast/speechsynthesis/speech-synthesis-stop-cross-document-utterance-crash.html

* LayoutTests/fast/speechsynthesis/speech-synthesis-speak-fuzzer-crash.html:
* 
LayoutTests/fast/speechsynthesis/speech-synthesis-stop-cross-document-utterance-crash-expected.txt:
 Added.
* 
LayoutTests/fast/speechsynthesis/speech-synthesis-stop-cross-document-utterance-crash.html:
 Added.
* 
LayoutTests/platform/mac/fast/speechsynthesis/speech-synthesis-speak-fuzzer-crash-expected.png:
 Added.
* Source/WebCore/Modules/speech/SpeechSynthesis.cpp:
(WebCore::SpeechSynthesis::handleSpeakingCompleted):
(WebCore::SpeechSynthesis::suspend):
(WebCore::SpeechSynthesis::stop):
(WebCore::SpeechSynthesis::stopPlatformSpeech):
* Source/WebCore/Modules/speech/SpeechSynthesis.h:

Originally-landed-as: [email protected] (f1fe25fdbdb4). 
rdar://184744736
Canonical link: https://commits.webkit.org/319573@main



To unsubscribe from these emails, change your notification settings at 
https://github.com/WebKit/WebKit/settings/notifications

Reply via email to