From 0d4683513caf3a8f13ec7632b51b0d99909261d8 Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Tue, 6 Jun 2023 06:28:14 +0000 Subject: [PATCH] Bug 1432719 - Notify user on speechd errors r=eeejay,fluent-reviewers,flod Differential Revision: https://phabricator.services.mozilla.com/D176532 --- browser/actors/SpeechDispatcherChild.sys.mjs | 10 +++ browser/actors/SpeechDispatcherParent.sys.mjs | 90 +++++++++++++++++++ browser/actors/moz.build | 2 + browser/components/BrowserGlue.sys.mjs | 14 +++ .../en-US/browser/speechDispatcher.ftl | 15 ++++ dom/media/webspeech/synth/SpeechSynthesis.cpp | 18 ++++ .../webspeech/synth/ipc/PSpeechSynthesis.ipdl | 2 + .../synth/ipc/SpeechSynthesisChild.cpp | 6 ++ .../synth/ipc/SpeechSynthesisChild.h | 2 + .../webspeech/synth/nsISynthVoiceRegistry.idl | 5 ++ .../webspeech/synth/nsSynthVoiceRegistry.cpp | 30 +++++++ .../webspeech/synth/nsSynthVoiceRegistry.h | 2 + .../synth/speechd/SpeechDispatcherService.cpp | 20 +++++ .../synth/speechd/SpeechDispatcherService.h | 2 + 14 files changed, 218 insertions(+) create mode 100644 browser/actors/SpeechDispatcherChild.sys.mjs create mode 100644 browser/actors/SpeechDispatcherParent.sys.mjs create mode 100644 browser/locales/en-US/browser/speechDispatcher.ftl diff --git a/browser/actors/SpeechDispatcherChild.sys.mjs b/browser/actors/SpeechDispatcherChild.sys.mjs new file mode 100644 index 000000000000..1184d7244679 --- /dev/null +++ b/browser/actors/SpeechDispatcherChild.sys.mjs @@ -0,0 +1,10 @@ +/* vim: set ts=2 sw=2 sts=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +export class SpeechDispatcherChild extends JSWindowActorChild { + observe(aSubject, aTopic, aData) { + this.sendAsyncMessage("SpeechDispatcher:Error", aData); + } +} diff --git a/browser/actors/SpeechDispatcherParent.sys.mjs b/browser/actors/SpeechDispatcherParent.sys.mjs new file mode 100644 index 000000000000..40ddf0b3c4da --- /dev/null +++ b/browser/actors/SpeechDispatcherParent.sys.mjs @@ -0,0 +1,90 @@ +/* vim: set ts=2 sw=2 sts=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +export class SpeechDispatcherParent extends JSWindowActorParent { + prefName() { + return "media.webspeech.synth.dont_notify_on_error"; + } + + disableNotification() { + Services.prefs.setBoolPref(this.prefName(), true); + } + + async receiveMessage(aMessage) { + // The top level browsing context's embedding element should be a xul browser element. + let browser = this.browsingContext.top.embedderElement; + + if (!browser) { + // We don't have a browser so bail! + return; + } + + let notificationId; + + if (Services.prefs.getBoolPref(this.prefName(), false)) { + console.info("Opted out from speech-dispatcher error notification"); + return; + } + + let messageId; + switch (aMessage.data) { + case "lib-missing": + messageId = "speech-dispatcher-lib-missing"; + break; + + case "lib-too-old": + messageId = "speech-dispatcher-lib-too-old"; + break; + + case "missing-symbol": + messageId = "speech-dispatcher-missing-symbol"; + break; + + case "open-fail": + messageId = "speech-dispatcher-open-fail"; + break; + + case "no-voices": + messageId = "speech-dispatcher-no-voices"; + break; + + default: + break; + } + + let MozXULElement = browser.ownerGlobal.MozXULElement; + MozXULElement.insertFTLIfNeeded("browser/speechDispatcher.ftl"); + + // Now actually create the notification + let notificationBox = browser.getTabBrowser().getNotificationBox(browser); + if (notificationBox.getNotificationWithValue(notificationId)) { + return; + } + + let buttons = [ + { + supportPage: "speechd-setup", + }, + { + "l10n-id": "speech-dispatcher-dismiss-button", + callback: () => { + this.disableNotification(); + }, + }, + ]; + + let iconURL = "chrome://browser/skin/drm-icon.svg"; + notificationBox.appendNotification( + notificationId, + { + label: { "l10n-id": messageId }, + image: iconURL, + priority: notificationBox.PRIORITY_INFO_HIGH, + type: "warning", + }, + buttons + ); + } +} diff --git a/browser/actors/moz.build b/browser/actors/moz.build index af01262d32c8..282d00d7565d 100644 --- a/browser/actors/moz.build +++ b/browser/actors/moz.build @@ -82,6 +82,8 @@ FINAL_TARGET_FILES.actors += [ "ScreenshotsComponentChild.sys.mjs", "SearchSERPTelemetryChild.sys.mjs", "SearchSERPTelemetryParent.sys.mjs", + "SpeechDispatcherChild.sys.mjs", + "SpeechDispatcherParent.sys.mjs", "SwitchDocumentDirectionChild.sys.mjs", "WebRTCChild.sys.mjs", "WebRTCParent.sys.mjs", diff --git a/browser/components/BrowserGlue.sys.mjs b/browser/components/BrowserGlue.sys.mjs index 92acb0c98ca4..89177a168354 100644 --- a/browser/components/BrowserGlue.sys.mjs +++ b/browser/components/BrowserGlue.sys.mjs @@ -752,6 +752,20 @@ let JSWINDOWACTORS = { matches: ["about:studies*"], }, + SpeechDispatcher: { + parent: { + esModuleURI: "resource:///actors/SpeechDispatcherParent.sys.mjs", + }, + + child: { + esModuleURI: "resource:///actors/SpeechDispatcherChild.sys.mjs", + observers: ["chrome-synth-voices-error"], + }, + + messageManagerGroups: ["browsers"], + allFrames: true, + }, + ASRouter: { parent: { esModuleURI: "resource:///actors/ASRouterParent.sys.mjs", diff --git a/browser/locales/en-US/browser/speechDispatcher.ftl b/browser/locales/en-US/browser/speechDispatcher.ftl new file mode 100644 index 000000000000..761c73afe943 --- /dev/null +++ b/browser/locales/en-US/browser/speechDispatcher.ftl @@ -0,0 +1,15 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +### Speech Dispatches is the name of a speech synthesis tool and shouldn’t be +### localized (https://freebsoft.org/speechd). + +speech-dispatcher-lib-missing = You can’t use speech synthesis because the Speech Dispatcher library is missing. +speech-dispatcher-lib-too-old = You can’t use speech synthesis because Speech Dispatcher needs to be updated. +speech-dispatcher-missing-symbol = You can’t use speech synthesis because the Speech Dispatcher library is broken. +speech-dispatcher-open-fail = You can’t use speech synthesis because Speech Dispatcher won’t open. +speech-dispatcher-no-voices = You can’t use speech synthesis because voices aren’t available in Speech Dispatcher. +speech-dispatcher-dismiss-button = + .label = Don’t show again + .accesskey = D diff --git a/dom/media/webspeech/synth/SpeechSynthesis.cpp b/dom/media/webspeech/synth/SpeechSynthesis.cpp index 20e3ef754bed..f26e36a3f4a7 100644 --- a/dom/media/webspeech/synth/SpeechSynthesis.cpp +++ b/dom/media/webspeech/synth/SpeechSynthesis.cpp @@ -65,6 +65,7 @@ SpeechSynthesis::SpeechSynthesis(nsPIDOMWindowInner* aParent) if (obs) { obs->AddObserver(this, "inner-window-destroyed", true); obs->AddObserver(this, "synth-voices-changed", true); + obs->AddObserver(this, "synth-voices-error", true); } } @@ -307,6 +308,23 @@ SpeechSynthesis::Observe(nsISupports* aSubject, const char* aTopic, AdvanceQueue(); } } + } else if (strcmp(aTopic, "synth-voices-error") == 0) { + NS_WARNING("SpeechSynthesis::Observe: synth-voices-error"); + LOG(LogLevel::Debug, ("SpeechSynthesis::onvoiceserror")); + nsCOMPtr window = GetOwner(); + + nsCOMPtr obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(window, "chrome-synth-voices-error", aData); + } + + if (!mSpeechQueue.IsEmpty()) { + for (RefPtr& utterance : mSpeechQueue) { + utterance->DispatchSpeechSynthesisEvent(u"error"_ns, 0, nullptr, 0, + u""_ns); + } + mSpeechQueue.Clear(); + } } return NS_OK; diff --git a/dom/media/webspeech/synth/ipc/PSpeechSynthesis.ipdl b/dom/media/webspeech/synth/ipc/PSpeechSynthesis.ipdl index 38e360bf4c93..897c9e787974 100644 --- a/dom/media/webspeech/synth/ipc/PSpeechSynthesis.ipdl +++ b/dom/media/webspeech/synth/ipc/PSpeechSynthesis.ipdl @@ -36,6 +36,8 @@ child: async NotifyVoicesChanged(); + async NotifyVoicesError(nsString aError); + async InitialVoicesAndState(RemoteVoice[] aVoices, nsString[] aDefaults, bool aIsSpeaking); diff --git a/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.cpp b/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.cpp index 9a9e9b6fe292..ff28d0c4183d 100644 --- a/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.cpp +++ b/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.cpp @@ -52,6 +52,12 @@ mozilla::ipc::IPCResult SpeechSynthesisChild::RecvNotifyVoicesChanged() { return IPC_OK(); } +mozilla::ipc::IPCResult SpeechSynthesisChild::RecvNotifyVoicesError( + const nsAString& aError) { + nsSynthVoiceRegistry::RecvNotifyVoicesError(aError); + return IPC_OK(); +} + PSpeechSynthesisRequestChild* SpeechSynthesisChild::AllocPSpeechSynthesisRequestChild( const nsAString& aText, const nsAString& aLang, const nsAString& aUri, diff --git a/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.h b/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.h index f57582932aae..459855ee8655 100644 --- a/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.h +++ b/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.h @@ -36,6 +36,8 @@ class SpeechSynthesisChild : public PSpeechSynthesisChild { mozilla::ipc::IPCResult RecvNotifyVoicesChanged(); + mozilla::ipc::IPCResult RecvNotifyVoicesError(const nsAString& aError); + protected: SpeechSynthesisChild(); virtual ~SpeechSynthesisChild(); diff --git a/dom/media/webspeech/synth/nsISynthVoiceRegistry.idl b/dom/media/webspeech/synth/nsISynthVoiceRegistry.idl index 8dd3a0426c75..1898bf68c180 100644 --- a/dom/media/webspeech/synth/nsISynthVoiceRegistry.idl +++ b/dom/media/webspeech/synth/nsISynthVoiceRegistry.idl @@ -38,6 +38,11 @@ interface nsISynthVoiceRegistry : nsISupports */ void notifyVoicesChanged(); + /** + * Notify chrome code of an error when starting speech synthesis service + */ + void notifyVoicesError(in AString aError); + /** * Set a voice as default. * diff --git a/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp b/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp index d289c816558b..c4781661f3bc 100644 --- a/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp +++ b/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp @@ -282,6 +282,15 @@ void nsSynthVoiceRegistry::RecvNotifyVoicesChanged() { gSynthVoiceRegistry->NotifyVoicesChanged(); } +void nsSynthVoiceRegistry::RecvNotifyVoicesError(const nsAString& aError) { + // If we dont have a local instance of the registry yet, we don't care. + if (!gSynthVoiceRegistry) { + return; + } + + gSynthVoiceRegistry->NotifyVoicesError(aError); +} + NS_IMETHODIMP nsSynthVoiceRegistry::AddVoice(nsISpeechService* aService, const nsAString& aUri, const nsAString& aName, @@ -369,6 +378,27 @@ nsSynthVoiceRegistry::NotifyVoicesChanged() { return NS_OK; } +NS_IMETHODIMP +nsSynthVoiceRegistry::NotifyVoicesError(const nsAString& aError) { + if (XRE_IsParentProcess()) { + nsTArray ssplist; + GetAllSpeechSynthActors(ssplist); + + for (uint32_t i = 0; i < ssplist.Length(); ++i) { + Unused << ssplist[i]->SendNotifyVoicesError(aError); + } + } + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (NS_WARN_IF(!(obs))) { + return NS_ERROR_NOT_AVAILABLE; + } + + obs->NotifyObservers(nullptr, "synth-voices-error", aError.BeginReading()); + + return NS_OK; +} + NS_IMETHODIMP nsSynthVoiceRegistry::SetDefaultVoice(const nsAString& aUri, bool aIsDefault) { bool found = false; diff --git a/dom/media/webspeech/synth/nsSynthVoiceRegistry.h b/dom/media/webspeech/synth/nsSynthVoiceRegistry.h index 85c67c087f6d..ba2b57d0d3a7 100644 --- a/dom/media/webspeech/synth/nsSynthVoiceRegistry.h +++ b/dom/media/webspeech/synth/nsSynthVoiceRegistry.h @@ -65,6 +65,8 @@ class nsSynthVoiceRegistry final : public nsISynthVoiceRegistry { static void RecvNotifyVoicesChanged(); + static void RecvNotifyVoicesError(const nsAString& aError); + private: virtual ~nsSynthVoiceRegistry(); diff --git a/dom/media/webspeech/synth/speechd/SpeechDispatcherService.cpp b/dom/media/webspeech/synth/speechd/SpeechDispatcherService.cpp index e0d548874832..c0944cf24f1e 100644 --- a/dom/media/webspeech/synth/speechd/SpeechDispatcherService.cpp +++ b/dom/media/webspeech/synth/speechd/SpeechDispatcherService.cpp @@ -322,6 +322,7 @@ void SpeechDispatcherService::Setup() { if (!speechdLib) { NS_WARNING("Failed to load speechd library"); + NotifyError(u"lib-missing"_ns); return; } @@ -329,6 +330,7 @@ void SpeechDispatcherService::Setup() { // There is no version getter function, so we rely on a symbol that was // introduced in release 0.8.2 in order to check for ABI compatibility. NS_WARNING("Unsupported version of speechd detected"); + NotifyError(u"lib-too-old"_ns); return; } @@ -340,6 +342,7 @@ void SpeechDispatcherService::Setup() { NS_WARNING(nsPrintfCString("Failed to find speechd symbol for'%s'", kSpeechDispatcherSymbols[i].functionName) .get()); + NotifyError(u"missing-symbol"_ns); return; } } @@ -348,6 +351,7 @@ void SpeechDispatcherService::Setup() { spd_open("firefox", "web speech api", "who", SPD_MODE_THREADED); if (!mSpeechdClient) { NS_WARNING("Failed to call spd_open"); + NotifyError(u"open-fail"_ns); return; } @@ -386,6 +390,10 @@ void SpeechDispatcherService::Setup() { } } + if (mVoices.Count() == 0) { + NotifyError(u"no-voices"_ns); + } + NS_DispatchToMainThread( NewRunnableMethod("dom::SpeechDispatcherService::RegisterVoices", this, &SpeechDispatcherService::RegisterVoices)); @@ -395,6 +403,18 @@ void SpeechDispatcherService::Setup() { // private methods +void SpeechDispatcherService::NotifyError(const nsString& aError) { + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(NewRunnableMethod( + "dom::SpeechDispatcherService::NotifyError", this, + &SpeechDispatcherService::NotifyError, aError)); + return; + } + + RefPtr registry = nsSynthVoiceRegistry::GetInstance(); + DebugOnly rv = registry->NotifyVoicesError(aError); +} + void SpeechDispatcherService::RegisterVoices() { RefPtr registry = nsSynthVoiceRegistry::GetInstance(); for (const auto& entry : mVoices) { diff --git a/dom/media/webspeech/synth/speechd/SpeechDispatcherService.h b/dom/media/webspeech/synth/speechd/SpeechDispatcherService.h index 2922053c80c8..ac67f64d0fd9 100644 --- a/dom/media/webspeech/synth/speechd/SpeechDispatcherService.h +++ b/dom/media/webspeech/synth/speechd/SpeechDispatcherService.h @@ -47,6 +47,8 @@ class SpeechDispatcherService final : public nsIObserver, private: virtual ~SpeechDispatcherService(); + void NotifyError(const nsString& aError); + void RegisterVoices(); bool mInitialized;