Skip to content

Commit

Permalink
Add initial support for JS SpeechSynthesisErrorEvent
Browse files Browse the repository at this point in the history
1) Add new type SpeechSynthesisErrorEvent and expose it to JS
2) Extend PlatformSpeechSynthesizer speakingErrorOccured with error type arg
3) Send proper JS event on error condiditon
4) Stick to using older SpeechSynthesisEvent for platforms
   that doesn't support ErrorEvent yeti (mac)
  • Loading branch information
asurdej-comcast committed Apr 6, 2022
1 parent adec775 commit 0abbe61
Show file tree
Hide file tree
Showing 14 changed files with 231 additions and 16 deletions.
1 change: 1 addition & 0 deletions Source/WebCore/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ set(WebCore_NON_SVG_IDL_FILES
Modules/speech/DOMWindowSpeechSynthesis.idl
Modules/speech/SpeechSynthesis.idl
Modules/speech/SpeechSynthesisEvent.idl
Modules/speech/SpeechSynthesisErrorEvent.idl
Modules/speech/SpeechSynthesisUtterance.idl
Modules/speech/SpeechSynthesisVoice.idl

Expand Down
43 changes: 37 additions & 6 deletions Source/WebCore/Modules/speech/SpeechSynthesis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,32 @@
#include "EventNames.h"
#include "PlatformSpeechSynthesisVoice.h"
#include "PlatformSpeechSynthesizer.h"
#include "SpeechSynthesisErrorEvent.h"
#include "SpeechSynthesisEvent.h"
#include "SpeechSynthesisUtterance.h"
#include "UserGestureIndicator.h"
#include <wtf/NeverDestroyed.h>

namespace {
WebCore::SpeechSynthesisErrorEvent::Code toSpeechSynthesisErrorEventCode(WebCore::SpeechError error) {
switch(error) {
case WebCore::SpeechErrorCanceled: return WebCore::SpeechSynthesisErrorEvent::Code::Canceled;
case WebCore::SpeechErrorInterrupted: return WebCore::SpeechSynthesisErrorEvent::Code::Interrupted;
case WebCore::SpeechErrorAudioBusy: return WebCore::SpeechSynthesisErrorEvent::Code::AudioBusy;
case WebCore::SpeechErrorAudioHardware: return WebCore::SpeechSynthesisErrorEvent::Code::AudioHardware;
case WebCore::SpeechErrorNetwork: return WebCore::SpeechSynthesisErrorEvent::Code::Network;
case WebCore::SpeechErrorSynthesisUnavailable: return WebCore::SpeechSynthesisErrorEvent::Code::SynthesisUnavailable;
case WebCore::SpeechErrorSynthesisFailed: return WebCore::SpeechSynthesisErrorEvent::Code::SynthesisFailed;
case WebCore::SpeechErrorLanguageUnavailable: return WebCore::SpeechSynthesisErrorEvent::Code::LanguageUnavailable;
case WebCore::SpeechErrorVoiceUnavailable: return WebCore::SpeechSynthesisErrorEvent::Code::VoiceUnavailable;
case WebCore::SpeechErrorTextTooLong: return WebCore::SpeechSynthesisErrorEvent::Code::TextTooLong;
case WebCore::SpeechErrorInvalidArgument: return WebCore::SpeechSynthesisErrorEvent::Code::InvalidArgument;
case WebCore::SpeechErrorNotAllowed: return WebCore::SpeechSynthesisErrorEvent::Code::NotAllowed;
default: ASSERT(false, "Invalid SpeechError code"); return WebCore::SpeechSynthesisErrorEvent::Code::Interrupted;
}
}
} // namespace

namespace WebCore {

Ref<SpeechSynthesis> SpeechSynthesis::create(WeakPtr<SpeechSynthesisClient> client)
Expand Down Expand Up @@ -185,14 +206,24 @@ void SpeechSynthesis::fireEvent(const AtomString& type, SpeechSynthesisUtterance
utterance.dispatchEvent(SpeechSynthesisEvent::create(type, charIndex, (MonotonicTime::now() - utterance.startTime()).seconds(), name));
}

void SpeechSynthesis::handleSpeakingCompleted(SpeechSynthesisUtterance& utterance, bool errorOccurred)
void SpeechSynthesis::fireErrorEvent(SpeechSynthesisUtterance& utterance, SpeechError error)
{
utterance.dispatchEvent(SpeechSynthesisErrorEvent::create(toSpeechSynthesisErrorEventCode(error)));
}

void SpeechSynthesis::handleSpeakingCompleted(SpeechSynthesisUtterance& utterance, bool errorOccurred, SpeechError error)
{
ASSERT(m_currentSpeechUtterance);
Ref<SpeechSynthesisUtterance> protect(utterance);

m_currentSpeechUtterance = nullptr;
if (m_currentSpeechUtterance == &utterance)
m_currentSpeechUtterance = nullptr;

fireEvent(errorOccurred ? eventNames().errorEvent : eventNames().endEvent, utterance, 0, String());
// For older PlatformSpeechSynthesizers that doesn't support ErrorEvent yet
if (!errorOccurred || error == SpeechErrorNone)
fireEvent(errorOccurred ? eventNames().errorEvent : eventNames().endEvent, utterance, 0, String());
else
fireErrorEvent(utterance, error);

if (m_utteranceQueue.size()) {
Ref<SpeechSynthesisUtterance> firstUtterance = m_utteranceQueue.takeFirst();
Expand Down Expand Up @@ -255,7 +286,7 @@ void SpeechSynthesis::speakingErrorOccurred()
{
if (!m_currentSpeechUtterance)
return;
speakingErrorOccurred(*m_currentSpeechUtterance->platformUtterance());
speakingErrorOccurred(*m_currentSpeechUtterance->platformUtterance(), SpeechErrorNone);
}

void SpeechSynthesis::boundaryEventOccurred(bool wordBoundary, unsigned charIndex)
Expand Down Expand Up @@ -296,10 +327,10 @@ void SpeechSynthesis::didFinishSpeaking(PlatformSpeechSynthesisUtterance& uttera
handleSpeakingCompleted(static_cast<SpeechSynthesisUtterance&>(*utterance.client()), false);
}

void SpeechSynthesis::speakingErrorOccurred(PlatformSpeechSynthesisUtterance& utterance)
void SpeechSynthesis::speakingErrorOccurred(PlatformSpeechSynthesisUtterance& utterance, SpeechError error)
{
if (utterance.client())
handleSpeakingCompleted(static_cast<SpeechSynthesisUtterance&>(*utterance.client()), true);
handleSpeakingCompleted(static_cast<SpeechSynthesisUtterance&>(*utterance.client()), true, error);
}

} // namespace WebCore
Expand Down
5 changes: 3 additions & 2 deletions Source/WebCore/Modules/speech/SpeechSynthesis.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class SpeechSynthesis : public PlatformSpeechSynthesizerClient, public SpeechSyn
void didPauseSpeaking(PlatformSpeechSynthesisUtterance&) override;
void didResumeSpeaking(PlatformSpeechSynthesisUtterance&) override;
void didFinishSpeaking(PlatformSpeechSynthesisUtterance&) override;
void speakingErrorOccurred(PlatformSpeechSynthesisUtterance&) override;
void speakingErrorOccurred(PlatformSpeechSynthesisUtterance&, SpeechError) override;
void boundaryEventOccurred(PlatformSpeechSynthesisUtterance&, SpeechBoundary, unsigned charIndex) override;

// SpeechSynthesisClient override methods
Expand All @@ -80,8 +80,9 @@ class SpeechSynthesis : public PlatformSpeechSynthesizerClient, public SpeechSyn
void voicesChanged() override;

void startSpeakingImmediately(SpeechSynthesisUtterance&);
void handleSpeakingCompleted(SpeechSynthesisUtterance&, bool errorOccurred);
void handleSpeakingCompleted(SpeechSynthesisUtterance&, bool errorOccurred, SpeechError error = SpeechErrorNone);
void fireEvent(const AtomString& type, SpeechSynthesisUtterance&, unsigned long charIndex, const String& name);
void fireErrorEvent(SpeechSynthesisUtterance&, SpeechError);

#if PLATFORM(IOS_FAMILY)
// Restrictions to change default behaviors.
Expand Down
53 changes: 53 additions & 0 deletions Source/WebCore/Modules/speech/SpeechSynthesisErrorEvent.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* Copyright (C) 2019 RDK Management. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "config.h"
#include "SpeechSynthesisErrorEvent.h"

#if ENABLE(SPEECH_SYNTHESIS)

#include "EventNames.h"
#include <wtf/IsoMallocInlines.h>

namespace WebCore {

WTF_MAKE_ISO_ALLOCATED_IMPL(SpeechSynthesisErrorEvent);

Ref<SpeechSynthesisErrorEvent> SpeechSynthesisErrorEvent::create(Code error)
{
return adoptRef(*new SpeechSynthesisErrorEvent(error));
}

SpeechSynthesisErrorEvent::SpeechSynthesisErrorEvent(Code error)
: SpeechSynthesisEvent(eventNames().errorEvent, 0, 0.0, String()), m_error(error)
{
}

SpeechSynthesisErrorEvent::Code SpeechSynthesisErrorEvent::error() const {
return m_error;
}

} // namespace WebCore

#endif // ENABLE(SPEECH_SYNTHESIS)
63 changes: 63 additions & 0 deletions Source/WebCore/Modules/speech/SpeechSynthesisErrorEvent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* Copyright (C) 2019 RDK Management. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#pragma once

#if ENABLE(SPEECH_SYNTHESIS)

#include "SpeechSynthesisEvent.h"

namespace WebCore {

class SpeechSynthesisErrorEvent final : public SpeechSynthesisEvent {
WTF_MAKE_ISO_ALLOCATED(SpeechSynthesisErrorEvent);
public:
enum class Code {
Canceled,
Interrupted,
AudioBusy,
AudioHardware,
Network,
SynthesisUnavailable,
SynthesisFailed,
LanguageUnavailable,
VoiceUnavailable,
TextTooLong,
InvalidArgument,
NotAllowed
};

static Ref<SpeechSynthesisErrorEvent> create(const Code error);
virtual EventInterface eventInterface() const { return SpeechSynthesisErrorEventInterfaceType; }
Code error() const;

private:
SpeechSynthesisErrorEvent(const Code error);

Code m_error;
};

} // namespace WebCore

#endif // ENABLE(SPEECH_SYNTHESIS)
46 changes: 46 additions & 0 deletions Source/WebCore/Modules/speech/SpeechSynthesisErrorEvent.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* Copyright (C) 2019 RDK Management. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// https://w3c.github.io/speech-api/#enumdef-speechsynthesiserrorcode
[
Conditional=SPEECH_SYNTHESIS,
] enum SpeechSynthesisErrorCode {
"canceled",
"interrupted",
"audio-busy",
"audio-hardware",
"network",
"synthesis-unavailable",
"synthesis-failed",
"language-unavailable",
"voice-unavailable",
"text-too-long",
"invalid-argument",
"not-allowed"
};

[
Conditional=SPEECH_SYNTHESIS
] interface SpeechSynthesisErrorEvent : SpeechSynthesisEvent {
readonly attribute SpeechSynthesisErrorCode error;
};
5 changes: 3 additions & 2 deletions Source/WebCore/Modules/speech/SpeechSynthesisEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

namespace WebCore {

class SpeechSynthesisEvent final : public Event {
class SpeechSynthesisEvent : public Event {
WTF_MAKE_ISO_ALLOCATED(SpeechSynthesisEvent);
public:
static Ref<SpeechSynthesisEvent> create(const AtomString& type, unsigned charIndex, float elapsedTime, const String& name);
Expand All @@ -42,9 +42,10 @@ class SpeechSynthesisEvent final : public Event {

virtual EventInterface eventInterface() const { return SpeechSynthesisEventInterfaceType; }

private:
protected:
SpeechSynthesisEvent(const AtomString& type, unsigned charIndex, float elapsedTime, const String& name);

private:
unsigned long m_charIndex;
float m_elapsedTime;
String m_name;
Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/Sources.txt
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ Modules/remoteplayback/RemotePlayback.cpp
Modules/speech/DOMWindowSpeechSynthesis.cpp
Modules/speech/SpeechSynthesis.cpp
Modules/speech/SpeechSynthesisEvent.cpp
Modules/speech/SpeechSynthesisErrorEvent.cpp
Modules/speech/SpeechSynthesisUtterance.cpp
Modules/speech/SpeechSynthesisVoice.cpp

Expand Down Expand Up @@ -3412,6 +3413,7 @@ JSSourceBuffer.cpp
JSSourceBufferList.cpp
JSSpeechSynthesis.cpp
JSSpeechSynthesisEvent.cpp
JSSpeechSynthesisErrorEvent.cpp
JSSpeechSynthesisUtterance.cpp
JSSpeechSynthesisVoice.cpp
JSStaticRange.cpp
Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/dom/EventNames.in
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ RTCDTMFToneChangeEvent conditional=WEB_RTC
RTCPeerConnectionIceEvent conditional=WEB_RTC
RTCTrackEvent conditional=WEB_RTC
SpeechSynthesisEvent conditional=SPEECH_SYNTHESIS
SpeechSynthesisErrorEvent conditional=SPEECH_SYNTHESIS
WebGLContextEvent conditional=WEBGL
StorageEvent
SVGEvents interfaceName=Event
Expand Down
18 changes: 17 additions & 1 deletion Source/WebCore/platform/PlatformSpeechSynthesizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@ enum class SpeechBoundary : uint8_t {
SpeechSentenceBoundary
};

enum SpeechError {
SpeechErrorNone,
SpeechErrorCanceled,
SpeechErrorInterrupted,
SpeechErrorAudioBusy,
SpeechErrorAudioHardware,
SpeechErrorNetwork,
SpeechErrorSynthesisUnavailable,
SpeechErrorSynthesisFailed,
SpeechErrorLanguageUnavailable,
SpeechErrorVoiceUnavailable,
SpeechErrorTextTooLong,
SpeechErrorInvalidArgument,
SpeechErrorNotAllowed
};

class PlatformSpeechSynthesisUtterance;

class PlatformSpeechSynthesizerClient {
Expand All @@ -51,7 +67,7 @@ class PlatformSpeechSynthesizerClient {
virtual void didFinishSpeaking(PlatformSpeechSynthesisUtterance&) = 0;
virtual void didPauseSpeaking(PlatformSpeechSynthesisUtterance&) = 0;
virtual void didResumeSpeaking(PlatformSpeechSynthesisUtterance&) = 0;
virtual void speakingErrorOccurred(PlatformSpeechSynthesisUtterance&) = 0;
virtual void speakingErrorOccurred(PlatformSpeechSynthesisUtterance&, SpeechError) = 0;
virtual void boundaryEventOccurred(PlatformSpeechSynthesisUtterance&, SpeechBoundary, unsigned charIndex) = 0;
virtual void voicesDidChange() = 0;
protected:
Expand Down
4 changes: 2 additions & 2 deletions Source/WebCore/platform/mac/PlatformSpeechSynthesizerMac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ - (void)cancel
return;

[m_synthesizer stopSpeakingAtBoundary:NSSpeechImmediateBoundary];
m_synthesizerObject->client()->speakingErrorOccurred(*m_utterance);
m_synthesizerObject->client()->speakingErrorOccurred(*m_utterance, WebCore::SpeechErrorNone);
m_utterance = 0;
}

Expand All @@ -185,7 +185,7 @@ - (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)
if (finishedSpeaking)
m_synthesizerObject->client()->didFinishSpeaking(*utterance);
else
m_synthesizerObject->client()->speakingErrorOccurred(*utterance);
m_synthesizerObject->client()->speakingErrorOccurred(*utterance, WebCore::SpeechErrorNone);
}

- (void)speechSynthesizer:(NSSpeechSynthesizer *)sender willSpeakWord:(NSRange)characterRange ofString:(NSString *)string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ void PlatformSpeechSynthesizerMock::cancel()
return;

m_speakingFinishedTimer.stop();
client()->speakingErrorOccurred(*m_utterance);
client()->speakingErrorOccurred(*m_utterance, SpeechErrorCanceled);
m_utterance = nullptr;
}

Expand Down
2 changes: 1 addition & 1 deletion Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@
speechSynthesisData().speakingResumedCompletionHandler();
}

void WebPageProxy::speakingErrorOccurred(WebCore::PlatformSpeechSynthesisUtterance&)
void WebPageProxy::speakingErrorOccurred(WebCore::PlatformSpeechSynthesisUtterance&, WebCore::SpeechError)
{
process().send(Messages::WebPage::SpeakingErrorOccurred(), m_webPageID);
}
Expand Down
2 changes: 1 addition & 1 deletion Source/WebKit/UIProcess/WebPageProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -2225,7 +2225,7 @@ class WebPageProxy : public API::ObjectImpl<API::Object::Type::Page>
void didFinishSpeaking(WebCore::PlatformSpeechSynthesisUtterance&) override;
void didPauseSpeaking(WebCore::PlatformSpeechSynthesisUtterance&) override;
void didResumeSpeaking(WebCore::PlatformSpeechSynthesisUtterance&) override;
void speakingErrorOccurred(WebCore::PlatformSpeechSynthesisUtterance&) override;
void speakingErrorOccurred(WebCore::PlatformSpeechSynthesisUtterance&, WebCore::SpeechError) override;
void boundaryEventOccurred(WebCore::PlatformSpeechSynthesisUtterance&, WebCore::SpeechBoundary, unsigned charIndex) override;
void voicesDidChange() override;

Expand Down

0 comments on commit 0abbe61

Please sign in to comment.