Bug 1254378 - Implement nsISynthVoiceRegistry.notifyVoicesChanged. r=smaug
authorEitan Isaacson <eitan@monotonous.org>
Wed, 23 Mar 2016 10:38:18 -0700
changeset 291118 47390c18d9b12131f4849a2625302cf0588ac720
parent 291117 3ace54af7a05d57bb02b4360a09c156fd5442d0e
child 291119 70ead871eea8cbc07577cfceb86458bd5a16d478
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1254378
milestone48.0a1
Bug 1254378 - Implement nsISynthVoiceRegistry.notifyVoicesChanged. r=smaug MozReview-Commit-ID: DIRcnw5Rfnc
dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
dom/media/webspeech/synth/ipc/PSpeechSynthesis.ipdl
dom/media/webspeech/synth/ipc/SpeechSynthesisChild.cpp
dom/media/webspeech/synth/ipc/SpeechSynthesisChild.h
dom/media/webspeech/synth/nsISynthVoiceRegistry.idl
dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp
dom/media/webspeech/synth/nsSynthVoiceRegistry.h
dom/media/webspeech/synth/speechd/SpeechDispatcherService.cpp
dom/media/webspeech/synth/windows/SapiService.cpp
--- a/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
+++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
@@ -245,16 +245,19 @@ RegisterVoicesRunnable::Run()
     if (NS_WARN_IF(NS_FAILED(rv))) {
       continue;
     }
 
     if (voice.mIsDefault) {
       registry->SetDefaultVoice(voice.mUri, true);
     }
   }
+
+  registry->NotifyVoicesChanged();
+
   return NS_OK;
 }
 
 class EnumVoicesRunnable final : public nsRunnable
 {
 public:
   explicit EnumVoicesRunnable(OSXSpeechSynthesizerService* aSpeechService)
     : mSpeechService(aSpeechService)
--- a/dom/media/webspeech/synth/ipc/PSpeechSynthesis.ipdl
+++ b/dom/media/webspeech/synth/ipc/PSpeechSynthesis.ipdl
@@ -28,16 +28,18 @@ child:
     async VoiceAdded(RemoteVoice aVoice);
 
     async VoiceRemoved(nsString aUri);
 
     async SetDefaultVoice(nsString aUri, bool aIsDefault);
 
     async IsSpeakingChanged(bool aIsSpeaking);
 
+    async NotifyVoicesChanged();
+
 parent:
     async __delete__();
 
     async PSpeechSynthesisRequest(nsString aText, nsString aUri, nsString aLang,
                                   float aVolume, float aRate, float aPitch);
 
     sync ReadVoicesAndState() returns (RemoteVoice[] aVoices,
                                        nsString[] aDefaults, bool aIsSpeaking);
--- a/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.cpp
+++ b/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.cpp
@@ -42,16 +42,23 @@ SpeechSynthesisChild::RecvSetDefaultVoic
 
 bool
 SpeechSynthesisChild::RecvIsSpeakingChanged(const bool& aIsSpeaking)
 {
   nsSynthVoiceRegistry::RecvIsSpeakingChanged(aIsSpeaking);
   return true;
 }
 
+bool
+SpeechSynthesisChild::RecvNotifyVoicesChanged()
+{
+  nsSynthVoiceRegistry::RecvNotifyVoicesChanged();
+  return true;
+}
+
 PSpeechSynthesisRequestChild*
 SpeechSynthesisChild::AllocPSpeechSynthesisRequestChild(const nsString& aText,
                                                         const nsString& aLang,
                                                         const nsString& aUri,
                                                         const float& aVolume,
                                                         const float& aRate,
                                                         const float& aPitch)
 {
--- a/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.h
+++ b/dom/media/webspeech/synth/ipc/SpeechSynthesisChild.h
@@ -25,16 +25,18 @@ public:
   bool RecvVoiceAdded(const RemoteVoice& aVoice) override;
 
   bool RecvVoiceRemoved(const nsString& aUri) override;
 
   bool RecvSetDefaultVoice(const nsString& aUri, const bool& aIsDefault) override;
 
   bool RecvIsSpeakingChanged(const bool& aIsSpeaking) override;
 
+  bool RecvNotifyVoicesChanged() override;
+
 protected:
   SpeechSynthesisChild();
   virtual ~SpeechSynthesisChild();
 
   PSpeechSynthesisRequestChild* AllocPSpeechSynthesisRequestChild(const nsString& aLang,
                                                                   const nsString& aUri,
                                                                   const nsString& aText,
                                                                   const float& aVolume,
--- a/dom/media/webspeech/synth/nsISynthVoiceRegistry.idl
+++ b/dom/media/webspeech/synth/nsISynthVoiceRegistry.idl
@@ -2,17 +2,17 @@
 /* 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/. */
 
 #include "nsISupports.idl"
 
 interface nsISpeechService;
 
-[scriptable, builtinclass, uuid(dac09c3a-156e-4025-a4ab-bc88b0ea92e7)]
+[scriptable, builtinclass, uuid(5d7a0b38-77e5-4ee5-897c-ce5db9b85d44)]
 interface nsISynthVoiceRegistry : nsISupports
 {
   /**
    * Register a speech synthesis voice.
    *
    * @param aService          the service that provides this voice.
    * @param aUri              a unique identifier for this voice.
    * @param aName             human-readable name for this voice.
@@ -28,16 +28,22 @@ interface nsISynthVoiceRegistry : nsISup
    * Remove a speech synthesis voice.
    *
    * @param aService the service that was used to add the voice.
    * @param aUri     a unique identifier of an existing voice.
    */
   void removeVoice(in nsISpeechService aService, in DOMString aUri);
 
   /**
+   * Notify content of voice availability changes. This allows content
+   * to be notified of voice catalog changes in real time.
+   */
+  void notifyVoicesChanged();
+
+  /**
    * Set a voice as default.
    *
    * @param aUri       a unique identifier of an existing voice.
    * @param aIsDefault true if this voice should be toggled as default.
    */
   void setDefaultVoice(in DOMString aUri, in boolean aIsDefault);
 
   readonly attribute uint32_t voiceCount;
--- a/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp
+++ b/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp
@@ -281,16 +281,27 @@ nsSynthVoiceRegistry::RecvIsSpeakingChan
   // speaking state on construction.
   if(!gSynthVoiceRegistry) {
     return;
   }
 
   gSynthVoiceRegistry->mIsSpeaking = aIsSpeaking;
 }
 
+void
+nsSynthVoiceRegistry::RecvNotifyVoicesChanged()
+{
+  // If we dont have a local instance of the registry yet, we don't care.
+  if(!gSynthVoiceRegistry) {
+    return;
+  }
+
+  gSynthVoiceRegistry->NotifyVoicesChanged();
+}
+
 NS_IMETHODIMP
 nsSynthVoiceRegistry::AddVoice(nsISpeechService* aService,
                                const nsAString& aUri,
                                const nsAString& aName,
                                const nsAString& aLang,
                                bool aLocalService,
                                bool aQueuesUtterances)
 {
@@ -352,16 +363,37 @@ nsSynthVoiceRegistry::RemoveVoice(nsISpe
 
   for (uint32_t i = 0; i < ssplist.Length(); ++i)
     Unused << ssplist[i]->SendVoiceRemoved(nsString(aUri));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsSynthVoiceRegistry::NotifyVoicesChanged()
+{
+  if (XRE_IsParentProcess()) {
+    nsTArray<SpeechSynthesisParent*> ssplist;
+    GetAllSpeechSynthActors(ssplist);
+
+    for (uint32_t i = 0; i < ssplist.Length(); ++i)
+      Unused << ssplist[i]->SendNotifyVoicesChanged();
+  }
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if(NS_WARN_IF(!(obs))) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  obs->NotifyObservers(nullptr, "synth-voices-changed", nullptr);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsSynthVoiceRegistry::SetDefaultVoice(const nsAString& aUri,
                                       bool aIsDefault)
 {
   bool found = false;
   VoiceData* retval = mUriVoiceMap.GetWeak(aUri, &found);
   if(NS_WARN_IF(!(found))) {
     return NS_ERROR_NOT_AVAILABLE;
   }
--- a/dom/media/webspeech/synth/nsSynthVoiceRegistry.h
+++ b/dom/media/webspeech/synth/nsSynthVoiceRegistry.h
@@ -59,16 +59,18 @@ public:
   static void RecvRemoveVoice(const nsAString& aUri);
 
   static void RecvAddVoice(const RemoteVoice& aVoice);
 
   static void RecvSetDefaultVoice(const nsAString& aUri, bool aIsDefault);
 
   static void RecvIsSpeakingChanged(bool aIsSpeaking);
 
+  static void RecvNotifyVoicesChanged();
+
   static void Shutdown();
 
 private:
   virtual ~nsSynthVoiceRegistry();
 
   VoiceData* FindBestMatch(const nsAString& aUri, const nsAString& lang);
 
   bool FindVoiceByLang(const nsAString& aLang, VoiceData** aRetval);
--- a/dom/media/webspeech/synth/speechd/SpeechDispatcherService.cpp
+++ b/dom/media/webspeech/synth/speechd/SpeechDispatcherService.cpp
@@ -417,17 +417,17 @@ SpeechDispatcherService::Setup()
   //mInitialized = true;
 }
 
 // private methods
 
 void
 SpeechDispatcherService::RegisterVoices()
 {
-  nsSynthVoiceRegistry* registry = nsSynthVoiceRegistry::GetInstance();
+  RefPtr<nsSynthVoiceRegistry> registry = nsSynthVoiceRegistry::GetInstance();
   for (auto iter = mVoices.Iter(); !iter.Done(); iter.Next()) {
     RefPtr<SpeechDispatcherVoice>& voice = iter.Data();
 
     // This service can only speak one utterance at a time, so we set
     // aQueuesUtterances to true in order to track global state and schedule
     // access to this service.
     DebugOnly<nsresult> rv =
       registry->AddVoice(this, iter.Key(), voice->mName, voice->mLanguage,
@@ -435,16 +435,18 @@ SpeechDispatcherService::RegisterVoices(
 
     NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to add voice");
   }
 
   mInitThread->Shutdown();
   mInitThread = nullptr;
 
   mInitialized = true;
+
+  registry->NotifyVoicesChanged();
 }
 
 // nsIObserver
 
 NS_IMETHODIMP
 SpeechDispatcherService::Observe(nsISupports* aSubject, const char* aTopic,
                                  const char16_t* aData)
 {
--- a/dom/media/webspeech/synth/windows/SapiService.cpp
+++ b/dom/media/webspeech/synth/windows/SapiService.cpp
@@ -308,16 +308,18 @@ SapiService::RegisterVoices()
     CoTaskMemFree(description);
     if (NS_FAILED(rv)) {
       continue;
     }
 
     mVoices.Put(uri, voiceToken);
   }
 
+  registry->NotifyVoicesChanged();
+
   return true;
 }
 
 NS_IMETHODIMP
 SapiService::Speak(const nsAString& aText, const nsAString& aUri,
                    float aVolume, float aRate, float aPitch,
                    nsISpeechTask* aTask)
 {