Bug 1155034 - Let indirect audio speech services send their own events. Make pause/resume IPC safe. r=smaug
authorEitan Isaacson <eitan@monotonous.org>
Thu, 16 Apr 2015 10:36:47 -0700
changeset 239629 9268463eb2be7637e3990a4d084401cb09a48475
parent 239628 1801788ab9b45a21ee91bae49ed757c59a530ba5
child 239630 6e469cebd8d7bd1b9a48476efb1e9548c53c1098
push id28606
push userryanvm@gmail.com
push dateFri, 17 Apr 2015 19:45:37 +0000
treeherdermozilla-central@a55f9bdb2bf4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1155034
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1155034 - Let indirect audio speech services send their own events. Make pause/resume IPC safe. r=smaug
dom/media/webspeech/synth/SpeechSynthesis.cpp
dom/media/webspeech/synth/nsISpeechService.idl
dom/media/webspeech/synth/nsSpeechTask.cpp
dom/media/webspeech/synth/test/common.js
dom/media/webspeech/synth/test/file_indirect_service_events.html
dom/media/webspeech/synth/test/mochitest.ini
dom/media/webspeech/synth/test/test_indirect_service_events.html
--- a/dom/media/webspeech/synth/SpeechSynthesis.cpp
+++ b/dom/media/webspeech/synth/SpeechSynthesis.cpp
@@ -191,25 +191,25 @@ SpeechSynthesis::Cancel()
 
    mCurrentTask->Cancel();
   }
 }
 
 void
 SpeechSynthesis::Pause()
 {
-  if (mCurrentTask) {
+  if (mCurrentTask && !Paused() && (Speaking() || Pending())) {
     mCurrentTask->Pause();
   }
 }
 
 void
 SpeechSynthesis::Resume()
 {
-  if (mCurrentTask) {
+  if (mCurrentTask && Paused()) {
     mCurrentTask->Resume();
   }
 }
 
 void
 SpeechSynthesis::OnEnd(const nsSpeechTask* aTask)
 {
   MOZ_ASSERT(mCurrentTask == aTask);
--- a/dom/media/webspeech/synth/nsISpeechService.idl
+++ b/dom/media/webspeech/synth/nsISpeechService.idl
@@ -73,25 +73,25 @@ interface nsISpeechTask : nsISupports
    * Dispatch end event.
    *
    * @param aElapsedTime time in seconds since speech has started.
    * @param aCharIndex   offset of spoken characters.
    */
   void dispatchEnd(in float aElapsedTime, in unsigned long aCharIndex);
 
   /**
-   * Dispatch pause event. Should not be called directly by service.
+   * Dispatch pause event.
    *
    * @param aElapsedTime time in seconds since speech has started.
    * @param aCharIndex   offset of spoken characters.
    */
   void dispatchPause(in float aElapsedTime, in unsigned long aCharIndex);
 
   /**
-   * Dispatch resume event. Should not be called directly by service.
+   * Dispatch resume event.
    *
    * @param aElapsedTime time in seconds since speech has started.
    * @param aCharIndex   offset of spoken characters.
    */
   void dispatchResume(in float aElapsedTime, in unsigned long aCharIndex);
 
   /**
    * Dispatch error event.
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp
+++ b/dom/media/webspeech/synth/nsSpeechTask.cpp
@@ -469,71 +469,59 @@ nsSpeechTask::DispatchMarkImpl(const nsA
   return NS_OK;
 }
 
 void
 nsSpeechTask::Pause()
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
-  if (mUtterance->IsPaused() ||
-      mUtterance->GetState() == SpeechSynthesisUtterance::STATE_ENDED) {
-    return;
-  }
-
   if (mCallback) {
     DebugOnly<nsresult> rv = mCallback->OnPause();
     NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Unable to call onPause() callback");
   }
 
   if (mStream) {
     mStream->ChangeExplicitBlockerCount(1);
+    DispatchPauseImpl(GetCurrentTime(), GetCurrentCharOffset());
   }
-
-  DispatchPauseImpl(GetCurrentTime(), GetCurrentCharOffset());
 }
 
 void
 nsSpeechTask::Resume()
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
-  if (!mUtterance->IsPaused()) {
-    return;
-  }
-
   if (mCallback) {
     DebugOnly<nsresult> rv = mCallback->OnResume();
     NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Unable to call onResume() callback");
   }
 
   if (mStream) {
     mStream->ChangeExplicitBlockerCount(-1);
+    DispatchResumeImpl(GetCurrentTime(), GetCurrentCharOffset());
   }
-
-  DispatchResumeImpl(GetCurrentTime(), GetCurrentCharOffset());
 }
 
 void
 nsSpeechTask::Cancel()
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
   LOG(PR_LOG_DEBUG, ("nsSpeechTask::Cancel"));
 
   if (mCallback) {
     DebugOnly<nsresult> rv = mCallback->OnCancel();
     NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Unable to call onCancel() callback");
   }
 
   if (mStream) {
     mStream->ChangeExplicitBlockerCount(1);
+    DispatchEndImpl(GetCurrentTime(), GetCurrentCharOffset());
   }
-
-  DispatchEndImpl(GetCurrentTime(), GetCurrentCharOffset());
 }
 
 float
 nsSpeechTask::GetCurrentTime()
 {
   return mStream ? (float)(mStream->GetCurrentTime() / 1000000.0) : 0;
 }
 
--- a/dom/media/webspeech/synth/test/common.js
+++ b/dom/media/webspeech/synth/test/common.js
@@ -76,24 +76,48 @@ var TestSpeechServiceNoAudio = SpecialPo
       var args = {uri: aUri, rate: aRate, pitch: aPitch};
 
       for (var attr in args) {
         if (expected[attr] != undefined)
           is(args[attr], expected[attr], "expected service arg " + attr);
       }
     }
 
+    // If the utterance contains the phrase 'callback events', we will dispatch
+    // an appropriate event for each callback method.
+    var no_events = (aText.indexOf('callback events') < 0);
+    // If the utterance contains the phrase 'never end', we don't immediately
+    // end the 'synthesis' of the utterance.
+    var end_utterance = (aText.indexOf('never end') < 0);
+
     var task = SpecialPowers.wrap(aTask);
-    task.setup(SpecialPowers.wrapCallbackObject(new SpeechTaskCallback()));
+    task.setup(SpecialPowers.wrapCallbackObject(new SpeechTaskCallback(
+      function() {
+        if (!no_events) {
+          task.dispatchPause(1, 1.23);
+        }
+      },
+      function() {
+        if (!no_events) {
+          task.dispatchResume(1, 1.23);
+        }
+      },
+      function() {
+        if (!no_events) {
+          task.dispatchEnd(1, 1.23);
+        }
+      })));
     setTimeout(function () {
                  task.dispatchStart();
-                 setTimeout(function () {
-                              task.dispatchEnd(aText.length / 2.0, aText.length);
-                            }, 0);
-
+                 if (end_utterance) {
+                   setTimeout(function () {
+                                task.dispatchEnd(
+                                  aText.length / 2.0, aText.length);
+                              }, 0);
+                 }
                }, 0);
   },
 
   QueryInterface: function(iid) {
     return this;
   },
 
   getInterfaces: function(c) {},
new file mode 100644
--- /dev/null
+++ b/dom/media/webspeech/synth/test/file_indirect_service_events.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1155034
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1155034: Check that indirect audio services dispatch their own events</title>
+  <script type="application/javascript">
+    window.SimpleTest = parent.SimpleTest;
+    window.info = parent.info;
+    window.is = parent.is;
+    window.isnot = parent.isnot;
+    window.ok = parent.ok;
+  </script>
+  <script type="application/javascript" src="common.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1155034">Mozilla Bug 1155034</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1155034 **/
+
+synthAddVoice('TestSpeechServiceNoAudio', 'Female 1', 'en-GB', true);
+
+function test_with_events() {
+  info('test_with_events');
+  var utterance = new SpeechSynthesisUtterance("never end, callback events");
+
+  utterance.addEventListener('start', function(e) {
+    speechSynthesis.pause();
+  // Wait to see if we get some bad events we didn't expect.
+  });
+
+  utterance.addEventListener('pause', function(e) {
+    ok(e.charIndex, 1, 'pause event charIndex matches service arguments');
+    ok(e.elapsedTime, 1.23, 'pause event elapsedTime matches service arguments');
+    speechSynthesis.resume();
+  });
+
+  utterance.addEventListener('resume', function(e) {
+    ok(e.charIndex, 1, 'resume event charIndex matches service arguments');
+    ok(e.elapsedTime, 1.23, 'resume event elapsedTime matches service arguments');
+    speechSynthesis.cancel();
+  });
+
+  utterance.addEventListener('end', function(e) {
+    ok(e.charIndex, 1, 'resume event charIndex matches service arguments');
+    ok(e.elapsedTime, 1.23, 'end event elapsedTime matches service arguments');
+    test_no_events();
+  });
+
+  speechSynthesis.speak(utterance);
+}
+
+function test_no_events() {
+  var utterance = new SpeechSynthesisUtterance("never end");
+
+  utterance.addEventListener('start', function(e) {
+    speechSynthesis.pause();
+    // Wait to see if we get some bad events we didn't expect.
+    setTimeout(function() {
+      synthCleanup();
+      SimpleTest.finish();
+    }, 1000);
+  });
+
+  utterance.addEventListener('pause', function(e) {
+    ok(false, 'no pause event was explicitly dispatched from the service')
+    speechSynthesis.resume();
+  });
+
+  utterance.addEventListener('resume', function(e) {
+    ok(false, 'no resume event was explicitly dispatched from the service')
+    speechSynthesis.cancel();
+  });
+
+  utterance.addEventListener('end', function(e) {
+    ok(false, 'no end event was explicitly dispatched from the service')
+  });
+
+  speechSynthesis.speak(utterance);
+}
+
+test_with_events();
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/webspeech/synth/test/mochitest.ini
+++ b/dom/media/webspeech/synth/test/mochitest.ini
@@ -1,16 +1,19 @@
 [DEFAULT]
 skip-if = e10s
 support-files =
   common.js
   file_setup.html
   file_speech_queue.html
   file_speech_simple.html
   file_speech_cancel.html
+  file_indirect_service_events.html
 
 [test_setup.html]
 [test_speech_queue.html]
 skip-if = buildapp == 'b2g' # b2g(Test timed out)
 [test_speech_simple.html]
 skip-if = buildapp == 'b2g' # b2g(Test timed out)
 [test_speech_cancel.html]
 skip-if = toolkit == 'gonk' # b2g(Test timed out)
+[test_indirect_service_events.html]
+skip-if = toolkit == 'gonk' # b2g(Test timed out)
new file mode 100644
--- /dev/null
+++ b/dom/media/webspeech/synth/test/test_indirect_service_events.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1155034
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1155034: Check that indirect audio services dispatch their own events</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="common.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1155034">Mozilla Bug 1155034</a>
+<p id="display"></p>
+<iframe id="testFrame"></iframe>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1155034 **/
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({ set: [['media.webspeech.synth.enabled', true]] },
+                          function() { document.getElementById("testFrame").src = "file_indirect_service_events.html"; });
+
+</script>
+</pre>
+</body>
+</html>