зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1473467: implement AudioWorkletGlobalScope::RegisterProcessor(). r=baku,karlt
Differential Revision: https://phabricator.services.mozilla.com/D6368 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
81a039f147
Коммит
bb6b29da9d
|
@ -98,6 +98,10 @@ DOMInterfaces = {
|
|||
'nativeType': 'mozilla::dom::Worklet',
|
||||
},
|
||||
|
||||
'AudioWorkletGlobalScope': {
|
||||
'implicitJSContext': [ 'registerProcessor' ],
|
||||
},
|
||||
|
||||
'BarProp': {
|
||||
'headerFile': 'mozilla/dom/BarProps.h',
|
||||
},
|
||||
|
|
|
@ -115,3 +115,4 @@ MSG_DEF(MSG_VALUE_OUT_OF_RANGE, 1, JSEXN_RANGEERR, "The value for the {0} is out
|
|||
MSG_DEF(MSG_INVALID_PANNERNODE_REFDISTANCE_ERROR, 0, JSEXN_RANGEERR, "The refDistance value passed to PannerNode must not be negative.")
|
||||
MSG_DEF(MSG_INVALID_PANNERNODE_MAXDISTANCE_ERROR, 0, JSEXN_RANGEERR, "The maxDistance value passed to PannerNode must be positive.")
|
||||
MSG_DEF(MSG_INVALID_PANNERNODE_ROLLOFF_ERROR, 0, JSEXN_RANGEERR, "The rolloffFactor value passed to PannerNode must not be negative.")
|
||||
MSG_DEF(MSG_NOT_ARRAY_NOR_UNDEFINED, 1, JSEXN_TYPEERR, "{0} is neither an array nor undefined.")
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
[Global=(Worklet,AudioWorklet),Exposed=AudioWorklet]
|
||||
interface AudioWorkletGlobalScope : WorkletGlobalScope {
|
||||
[Throws]
|
||||
void registerProcessor (DOMString name, VoidFunction processorCtor);
|
||||
readonly attribute unsigned long long currentFrame;
|
||||
readonly attribute double currentTime;
|
||||
|
|
|
@ -5,13 +5,22 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "AudioWorkletGlobalScope.h"
|
||||
#include "WorkletPrincipal.h"
|
||||
#include "jsapi.h"
|
||||
#include "mozilla/dom/AudioWorkletGlobalScopeBinding.h"
|
||||
#include "mozilla/dom/FunctionBinding.h"
|
||||
#include "WorkletPrincipal.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioWorkletGlobalScope, WorkletGlobalScope,
|
||||
mNameToProcessorMap);
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioWorkletGlobalScope)
|
||||
NS_INTERFACE_MAP_END_INHERITING(WorkletGlobalScope)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(AudioWorkletGlobalScope, WorkletGlobalScope)
|
||||
NS_IMPL_RELEASE_INHERITED(AudioWorkletGlobalScope, WorkletGlobalScope)
|
||||
|
||||
AudioWorkletGlobalScope::AudioWorkletGlobalScope(WorkletImpl* aImpl)
|
||||
: WorkletGlobalScope(aImpl)
|
||||
, mCurrentFrame(0)
|
||||
|
@ -31,10 +40,153 @@ AudioWorkletGlobalScope::WrapGlobalObject(JSContext* aCx,
|
|||
}
|
||||
|
||||
void
|
||||
AudioWorkletGlobalScope::RegisterProcessor(const nsAString& aType,
|
||||
VoidFunction& aProcessorCtor)
|
||||
AudioWorkletGlobalScope::RegisterProcessor(JSContext* aCx,
|
||||
const nsAString& aName,
|
||||
VoidFunction& aProcessorCtor,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
// Nothing to do here.
|
||||
JS::Rooted<JSObject*> processorConstructor(aCx, aProcessorCtor.CallableOrNull());
|
||||
|
||||
/**
|
||||
* 1. If the name is the empty string, throw a NotSupportedError
|
||||
* exception and abort these steps because the empty string is not
|
||||
* a valid key.
|
||||
*/
|
||||
if (aName.IsEmpty()) {
|
||||
aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
|
||||
NS_LITERAL_CSTRING(
|
||||
"Argument 1 of AudioWorkletGlobalScope.registerProcessor "
|
||||
"should not be an empty string."));
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 2. If the name exists as a key in the node name to processor
|
||||
* definition map, throw a NotSupportedError exception and abort
|
||||
* these steps because registering a definition with a duplicated
|
||||
* key is not allowed.
|
||||
*/
|
||||
if (mNameToProcessorMap.GetWeak(aName)) {
|
||||
// Duplicate names are not allowed
|
||||
aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
|
||||
NS_LITERAL_CSTRING(
|
||||
"Argument 1 of AudioWorkletGlobalScope.registerProcessor "
|
||||
"is invalid: a class with the same name is already registered."));
|
||||
return;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> constructorUnwrapped(
|
||||
aCx, js::CheckedUnwrap(processorConstructor));
|
||||
if (!constructorUnwrapped) {
|
||||
// If the caller's compartment does not have permission to access the
|
||||
// unwrapped constructor then throw.
|
||||
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. If the result of IsConstructor(argument=processorCtor) is false,
|
||||
* throw a TypeError and abort these steps.
|
||||
*/
|
||||
if (!JS::IsConstructor(constructorUnwrapped)) {
|
||||
aRv.ThrowTypeError<MSG_NOT_CONSTRUCTOR>(NS_LITERAL_STRING(
|
||||
"Argument 2 of AudioWorkletGlobalScope.registerProcessor"));
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 4. Let prototype be the result of Get(O=processorCtor, P="prototype").
|
||||
*/
|
||||
// The .prototype on the constructor passed could be an "expando" of a
|
||||
// wrapper. So we should get it from wrapper instead of the underlying
|
||||
// object.
|
||||
JS::Rooted<JS::Value> prototype(aCx);
|
||||
if (!JS_GetProperty(aCx, processorConstructor, "prototype", &prototype)) {
|
||||
aRv.NoteJSContextException(aCx);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 5. If the result of Type(argument=prototype) is not Object, throw a
|
||||
* TypeError and abort all these steps.
|
||||
*/
|
||||
if (!prototype.isObject()) {
|
||||
aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING(
|
||||
"Argument 2 of AudioWorkletGlobalScope.registerProcessor "
|
||||
"processorCtor.prototype"));
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 6. If the result of IsCallable(argument=Get(O=prototype, P="process"))
|
||||
* is false, throw a TypeError and abort these steps.
|
||||
*/
|
||||
JS::Rooted<JS::Value> process(aCx);
|
||||
JS::Rooted<JSObject*> prototypeObject(aCx, &prototype.toObject());
|
||||
if (!JS_GetProperty(aCx, prototypeObject, "process", &process)) {
|
||||
aRv.NoteJSContextException(aCx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!process.isObjectOrNull() ||
|
||||
!JS::IsCallable(process.toObjectOrNull())) {
|
||||
aRv.ThrowTypeError<MSG_NOT_CALLABLE>(NS_LITERAL_STRING(
|
||||
"Argument 2 of AudioWorkletGlobalScope.registerProcessor "
|
||||
"constructor.process"));
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 7. Let descriptors be the result of Get(O=processorCtor,
|
||||
* P="parameterDescriptors").
|
||||
*/
|
||||
JS::Rooted<JS::Value> descriptors(aCx);
|
||||
if (!JS_GetProperty(aCx, processorConstructor, "parameterDescriptors",
|
||||
&descriptors)) {
|
||||
aRv.NoteJSContextException(aCx);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 8. If descriptors is neither an array nor undefined, throw a
|
||||
* TypeError and abort these steps.
|
||||
*/
|
||||
bool isArray = false;
|
||||
if (!JS_IsArrayObject(aCx, descriptors, &isArray)) {
|
||||
// I would assume isArray won't be set to true if JS_IsArrayObject
|
||||
// failed, but just in case, force it to false
|
||||
isArray = false;
|
||||
JS_ClearPendingException(aCx);
|
||||
}
|
||||
|
||||
if (!descriptors.isUndefined() && !isArray) {
|
||||
aRv.ThrowTypeError<MSG_NOT_ARRAY_NOR_UNDEFINED>(NS_LITERAL_STRING(
|
||||
"Argument 2 of AudioWorkletGlobalScope.registerProcessor "
|
||||
"constructor.parameterDescriptors"));
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 9. Let definition be a new AudioWorkletProcessor definition with:
|
||||
* - node name being name
|
||||
* - processor class constructor being processorCtor
|
||||
* 10. Add the key-value pair (name - definition) to the node name to
|
||||
* processor definition map of the associated AudioWorkletGlobalScope.
|
||||
*/
|
||||
if (!mNameToProcessorMap.Put(aName, &aProcessorCtor, fallible)) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 11. Queue a task to the control thread to add the key-value pair
|
||||
* (name - descriptors) to the node name to parameter descriptor
|
||||
* map of the associated BaseAudioContext.
|
||||
*/
|
||||
// TODO: we don't have a proper mechanism to communicate with the
|
||||
// control thread currently. See
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1473467#c3
|
||||
// and https://bugzilla.mozilla.org/show_bug.cgi?id=1492014
|
||||
}
|
||||
|
||||
uint64_t AudioWorkletGlobalScope::CurrentFrame() const
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
#ifndef mozilla_dom_AudioWorkletGlobalScope_h
|
||||
#define mozilla_dom_AudioWorkletGlobalScope_h
|
||||
|
||||
#include "mozilla/dom/FunctionBinding.h"
|
||||
#include "mozilla/dom/WorkletGlobalScope.h"
|
||||
#include "nsRefPtrHashtable.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -15,11 +17,13 @@ class WorkletImpl;
|
|||
|
||||
namespace dom {
|
||||
|
||||
class VoidFunction;
|
||||
|
||||
class AudioWorkletGlobalScope final : public WorkletGlobalScope
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioWorkletGlobalScope, WorkletGlobalScope);
|
||||
|
||||
explicit AudioWorkletGlobalScope(WorkletImpl* aImpl);
|
||||
|
||||
bool
|
||||
|
@ -27,8 +31,9 @@ public:
|
|||
JS::MutableHandle<JSObject*> aReflector) override;
|
||||
|
||||
void
|
||||
RegisterProcessor(const nsAString& aType,
|
||||
VoidFunction& aProcessorCtor);
|
||||
RegisterProcessor(JSContext* aCx, const nsAString& aName,
|
||||
VoidFunction& aProcessorCtor,
|
||||
ErrorResult& aRv);
|
||||
|
||||
uint64_t CurrentFrame() const;
|
||||
|
||||
|
@ -42,6 +47,9 @@ private:
|
|||
uint64_t mCurrentFrame;
|
||||
double mCurrentTime;
|
||||
float mSampleRate;
|
||||
|
||||
typedef nsRefPtrHashtable<nsStringHashKey, VoidFunction> NodeNameToProcessorDefinitionMap;
|
||||
NodeNameToProcessorDefinitionMap mNameToProcessorMap;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -16,6 +16,8 @@ support-files=worklet_dump.js
|
|||
scheme = http
|
||||
[test_audioWorklet.html]
|
||||
support-files=worklet_audioWorklet.js
|
||||
[test_audioWorkletGlobalScopeRegisterProcessor.html]
|
||||
support-files=worklet_test_audioWorkletGlobalScopeRegisterProcessor.js
|
||||
[test_exception.html]
|
||||
support-files=worklet_exception.js
|
||||
[test_paintWorklet.html]
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for AudioWorklet</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript" src="common.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="application/javascript">
|
||||
|
||||
function configureTest() {
|
||||
|
||||
var expected_errors = [
|
||||
"TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor is not a constructor.",
|
||||
"NotSupportedError: Argument 1 of AudioWorkletGlobalScope.registerProcessor should not be an empty string.",
|
||||
"TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor is not an object.",
|
||||
"TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor constructor.process is not callable.",
|
||||
"TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor constructor.process is not callable.",
|
||||
"TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor constructor.parameterDescriptors is neither an array nor undefined.",
|
||||
"NotSupportedError: Argument 1 of AudioWorkletGlobalScope.registerProcessor is invalid: a class with the same name is already registered.",
|
||||
];
|
||||
|
||||
var expected_errors_i = 0;
|
||||
|
||||
function consoleListener() {
|
||||
SpecialPowers.addObserver(this, "console-api-log-event");
|
||||
}
|
||||
|
||||
consoleListener.prototype = {
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
if (aTopic == "console-api-log-event") {
|
||||
var obj = aSubject.wrappedJSObject;
|
||||
if (obj.arguments[0] == expected_errors[expected_errors_i]) {
|
||||
ok(true, "Expected error received: " + obj.arguments[0]);
|
||||
expected_errors_i++;
|
||||
}
|
||||
|
||||
if (expected_errors_i == expected_errors.length) {
|
||||
// All errors have been received, this test has been completed
|
||||
// succesfully!
|
||||
SpecialPowers.removeObserver(this, "console-api-log-event");
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cl = new consoleListener();
|
||||
|
||||
return SpecialPowers.pushPrefEnv(
|
||||
{"set": [["dom.audioworklet.enabled", true],
|
||||
["dom.worklet.enabled", true]]});
|
||||
}
|
||||
|
||||
// This function is called into an iframe.
|
||||
function runTestInIframe() {
|
||||
ok(window.isSecureContext, "Test should run in secure context");
|
||||
var audioContext = new AudioContext();
|
||||
ok(audioContext.audioWorklet instanceof AudioWorklet,
|
||||
"AudioContext.audioWorklet should be an instance of AudioWorklet");
|
||||
audioContext.audioWorklet.addModule("worklet_test_audioWorkletGlobalScopeRegisterProcessor.js")
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,3 +1,12 @@
|
|||
// This should work for real... at some point.
|
||||
registerProcessor("sure!", () => {});
|
||||
class DummyProcessWorkletProcessor extends AudioWorkletProcessor {
|
||||
constructor() { super(); }
|
||||
|
||||
process() {
|
||||
// Do nothing, output silence
|
||||
}
|
||||
}
|
||||
|
||||
// We need to pass a valid AudioWorkletProcessor here, otherwise, it will fail,
|
||||
// and the console.log won't be executed
|
||||
registerProcessor("sure!", DummyProcessWorkletProcessor);
|
||||
console.log(this instanceof AudioWorkletGlobalScope ? "So far so good" : "error");
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
// Define several classes.
|
||||
// Only the last ones are valid.
|
||||
class EmptyWorkletProcessor extends AudioWorkletProcessor {
|
||||
}
|
||||
|
||||
class NoProcessWorkletProcessor extends AudioWorkletProcessor {
|
||||
constructor() { super(); }
|
||||
}
|
||||
|
||||
class BadDescriptorsWorkletProcessor extends AudioWorkletProcessor {
|
||||
constructor() { super(); }
|
||||
|
||||
process() {
|
||||
// Do nothing, output silence
|
||||
}
|
||||
|
||||
static get parameterDescriptors() {
|
||||
return "A string";
|
||||
}
|
||||
}
|
||||
|
||||
class GoodDescriptorsWorkletProcessor extends AudioWorkletProcessor {
|
||||
constructor() { super(); }
|
||||
|
||||
process() {
|
||||
// Do nothing, output silence
|
||||
}
|
||||
|
||||
static get parameterDescriptors() {
|
||||
return [{
|
||||
name: 'myParam', defaultValue: 0.707
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
class DummyProcessWorkletProcessor extends AudioWorkletProcessor {
|
||||
constructor() { super(); }
|
||||
|
||||
process() {
|
||||
// Do nothing, output silence
|
||||
}
|
||||
}
|
||||
|
||||
// Test not a constructor
|
||||
// "TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor is not a constructor."
|
||||
try {
|
||||
registerProcessor("sure!", () => {});
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
// Test empty name
|
||||
// "NotSupportedError: Argument 1 of AudioWorkletGlobalScope.registerProcessor should not be an empty string."
|
||||
try {
|
||||
registerProcessor("", EmptyWorkletProcessor);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
// Test not an object
|
||||
// "TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor is not an object."
|
||||
try {
|
||||
registerProcessor("my-worklet-processor", "");
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
// Test Empty class definition
|
||||
// "TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor constructor.process is not callable."
|
||||
try {
|
||||
registerProcessor("empty-worklet-processor", EmptyWorkletProcessor);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
// Test class with constructor but not process function
|
||||
// "TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor constructor.process is not callable."
|
||||
try {
|
||||
registerProcessor("no-worklet-processor", NoProcessWorkletProcessor);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
// Test class with parameterDescriptors not being array nor undefined
|
||||
// "TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor constructor.parameterDescriptors is neither an array nor undefined."
|
||||
try {
|
||||
registerProcessor("bad-descriptors-worklet-processor", BadDescriptorsWorkletProcessor);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
// Test class with good parameterDescriptors
|
||||
// No error expected here
|
||||
registerProcessor("good-descriptors-worklet-processor", GoodDescriptorsWorkletProcessor);
|
||||
|
||||
// Test class with constructor and process function
|
||||
// No error expected here
|
||||
registerProcessor("dummy-worklet-processor", DummyProcessWorkletProcessor);
|
||||
|
||||
// Test class adding class with the same name twice
|
||||
// "NotSupportedError: Operation is not supported: Argument 1 of AudioWorkletGlobalScope.registerProcessor is invalid: a class with the same name is already registered."
|
||||
try {
|
||||
registerProcessor("dummy-worklet-processor", DummyProcessWorkletProcessor);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
Загрузка…
Ссылка в новой задаче