Bug 1432719 - Notify user on speechd errors r=eeejay,fluent-reviewers,flod
Differential Revision: https://phabricator.services.mozilla.com/D176532
This commit is contained in:
10
browser/actors/SpeechDispatcherChild.sys.mjs
Normal file
10
browser/actors/SpeechDispatcherChild.sys.mjs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
90
browser/actors/SpeechDispatcherParent.sys.mjs
Normal file
90
browser/actors/SpeechDispatcherParent.sys.mjs
Normal file
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,6 +82,8 @@ FINAL_TARGET_FILES.actors += [
|
|||||||
"ScreenshotsComponentChild.sys.mjs",
|
"ScreenshotsComponentChild.sys.mjs",
|
||||||
"SearchSERPTelemetryChild.sys.mjs",
|
"SearchSERPTelemetryChild.sys.mjs",
|
||||||
"SearchSERPTelemetryParent.sys.mjs",
|
"SearchSERPTelemetryParent.sys.mjs",
|
||||||
|
"SpeechDispatcherChild.sys.mjs",
|
||||||
|
"SpeechDispatcherParent.sys.mjs",
|
||||||
"SwitchDocumentDirectionChild.sys.mjs",
|
"SwitchDocumentDirectionChild.sys.mjs",
|
||||||
"WebRTCChild.sys.mjs",
|
"WebRTCChild.sys.mjs",
|
||||||
"WebRTCParent.sys.mjs",
|
"WebRTCParent.sys.mjs",
|
||||||
|
|||||||
@@ -752,6 +752,20 @@ let JSWINDOWACTORS = {
|
|||||||
matches: ["about:studies*"],
|
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: {
|
ASRouter: {
|
||||||
parent: {
|
parent: {
|
||||||
esModuleURI: "resource:///actors/ASRouterParent.sys.mjs",
|
esModuleURI: "resource:///actors/ASRouterParent.sys.mjs",
|
||||||
|
|||||||
15
browser/locales/en-US/browser/speechDispatcher.ftl
Normal file
15
browser/locales/en-US/browser/speechDispatcher.ftl
Normal file
@@ -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
|
||||||
@@ -65,6 +65,7 @@ SpeechSynthesis::SpeechSynthesis(nsPIDOMWindowInner* aParent)
|
|||||||
if (obs) {
|
if (obs) {
|
||||||
obs->AddObserver(this, "inner-window-destroyed", true);
|
obs->AddObserver(this, "inner-window-destroyed", true);
|
||||||
obs->AddObserver(this, "synth-voices-changed", 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();
|
AdvanceQueue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (strcmp(aTopic, "synth-voices-error") == 0) {
|
||||||
|
NS_WARNING("SpeechSynthesis::Observe: synth-voices-error");
|
||||||
|
LOG(LogLevel::Debug, ("SpeechSynthesis::onvoiceserror"));
|
||||||
|
nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
|
||||||
|
|
||||||
|
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
||||||
|
if (obs) {
|
||||||
|
obs->NotifyObservers(window, "chrome-synth-voices-error", aData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mSpeechQueue.IsEmpty()) {
|
||||||
|
for (RefPtr<SpeechSynthesisUtterance>& utterance : mSpeechQueue) {
|
||||||
|
utterance->DispatchSpeechSynthesisEvent(u"error"_ns, 0, nullptr, 0,
|
||||||
|
u""_ns);
|
||||||
|
}
|
||||||
|
mSpeechQueue.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ child:
|
|||||||
|
|
||||||
async NotifyVoicesChanged();
|
async NotifyVoicesChanged();
|
||||||
|
|
||||||
|
async NotifyVoicesError(nsString aError);
|
||||||
|
|
||||||
async InitialVoicesAndState(RemoteVoice[] aVoices, nsString[] aDefaults,
|
async InitialVoicesAndState(RemoteVoice[] aVoices, nsString[] aDefaults,
|
||||||
bool aIsSpeaking);
|
bool aIsSpeaking);
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ mozilla::ipc::IPCResult SpeechSynthesisChild::RecvNotifyVoicesChanged() {
|
|||||||
return IPC_OK();
|
return IPC_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mozilla::ipc::IPCResult SpeechSynthesisChild::RecvNotifyVoicesError(
|
||||||
|
const nsAString& aError) {
|
||||||
|
nsSynthVoiceRegistry::RecvNotifyVoicesError(aError);
|
||||||
|
return IPC_OK();
|
||||||
|
}
|
||||||
|
|
||||||
PSpeechSynthesisRequestChild*
|
PSpeechSynthesisRequestChild*
|
||||||
SpeechSynthesisChild::AllocPSpeechSynthesisRequestChild(
|
SpeechSynthesisChild::AllocPSpeechSynthesisRequestChild(
|
||||||
const nsAString& aText, const nsAString& aLang, const nsAString& aUri,
|
const nsAString& aText, const nsAString& aLang, const nsAString& aUri,
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class SpeechSynthesisChild : public PSpeechSynthesisChild {
|
|||||||
|
|
||||||
mozilla::ipc::IPCResult RecvNotifyVoicesChanged();
|
mozilla::ipc::IPCResult RecvNotifyVoicesChanged();
|
||||||
|
|
||||||
|
mozilla::ipc::IPCResult RecvNotifyVoicesError(const nsAString& aError);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SpeechSynthesisChild();
|
SpeechSynthesisChild();
|
||||||
virtual ~SpeechSynthesisChild();
|
virtual ~SpeechSynthesisChild();
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ interface nsISynthVoiceRegistry : nsISupports
|
|||||||
*/
|
*/
|
||||||
void notifyVoicesChanged();
|
void notifyVoicesChanged();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify chrome code of an error when starting speech synthesis service
|
||||||
|
*/
|
||||||
|
void notifyVoicesError(in AString aError);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a voice as default.
|
* Set a voice as default.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -282,6 +282,15 @@ void nsSynthVoiceRegistry::RecvNotifyVoicesChanged() {
|
|||||||
gSynthVoiceRegistry->NotifyVoicesChanged();
|
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
|
NS_IMETHODIMP
|
||||||
nsSynthVoiceRegistry::AddVoice(nsISpeechService* aService,
|
nsSynthVoiceRegistry::AddVoice(nsISpeechService* aService,
|
||||||
const nsAString& aUri, const nsAString& aName,
|
const nsAString& aUri, const nsAString& aName,
|
||||||
@@ -369,6 +378,27 @@ nsSynthVoiceRegistry::NotifyVoicesChanged() {
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
nsSynthVoiceRegistry::NotifyVoicesError(const nsAString& aError) {
|
||||||
|
if (XRE_IsParentProcess()) {
|
||||||
|
nsTArray<SpeechSynthesisParent*> ssplist;
|
||||||
|
GetAllSpeechSynthActors(ssplist);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < ssplist.Length(); ++i) {
|
||||||
|
Unused << ssplist[i]->SendNotifyVoicesError(aError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsIObserverService> 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
|
NS_IMETHODIMP
|
||||||
nsSynthVoiceRegistry::SetDefaultVoice(const nsAString& aUri, bool aIsDefault) {
|
nsSynthVoiceRegistry::SetDefaultVoice(const nsAString& aUri, bool aIsDefault) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ class nsSynthVoiceRegistry final : public nsISynthVoiceRegistry {
|
|||||||
|
|
||||||
static void RecvNotifyVoicesChanged();
|
static void RecvNotifyVoicesChanged();
|
||||||
|
|
||||||
|
static void RecvNotifyVoicesError(const nsAString& aError);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual ~nsSynthVoiceRegistry();
|
virtual ~nsSynthVoiceRegistry();
|
||||||
|
|
||||||
|
|||||||
@@ -322,6 +322,7 @@ void SpeechDispatcherService::Setup() {
|
|||||||
|
|
||||||
if (!speechdLib) {
|
if (!speechdLib) {
|
||||||
NS_WARNING("Failed to load speechd library");
|
NS_WARNING("Failed to load speechd library");
|
||||||
|
NotifyError(u"lib-missing"_ns);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,6 +330,7 @@ void SpeechDispatcherService::Setup() {
|
|||||||
// There is no version getter function, so we rely on a symbol that was
|
// 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.
|
// introduced in release 0.8.2 in order to check for ABI compatibility.
|
||||||
NS_WARNING("Unsupported version of speechd detected");
|
NS_WARNING("Unsupported version of speechd detected");
|
||||||
|
NotifyError(u"lib-too-old"_ns);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,6 +342,7 @@ void SpeechDispatcherService::Setup() {
|
|||||||
NS_WARNING(nsPrintfCString("Failed to find speechd symbol for'%s'",
|
NS_WARNING(nsPrintfCString("Failed to find speechd symbol for'%s'",
|
||||||
kSpeechDispatcherSymbols[i].functionName)
|
kSpeechDispatcherSymbols[i].functionName)
|
||||||
.get());
|
.get());
|
||||||
|
NotifyError(u"missing-symbol"_ns);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,6 +351,7 @@ void SpeechDispatcherService::Setup() {
|
|||||||
spd_open("firefox", "web speech api", "who", SPD_MODE_THREADED);
|
spd_open("firefox", "web speech api", "who", SPD_MODE_THREADED);
|
||||||
if (!mSpeechdClient) {
|
if (!mSpeechdClient) {
|
||||||
NS_WARNING("Failed to call spd_open");
|
NS_WARNING("Failed to call spd_open");
|
||||||
|
NotifyError(u"open-fail"_ns);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,6 +390,10 @@ void SpeechDispatcherService::Setup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mVoices.Count() == 0) {
|
||||||
|
NotifyError(u"no-voices"_ns);
|
||||||
|
}
|
||||||
|
|
||||||
NS_DispatchToMainThread(
|
NS_DispatchToMainThread(
|
||||||
NewRunnableMethod("dom::SpeechDispatcherService::RegisterVoices", this,
|
NewRunnableMethod("dom::SpeechDispatcherService::RegisterVoices", this,
|
||||||
&SpeechDispatcherService::RegisterVoices));
|
&SpeechDispatcherService::RegisterVoices));
|
||||||
@@ -395,6 +403,18 @@ void SpeechDispatcherService::Setup() {
|
|||||||
|
|
||||||
// private methods
|
// private methods
|
||||||
|
|
||||||
|
void SpeechDispatcherService::NotifyError(const nsString& aError) {
|
||||||
|
if (!NS_IsMainThread()) {
|
||||||
|
NS_DispatchToMainThread(NewRunnableMethod<const nsString>(
|
||||||
|
"dom::SpeechDispatcherService::NotifyError", this,
|
||||||
|
&SpeechDispatcherService::NotifyError, aError));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefPtr<nsSynthVoiceRegistry> registry = nsSynthVoiceRegistry::GetInstance();
|
||||||
|
DebugOnly<nsresult> rv = registry->NotifyVoicesError(aError);
|
||||||
|
}
|
||||||
|
|
||||||
void SpeechDispatcherService::RegisterVoices() {
|
void SpeechDispatcherService::RegisterVoices() {
|
||||||
RefPtr<nsSynthVoiceRegistry> registry = nsSynthVoiceRegistry::GetInstance();
|
RefPtr<nsSynthVoiceRegistry> registry = nsSynthVoiceRegistry::GetInstance();
|
||||||
for (const auto& entry : mVoices) {
|
for (const auto& entry : mVoices) {
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ class SpeechDispatcherService final : public nsIObserver,
|
|||||||
private:
|
private:
|
||||||
virtual ~SpeechDispatcherService();
|
virtual ~SpeechDispatcherService();
|
||||||
|
|
||||||
|
void NotifyError(const nsString& aError);
|
||||||
|
|
||||||
void RegisterVoices();
|
void RegisterVoices();
|
||||||
|
|
||||||
bool mInitialized;
|
bool mInitialized;
|
||||||
|
|||||||
Reference in New Issue
Block a user