Coverage Report

Created: 2018-09-25 14:53

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