LCOV - code coverage report
Current view: top level - dom/media/webspeech/synth - SpeechSynthesis.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 150 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 24 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim:set ts=2 sw=2 sts=2 et cindent: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "nsISupportsPrimitives.h"
       8             : #include "nsSpeechTask.h"
       9             : #include "mozilla/Logging.h"
      10             : #include "mozilla/SizePrintfMacros.h"
      11             : 
      12             : #include "mozilla/dom/ContentChild.h"
      13             : #include "mozilla/dom/Element.h"
      14             : 
      15             : #include "mozilla/dom/SpeechSynthesisBinding.h"
      16             : #include "SpeechSynthesis.h"
      17             : #include "nsSynthVoiceRegistry.h"
      18             : #include "nsIDocument.h"
      19             : 
      20             : #undef LOG
      21             : mozilla::LogModule*
      22           0 : GetSpeechSynthLog()
      23             : {
      24             :   static mozilla::LazyLogModule sLog("SpeechSynthesis");
      25             : 
      26           0 :   return sLog;
      27             : }
      28             : #define LOG(type, msg) MOZ_LOG(GetSpeechSynthLog(), type, msg)
      29             : 
      30             : namespace mozilla {
      31             : namespace dom {
      32             : 
      33             : NS_IMPL_CYCLE_COLLECTION_CLASS(SpeechSynthesis)
      34             : 
      35           0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SpeechSynthesis, DOMEventTargetHelper)
      36           0 :   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCurrentTask)
      37           0 :   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechQueue)
      38           0 :   tmp->mVoiceCache.Clear();
      39           0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
      40             : 
      41           0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SpeechSynthesis, DOMEventTargetHelper)
      42           0 :   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCurrentTask)
      43           0 :   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechQueue)
      44           0 :   for (auto iter = tmp->mVoiceCache.Iter(); !iter.Done(); iter.Next()) {
      45           0 :     SpeechSynthesisVoice* voice = iter.UserData();
      46           0 :     cb.NoteXPCOMChild(voice);
      47             :   }
      48           0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
      49             : 
      50           0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SpeechSynthesis)
      51           0 :   NS_INTERFACE_MAP_ENTRY(nsIObserver)
      52           0 :   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
      53           0 : NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
      54             : 
      55           0 : NS_IMPL_ADDREF_INHERITED(SpeechSynthesis, DOMEventTargetHelper)
      56           0 : NS_IMPL_RELEASE_INHERITED(SpeechSynthesis, DOMEventTargetHelper)
      57             : 
      58           0 : SpeechSynthesis::SpeechSynthesis(nsPIDOMWindowInner* aParent)
      59             :   : DOMEventTargetHelper(aParent)
      60             :   , mHoldQueue(false)
      61           0 :   , mInnerID(aParent->WindowID())
      62             : {
      63           0 :   MOZ_ASSERT(aParent->IsInnerWindow());
      64           0 :   MOZ_ASSERT(NS_IsMainThread());
      65             : 
      66           0 :   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
      67           0 :   if (obs) {
      68           0 :     obs->AddObserver(this, "inner-window-destroyed", true);
      69           0 :     obs->AddObserver(this, "synth-voices-changed", true);
      70             :   }
      71             : 
      72           0 : }
      73             : 
      74           0 : SpeechSynthesis::~SpeechSynthesis()
      75             : {
      76           0 : }
      77             : 
      78             : JSObject*
      79           0 : SpeechSynthesis::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
      80             : {
      81           0 :   return SpeechSynthesisBinding::Wrap(aCx, this, aGivenProto);
      82             : }
      83             : 
      84             : bool
      85           0 : SpeechSynthesis::Pending() const
      86             : {
      87           0 :   switch (mSpeechQueue.Length()) {
      88             :   case 0:
      89           0 :     return false;
      90             : 
      91             :   case 1:
      92           0 :     return mSpeechQueue.ElementAt(0)->GetState() == SpeechSynthesisUtterance::STATE_PENDING;
      93             : 
      94             :   default:
      95           0 :     return true;
      96             :   }
      97             : }
      98             : 
      99             : bool
     100           0 : SpeechSynthesis::Speaking() const
     101             : {
     102           0 :   if (!mSpeechQueue.IsEmpty() &&
     103           0 :       mSpeechQueue.ElementAt(0)->GetState() == SpeechSynthesisUtterance::STATE_SPEAKING) {
     104           0 :     return true;
     105             :   }
     106             : 
     107             :   // Returns global speaking state if global queue is enabled. Or false.
     108           0 :   return nsSynthVoiceRegistry::GetInstance()->IsSpeaking();
     109             : }
     110             : 
     111             : bool
     112           0 : SpeechSynthesis::Paused() const
     113             : {
     114           0 :   return mHoldQueue || (mCurrentTask && mCurrentTask->IsPrePaused()) ||
     115           0 :          (!mSpeechQueue.IsEmpty() && mSpeechQueue.ElementAt(0)->IsPaused());
     116             : }
     117             : 
     118             : bool
     119           0 : SpeechSynthesis::HasEmptyQueue() const
     120             : {
     121           0 :   return mSpeechQueue.Length() == 0;
     122             : }
     123             : 
     124           0 : bool SpeechSynthesis::HasVoices() const
     125             : {
     126           0 :   uint32_t voiceCount = mVoiceCache.Count();
     127           0 :   if (voiceCount == 0) {
     128           0 :     nsresult rv = nsSynthVoiceRegistry::GetInstance()->GetVoiceCount(&voiceCount);
     129           0 :     if(NS_WARN_IF(NS_FAILED(rv))) {
     130           0 :       return false;
     131             :     }
     132             :   }
     133             : 
     134           0 :   return voiceCount != 0;
     135             : }
     136             : 
     137             : void
     138           0 : SpeechSynthesis::Speak(SpeechSynthesisUtterance& aUtterance)
     139             : {
     140           0 :   if (aUtterance.mState != SpeechSynthesisUtterance::STATE_NONE) {
     141             :     // XXX: Should probably raise an error
     142           0 :     return;
     143             :   }
     144             : 
     145           0 :   mSpeechQueue.AppendElement(&aUtterance);
     146           0 :   aUtterance.mState = SpeechSynthesisUtterance::STATE_PENDING;
     147             : 
     148             :   // If we only have one item in the queue, we aren't pre-paused, and
     149             :   // we have voices available, speak it.
     150           0 :   if (mSpeechQueue.Length() == 1 && !mCurrentTask && !mHoldQueue && HasVoices()) {
     151           0 :     AdvanceQueue();
     152             :   }
     153             : }
     154             : 
     155             : void
     156           0 : SpeechSynthesis::AdvanceQueue()
     157             : {
     158           0 :   LOG(LogLevel::Debug,
     159             :       ("SpeechSynthesis::AdvanceQueue length=%" PRIuSIZE, mSpeechQueue.Length()));
     160             : 
     161           0 :   if (mSpeechQueue.IsEmpty()) {
     162           0 :     return;
     163             :   }
     164             : 
     165           0 :   RefPtr<SpeechSynthesisUtterance> utterance = mSpeechQueue.ElementAt(0);
     166             : 
     167           0 :   nsAutoString docLang;
     168           0 :   nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
     169           0 :   nsIDocument* doc = window ? window->GetExtantDoc() : nullptr;
     170             : 
     171           0 :   if (doc) {
     172           0 :     Element* elm = doc->GetHtmlElement();
     173             : 
     174           0 :     if (elm) {
     175           0 :       elm->GetLang(docLang);
     176             :     }
     177             :   }
     178             : 
     179             :   mCurrentTask =
     180           0 :     nsSynthVoiceRegistry::GetInstance()->SpeakUtterance(*utterance, docLang);
     181             : 
     182           0 :   if (mCurrentTask) {
     183           0 :     mCurrentTask->SetSpeechSynthesis(this);
     184             :   }
     185             : 
     186           0 :   return;
     187             : }
     188             : 
     189             : void
     190           0 : SpeechSynthesis::Cancel()
     191             : {
     192           0 :   if (!mSpeechQueue.IsEmpty() &&
     193           0 :       mSpeechQueue.ElementAt(0)->GetState() == SpeechSynthesisUtterance::STATE_SPEAKING) {
     194             :     // Remove all queued utterances except for current one, we will remove it
     195             :     // in OnEnd
     196           0 :     mSpeechQueue.RemoveElementsAt(1, mSpeechQueue.Length() - 1);
     197             :   } else {
     198           0 :     mSpeechQueue.Clear();
     199             :   }
     200             : 
     201           0 :   if (mCurrentTask) {
     202           0 :     mCurrentTask->Cancel();
     203             :   }
     204           0 : }
     205             : 
     206             : void
     207           0 : SpeechSynthesis::Pause()
     208             : {
     209           0 :   if (Paused()) {
     210           0 :     return;
     211             :   }
     212             : 
     213           0 :   if (mCurrentTask && !mSpeechQueue.IsEmpty() &&
     214           0 :       mSpeechQueue.ElementAt(0)->GetState() != SpeechSynthesisUtterance::STATE_ENDED) {
     215           0 :     mCurrentTask->Pause();
     216             :   } else {
     217           0 :     mHoldQueue = true;
     218             :   }
     219             : }
     220             : 
     221             : void
     222           0 : SpeechSynthesis::Resume()
     223             : {
     224           0 :   if (!Paused()) {
     225           0 :     return;
     226             :   }
     227             : 
     228           0 :   if (mCurrentTask) {
     229           0 :     mCurrentTask->Resume();
     230             :   } else {
     231           0 :     mHoldQueue = false;
     232           0 :     AdvanceQueue();
     233             :   }
     234             : }
     235             : 
     236             : void
     237           0 : SpeechSynthesis::OnEnd(const nsSpeechTask* aTask)
     238             : {
     239           0 :   MOZ_ASSERT(mCurrentTask == aTask);
     240             : 
     241           0 :   if (!mSpeechQueue.IsEmpty()) {
     242           0 :     mSpeechQueue.RemoveElementAt(0);
     243             :   }
     244             : 
     245           0 :   mCurrentTask = nullptr;
     246           0 :   AdvanceQueue();
     247           0 : }
     248             : 
     249             : void
     250           0 : SpeechSynthesis::GetVoices(nsTArray< RefPtr<SpeechSynthesisVoice> >& aResult)
     251             : {
     252           0 :   aResult.Clear();
     253           0 :   uint32_t voiceCount = 0;
     254             : 
     255           0 :   nsresult rv = nsSynthVoiceRegistry::GetInstance()->GetVoiceCount(&voiceCount);
     256           0 :   if(NS_WARN_IF(NS_FAILED(rv))) {
     257           0 :     return;
     258             :   }
     259             : 
     260           0 :   nsISupports* voiceParent = NS_ISUPPORTS_CAST(nsIObserver*, this);
     261             : 
     262           0 :   for (uint32_t i = 0; i < voiceCount; i++) {
     263           0 :     nsAutoString uri;
     264           0 :     rv = nsSynthVoiceRegistry::GetInstance()->GetVoice(i, uri);
     265             : 
     266           0 :     if (NS_FAILED(rv)) {
     267           0 :       NS_WARNING("Failed to retrieve voice from registry");
     268           0 :       continue;
     269             :     }
     270             : 
     271           0 :     SpeechSynthesisVoice* voice = mVoiceCache.GetWeak(uri);
     272             : 
     273           0 :     if (!voice) {
     274           0 :       voice = new SpeechSynthesisVoice(voiceParent, uri);
     275             :     }
     276             : 
     277           0 :     aResult.AppendElement(voice);
     278             :   }
     279             : 
     280           0 :   mVoiceCache.Clear();
     281             : 
     282           0 :   for (uint32_t i = 0; i < aResult.Length(); i++) {
     283           0 :     SpeechSynthesisVoice* voice = aResult[i];
     284           0 :     mVoiceCache.Put(voice->mUri, voice);
     285             :   }
     286             : }
     287             : 
     288             : // For testing purposes, allows us to cancel the current task that is
     289             : // misbehaving, and flush the queue.
     290             : void
     291           0 : SpeechSynthesis::ForceEnd()
     292             : {
     293           0 :   if (mCurrentTask) {
     294           0 :     mCurrentTask->ForceEnd();
     295             :   }
     296           0 : }
     297             : 
     298             : NS_IMETHODIMP
     299           0 : SpeechSynthesis::Observe(nsISupports* aSubject, const char* aTopic,
     300             :                          const char16_t* aData)
     301             : {
     302           0 :   MOZ_ASSERT(NS_IsMainThread());
     303             : 
     304             : 
     305           0 :   if (strcmp(aTopic, "inner-window-destroyed") == 0) {
     306           0 :     nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
     307           0 :     NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
     308             : 
     309             :     uint64_t innerID;
     310           0 :     nsresult rv = wrapper->GetData(&innerID);
     311           0 :     NS_ENSURE_SUCCESS(rv, rv);
     312             : 
     313           0 :     if (innerID == mInnerID) {
     314           0 :       Cancel();
     315             : 
     316           0 :       nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     317           0 :       if (obs) {
     318           0 :         obs->RemoveObserver(this, "inner-window-destroyed");
     319             :       }
     320             :     }
     321           0 :   } else if (strcmp(aTopic, "synth-voices-changed") == 0) {
     322           0 :     LOG(LogLevel::Debug, ("SpeechSynthesis::onvoiceschanged"));
     323           0 :     DispatchTrustedEvent(NS_LITERAL_STRING("voiceschanged"));
     324             :     // If we have a pending item, and voices become available, speak it.
     325           0 :     if (!mCurrentTask && !mHoldQueue && HasVoices()) {
     326           0 :       AdvanceQueue();
     327             :     }
     328             :   }
     329             : 
     330           0 :   return NS_OK;
     331             : }
     332             : 
     333             : } // namespace dom
     334             : } // namespace mozilla

Generated by: LCOV version 1.13