Commit e743aab0 authored by Charlie Harrison's avatar Charlie Harrison Committed by Commit Bot

Add SpeechSynthesisErrorEvent IDL interface

See spec:
https://w3c.github.io/speech-api/#speechsynthesiserrorevent

and discussion on i2s on blink-dev:
https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/8_H3FUZm23g

Previously, errors were just going through as SpeechSynthesisEvents.
This CL now dispatches errors as SpeechSynthesisErrorEvents with an
empty "error" method.

This CL also adds constructors on both SpeechSynthesisEvent and
SpeechSynthesisErrorEvent.

Bug: 885297
Change-Id: I70ba998182943a50e17f2fc11eb7e163d8289e94
Reviewed-on: https://chromium-review.googlesource.com/1231839
Commit-Queue: Charlie Harrison <csharrison@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: default avatarPhilip Jägenstedt <foolip@chromium.org>
Cr-Commit-Position: refs/heads/master@{#595128}
parent 63f526d3
......@@ -137,7 +137,8 @@ interface SpeechSynthesisUtterance : EventTarget {
attribute EventHandler onboundary;
};
[Exposed=Window]
[Exposed=Window,
Constructor(DOMString type, SpeechSynthesisEventInit eventInitDict)]
interface SpeechSynthesisEvent : Event {
readonly attribute SpeechSynthesisUtterance utterance;
readonly attribute unsigned long charIndex;
......@@ -145,6 +146,13 @@ interface SpeechSynthesisEvent : Event {
readonly attribute DOMString name;
};
dictionary SpeechSynthesisEventInit : EventInit {
required SpeechSynthesisUtterance utterance;
unsigned long charIndex = 0;
float elapsedTime = 0;
DOMString name = "";
};
enum SpeechSynthesisErrorCode {
"canceled",
"interrupted",
......@@ -160,11 +168,16 @@ enum SpeechSynthesisErrorCode {
"not-allowed",
};
[Exposed=Window]
[Exposed=Window,
Constructor(DOMString type, SpeechSynthesisErrorEventInit eventInitDict)]
interface SpeechSynthesisErrorEvent : SpeechSynthesisEvent {
readonly attribute SpeechSynthesisErrorCode error;
};
dictionary SpeechSynthesisErrorEventInit : SpeechSynthesisEventInit {
required SpeechSynthesisErrorCode error;
};
[Exposed=Window]
interface SpeechSynthesisVoice {
readonly attribute DOMString voiceURI;
......
......@@ -8,7 +8,7 @@
async_test(t => {
const utter = new SpeechSynthesisUtterance('1');
utter.onerror = t.step_func_done((e) => {
assert_equals(e.name, "not-allowed");
assert_equals(e.error, "not-allowed");
});
utter.onend = t.step_func_done(() => assert_unreached());
speechSynthesis.speak(utter);
......
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
/*
[Exposed=Window,
Constructor(DOMString type, SpeechSynthesisErrorEventInit eventInitDict)]
interface SpeechSynthesisErrorEvent : SpeechSynthesisErrorEvent {
readonly attribute SpeechSynthesisErrorCode error;
};
*/
test(() => {
assert_throws(new TypeError(), () => {
new SpeechSynthesisErrorEvent();
});
}, "SpeechSynthesisErrorEvent with no arguments throws TypeError");
test(() => {
assert_throws(new TypeError(), () => {
new SpeechSynthesisErrorEvent("type");
});
}, "SpeechSynthesisErrorEvent with no eventInitDict throws TypeError");
test(() => {
assert_throws(new TypeError(), () => {
new SpeechSynthesisErrorEvent("type", {});
});
}, `SpeechSynthesisErrorEvent with empty eventInitDict throws TypeError (requires
utterance and error)`);
test(() => {
assert_throws(new TypeError(), () => {
new SpeechSynthesisErrorEvent("type", {error:"not-allowed"});
});
}, `SpeechSynthesisErrorEvent with eventInitDict without utterance throws
TypeError`);
test(() => {
assert_throws(new TypeError(), () => {
new SpeechSynthesisErrorEvent("type", {utterance: new SpeechSynthesisUtterance()});
});
}, `SpeechSynthesisErrorEvent with eventInitDict without error throws
TypeError`);
test(() => {
const utterance = new SpeechSynthesisUtterance("foo");
const event = new SpeechSynthesisErrorEvent("type", {utterance: utterance, error:"not-allowed"});
assert_equals(event.utterance, utterance);
assert_equals(event.error, "not-allowed");
assert_equals(event.charIndex, 0);
assert_equals(event.elapsedTime, 0);
assert_equals(event.name, "");
}, "SpeechSynthesisErrorEvent with eventInitDict having utterance and error");
test(() => {
const utterance = new SpeechSynthesisUtterance("foo");
const event = new SpeechSynthesisErrorEvent("type", {
utterance: utterance,
charIndex: 5,
elapsedTime: 100,
name: "foo",
error: "synthesis-failed"
});
assert_equals(event.bubbles, false);
assert_equals(event.cancelable, false);
assert_equals(event.type, "type");
assert_equals(event.utterance, utterance);
assert_equals(event.charIndex, 5);
assert_equals(event.elapsedTime, 100);
assert_equals(event.name, "foo");
assert_equals(event.error, "synthesis-failed");
}, "SpeechSynthesisErrorEvent with custom eventInitDict");
test(() => {
function createEventFunc(error) {
return () => {
new SpeechSynthesisErrorEvent("type", {
utterance: new SpeechSynthesisUtterance(),
error: error
});
};
};
assert_throws(new TypeError(), createEventFunc(""));
assert_throws(new TypeError(), createEventFunc("foo"));
assert_throws(new TypeError(), createEventFunc("bar"));
}, "SpeechSynthesisErrorEvent with wrong error enum");
</script>
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
/*
[Exposed=Window,
Constructor(DOMString type, SpeechSynthesisEventInit eventInitDict)]
interface SpeechSynthesisEvent : Event {
readonly attribute SpeechSynthesisUtterance utterance;
readonly attribute unsigned long charIndex;
readonly attribute float elapsedTime;
readonly attribute DOMString name;
};
*/
test(() => {
assert_throws(new TypeError(), () => {
new SpeechSynthesisEvent();
});
}, "SpeechSynthesisEvent with no arguments throws TypeError");
test(() => {
assert_throws(new TypeError(), () => {
new SpeechSynthesisEvent("type");
});
}, "SpeechSynthesisEvent with no eventInitDict throws TypeError");
test(() => {
assert_throws(new TypeError(), () => {
new SpeechSynthesisEvent("type", {});
});
}, `SpeechSynthesisEvent with empty eventInitDict throws TypeError (requires
utterance)`);
test(() => {
assert_throws(new TypeError(), () => {
new SpeechSynthesisEvent("type", {charIndex: 10, elapsedTime: 50, name:"foo"});
});
}, `SpeechSynthesisEvent with eventInitDict not having utterance throws
TypeError`);
test(() => {
const utterance = new SpeechSynthesisUtterance("foo");
const event = new SpeechSynthesisEvent("type", {utterance: utterance});
assert_equals(event.utterance, utterance);
assert_equals(event.charIndex, 0);
assert_equals(event.elapsedTime, 0);
assert_equals(event.name, "");
}, "SpeechSynthesisEvent with eventInitDict having an utterance");
test(() => {
const utterance = new SpeechSynthesisUtterance("foo");
const event = new SpeechSynthesisEvent("type", {
utterance: utterance,
charIndex: 5,
elapsedTime: 100,
name: "foo"
});
assert_equals(event.bubbles, false);
assert_equals(event.cancelable, false);
assert_equals(event.type, "type");
assert_equals(event.utterance, utterance);
assert_equals(event.charIndex, 5);
assert_equals(event.elapsedTime, 100);
assert_equals(event.name, "foo");
}, "SpeechSynthesisEvent with custom eventInitDict");
</script>
This is a testharness.js-based test.
Found 216 tests; 60 PASS, 156 FAIL, 0 TIMEOUT, 0 NOTRUN.
Found 216 tests; 67 PASS, 149 FAIL, 0 TIMEOUT, 0 NOTRUN.
PASS idl_test setup
PASS Partial interface Window: original interface defined
FAIL SpeechRecognition interface: existence and properties of interface object assert_own_property: self does not have own property "SpeechRecognition" expected property "SpeechRecognition" missing
......@@ -195,13 +195,13 @@ PASS SpeechSynthesisEvent interface: attribute utterance
PASS SpeechSynthesisEvent interface: attribute charIndex
PASS SpeechSynthesisEvent interface: attribute elapsedTime
PASS SpeechSynthesisEvent interface: attribute name
FAIL SpeechSynthesisErrorEvent interface: existence and properties of interface object assert_own_property: self does not have own property "SpeechSynthesisErrorEvent" expected property "SpeechSynthesisErrorEvent" missing
FAIL SpeechSynthesisErrorEvent interface object length assert_own_property: self does not have own property "SpeechSynthesisErrorEvent" expected property "SpeechSynthesisErrorEvent" missing
FAIL SpeechSynthesisErrorEvent interface object name assert_own_property: self does not have own property "SpeechSynthesisErrorEvent" expected property "SpeechSynthesisErrorEvent" missing
FAIL SpeechSynthesisErrorEvent interface: existence and properties of interface prototype object assert_own_property: self does not have own property "SpeechSynthesisErrorEvent" expected property "SpeechSynthesisErrorEvent" missing
FAIL SpeechSynthesisErrorEvent interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "SpeechSynthesisErrorEvent" expected property "SpeechSynthesisErrorEvent" missing
FAIL SpeechSynthesisErrorEvent interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "SpeechSynthesisErrorEvent" expected property "SpeechSynthesisErrorEvent" missing
FAIL SpeechSynthesisErrorEvent interface: attribute error assert_own_property: self does not have own property "SpeechSynthesisErrorEvent" expected property "SpeechSynthesisErrorEvent" missing
PASS SpeechSynthesisErrorEvent interface: existence and properties of interface object
PASS SpeechSynthesisErrorEvent interface object length
PASS SpeechSynthesisErrorEvent interface object name
PASS SpeechSynthesisErrorEvent interface: existence and properties of interface prototype object
PASS SpeechSynthesisErrorEvent interface: existence and properties of interface prototype object's "constructor" property
PASS SpeechSynthesisErrorEvent interface: existence and properties of interface prototype object's @@unscopables property
PASS SpeechSynthesisErrorEvent interface: attribute error
FAIL SpeechSynthesisVoice interface: existence and properties of interface object assert_own_property: self does not have own property "SpeechSynthesisVoice" expected property "SpeechSynthesisVoice" missing
FAIL SpeechSynthesisVoice interface object length assert_own_property: self does not have own property "SpeechSynthesisVoice" expected property "SpeechSynthesisVoice" missing
FAIL SpeechSynthesisVoice interface object name assert_own_property: self does not have own property "SpeechSynthesisVoice" expected property "SpeechSynthesisVoice" missing
......
......@@ -6335,6 +6335,10 @@ interface SourceBufferList : EventTarget
method constructor
setter onaddsourcebuffer
setter onremovesourcebuffer
interface SpeechSynthesisErrorEvent : SpeechSynthesisEvent
attribute @@toStringTag
getter error
method constructor
interface SpeechSynthesisEvent : Event
attribute @@toStringTag
getter charIndex
......
......@@ -7070,6 +7070,10 @@ interface SourceBufferList : EventTarget
method constructor
setter onaddsourcebuffer
setter onremovesourcebuffer
interface SpeechSynthesisErrorEvent : SpeechSynthesisEvent
attribute @@toStringTag
getter error
method constructor
interface SpeechSynthesisEvent : Event
attribute @@toStringTag
getter charIndex
......
......@@ -52,6 +52,7 @@ generate_event_interfaces("modules_bindings_generated_event_interfaces") {
"//third_party/blink/renderer/modules/service_worker/install_event.idl",
"//third_party/blink/renderer/modules/speech/speech_recognition_error.idl",
"//third_party/blink/renderer/modules/speech/speech_recognition_event.idl",
"//third_party/blink/renderer/modules/speech/speech_synthesis_error_event.idl",
"//third_party/blink/renderer/modules/speech/speech_synthesis_event.idl",
"//third_party/blink/renderer/modules/storage/storage_event.idl",
"//third_party/blink/renderer/modules/vr/vr_display_event.idl",
......
......@@ -302,6 +302,7 @@ modules_idl_files =
"speech/speech_recognition_result_list.idl",
"speech/speech_synthesis.idl",
"speech/speech_synthesis_event.idl",
"speech/speech_synthesis_error_event.idl",
"speech/speech_synthesis_utterance.idl",
"speech/speech_synthesis_voice.idl",
"storage/storage.idl",
......@@ -477,6 +478,8 @@ modules_dictionary_idl_files =
"app_banner/before_install_prompt_event_init.idl",
"background_fetch/background_fetch_event_init.idl",
"background_fetch/background_fetch_options.idl",
"speech/speech_synthesis_error_event_init.idl",
"speech/speech_synthesis_event_init.idl",
"background_fetch/background_fetch_ui_options.idl",
"background_sync/sync_event_init.idl",
"bluetooth/bluetooth_le_scan_filter_init.idl",
......
......@@ -30,6 +30,8 @@ blink_modules_sources("speech") {
"speech_recognition_result_list.h",
"speech_synthesis.cc",
"speech_synthesis.h",
"speech_synthesis_error_event.cc",
"speech_synthesis_error_event.h",
"speech_synthesis_event.cc",
"speech_synthesis_event.h",
"speech_synthesis_utterance.cc",
......
......@@ -32,7 +32,10 @@
#include "third_party/blink/renderer/core/html/media/autoplay_policy.h"
#include "third_party/blink/renderer/core/timing/dom_window_performance.h"
#include "third_party/blink/renderer/core/timing/performance.h"
#include "third_party/blink/renderer/modules/speech/speech_synthesis_error_event.h"
#include "third_party/blink/renderer/modules/speech/speech_synthesis_error_event_init.h"
#include "third_party/blink/renderer/modules/speech/speech_synthesis_event.h"
#include "third_party/blink/renderer/modules/speech/speech_synthesis_event_init.h"
#include "third_party/blink/renderer/platform/speech/platform_speech_synthesis_voice.h"
namespace blink {
......@@ -158,9 +161,28 @@ void SpeechSynthesis::FireEvent(const AtomicString& type,
if (!GetElapsedTimeMillis(&millis))
return;
double elapsed_time_millis = millis - utterance->StartTime() * 1000.0;
utterance->DispatchEvent(*SpeechSynthesisEvent::Create(
type, utterance, char_index, elapsed_time_millis, name));
SpeechSynthesisEventInit init;
init.setUtterance(utterance);
init.setCharIndex(char_index);
init.setElapsedTime(millis - (utterance->StartTime() * 1000.0));
init.setName(name);
utterance->DispatchEvent(*SpeechSynthesisEvent::Create(type, init));
}
void SpeechSynthesis::FireErrorEvent(SpeechSynthesisUtterance* utterance,
unsigned long char_index,
const String& error) {
double millis;
if (!GetElapsedTimeMillis(&millis))
return;
SpeechSynthesisErrorEventInit init;
init.setUtterance(utterance);
init.setCharIndex(char_index);
init.setElapsedTime(millis - (utterance->StartTime() * 1000.0));
init.setError(error);
utterance->DispatchEvent(
*SpeechSynthesisErrorEvent::Create(EventTypeNames::error, init));
}
void SpeechSynthesis::HandleSpeakingCompleted(
......@@ -180,8 +202,13 @@ void SpeechSynthesis::HandleSpeakingCompleted(
// sent an event on an utterance before it got the message that we
// canceled it, and we should always report to the user what actually
// happened.
FireEvent(error_occurred ? EventTypeNames::error : EventTypeNames::end,
utterance, 0, String());
if (error_occurred) {
// TODO(csharrison): Actually pass the correct message. For now just use a
// generic error.
FireErrorEvent(utterance, 0, "synthesis-failed");
} else {
FireEvent(EventTypeNames::end, utterance, 0, String());
}
// Start the next utterance if we just finished one and one was pending.
if (should_start_speaking && !utterance_queue_.IsEmpty())
......
......@@ -92,6 +92,10 @@ class MODULES_EXPORT SpeechSynthesis final
unsigned long char_index,
const String& name);
void FireErrorEvent(SpeechSynthesisUtterance*,
unsigned long char_index,
const String& error);
// Returns the utterance at the front of the queue.
SpeechSynthesisUtterance* CurrentSpeechUtterance() const;
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/speech/speech_synthesis_error_event.h"
namespace blink {
// static
SpeechSynthesisErrorEvent* SpeechSynthesisErrorEvent::Create(
const AtomicString& type,
const SpeechSynthesisErrorEventInit& init) {
return new SpeechSynthesisErrorEvent(type, init);
}
SpeechSynthesisErrorEvent::SpeechSynthesisErrorEvent(
const AtomicString& type,
const SpeechSynthesisErrorEventInit& init)
: SpeechSynthesisEvent(type,
init.utterance(),
init.charIndex(),
init.elapsedTime(),
init.name()),
error_(init.error()) {}
} // namespace blink
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_SPEECH_SPEECH_SYNTHESIS_ERROR_EVENT_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_SPEECH_SPEECH_SYNTHESIS_ERROR_EVENT_H_
#include "third_party/blink/renderer/modules/event_modules.h"
#include "third_party/blink/renderer/modules/speech/speech_synthesis_error_event_init.h"
#include "third_party/blink/renderer/modules/speech/speech_synthesis_event.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
class SpeechSynthesisErrorEvent : public SpeechSynthesisEvent {
DEFINE_WRAPPERTYPEINFO();
public:
static SpeechSynthesisErrorEvent* Create(
const AtomicString& type,
const SpeechSynthesisErrorEventInit& init);
const String error() const { return error_; }
private:
SpeechSynthesisErrorEvent(const AtomicString& type,
const SpeechSynthesisErrorEventInit& init);
const String error_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_SPEECH_SPEECH_SYNTHESIS_ERROR_EVENT_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// https://w3c.github.io/speech-api/#enumdef-speechsynthesiserrorcode
enum SpeechSynthesisErrorCode {
"canceled",
"interrupted",
"audio-busy",
"audio-hardware",
"network",
"synthesis-unavailable",
"synthesis-failed",
"language-unavailable",
"voice-unavailable",
"text-too-long",
"invalid-argument",
"not-allowed",
};
// https://w3c.github.io/speech-api/#enumdef-speechsynthesiserrorcode
[
Exposed=Window,
Constructor(DOMString type, SpeechSynthesisErrorEventInit eventInitDict),
RuntimeEnabled=ScriptedSpeech
] interface SpeechSynthesisErrorEvent : SpeechSynthesisEvent {
readonly attribute SpeechSynthesisErrorCode error;
};
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
dictionary SpeechSynthesisErrorEventInit : SpeechSynthesisEventInit {
required SpeechSynthesisErrorCode error;
};
......@@ -27,22 +27,13 @@
namespace blink {
SpeechSynthesisEvent* SpeechSynthesisEvent::Create() {
return new SpeechSynthesisEvent;
}
SpeechSynthesisEvent* SpeechSynthesisEvent::Create(
const AtomicString& type,
SpeechSynthesisUtterance* utterance,
unsigned char_index,
float elapsed_time,
const String& name) {
return new SpeechSynthesisEvent(type, utterance, char_index, elapsed_time,
name);
const SpeechSynthesisEventInit& init) {
return new SpeechSynthesisEvent(type, init.utterance(), init.charIndex(),
init.elapsedTime(), init.name());
}
SpeechSynthesisEvent::SpeechSynthesisEvent() = default;
SpeechSynthesisEvent::SpeechSynthesisEvent(const AtomicString& type,
SpeechSynthesisUtterance* utterance,
unsigned char_index,
......
......@@ -27,20 +27,17 @@
#define THIRD_PARTY_BLINK_RENDERER_MODULES_SPEECH_SPEECH_SYNTHESIS_EVENT_H_
#include "third_party/blink/renderer/modules/event_modules.h"
#include "third_party/blink/renderer/modules/speech/speech_synthesis_event_init.h"
#include "third_party/blink/renderer/modules/speech/speech_synthesis_utterance.h"
namespace blink {
class SpeechSynthesisEvent final : public Event {
class SpeechSynthesisEvent : public Event {
DEFINE_WRAPPERTYPEINFO();
public:
static SpeechSynthesisEvent* Create();
static SpeechSynthesisEvent* Create(const AtomicString& type,
SpeechSynthesisUtterance*,
unsigned char_index,
float elapsed_time,
const String& name);
const SpeechSynthesisEventInit& init);
SpeechSynthesisUtterance* utterance() const { return utterance_; }
unsigned charIndex() const { return char_index_; }
......@@ -53,14 +50,14 @@ class SpeechSynthesisEvent final : public Event {
void Trace(blink::Visitor*) override;
private:
SpeechSynthesisEvent();
protected:
SpeechSynthesisEvent(const AtomicString& type,
SpeechSynthesisUtterance*,
unsigned char_index,
float elapsed_time,
const String& name);
private:
Member<SpeechSynthesisUtterance> utterance_;
unsigned char_index_;
float elapsed_time_;
......
......@@ -23,10 +23,10 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// https://dvcs.w3.org/hg/speech-api/raw-file/tip/webspeechapi.html#tts-section
// https://w3c.github.io/speech-api/#speechsynthesisevent
[
RuntimeEnabled=ScriptedSpeech
RuntimeEnabled=ScriptedSpeech,
Constructor(DOMString type, SpeechSynthesisEventInit eventInitDict)
] interface SpeechSynthesisEvent : Event {
readonly attribute SpeechSynthesisUtterance utterance;
readonly attribute unsigned long charIndex;
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
dictionary SpeechSynthesisEventInit : EventInit {
required SpeechSynthesisUtterance utterance;
unsigned long charIndex = 0;
float elapsedTime = 0;
DOMString name = "";
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment