summaryrefslogtreecommitdiffstats
path: root/dom/media/webaudio/AudioWorkletGlobalScope.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webaudio/AudioWorkletGlobalScope.cpp')
-rw-r--r--dom/media/webaudio/AudioWorkletGlobalScope.cpp370
1 files changed, 370 insertions, 0 deletions
diff --git a/dom/media/webaudio/AudioWorkletGlobalScope.cpp b/dom/media/webaudio/AudioWorkletGlobalScope.cpp
new file mode 100644
index 0000000000..cb13d7d8a5
--- /dev/null
+++ b/dom/media/webaudio/AudioWorkletGlobalScope.cpp
@@ -0,0 +1,370 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioWorkletGlobalScope.h"
+
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "AudioWorkletImpl.h"
+#include "jsapi.h"
+#include "js/ForOfIterator.h"
+#include "js/PropertyAndElement.h" // JS_GetProperty
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/AudioWorkletGlobalScopeBinding.h"
+#include "mozilla/dom/AudioWorkletProcessor.h"
+#include "mozilla/dom/BindingCallContext.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/AudioParamDescriptorBinding.h"
+#include "nsPrintfCString.h"
+#include "nsTHashSet.h"
+#include "Tracing.h"
+
+namespace mozilla::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(AudioWorkletImpl* aImpl)
+ : WorkletGlobalScope(aImpl) {}
+
+AudioWorkletImpl* AudioWorkletGlobalScope::Impl() const {
+ return static_cast<AudioWorkletImpl*>(mImpl.get());
+}
+
+bool AudioWorkletGlobalScope::WrapGlobalObject(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aReflector) {
+ // |this| is being exposed to JS and content script will soon be running.
+ // The graph needs a handle on the JSContext so it can interrupt JS.
+ Impl()->DestinationTrack()->Graph()->NotifyJSContext(aCx);
+
+ JS::RealmOptions options;
+
+ // TODO(bug 1834744)
+ options.behaviors().setShouldResistFingerprinting(
+ ShouldResistFingerprinting(RFPTarget::IsAlwaysEnabledForPrecompute));
+
+ // The SharedArrayBuffer global constructor property should not be present in
+ // a fresh global object when shared memory objects aren't allowed (because
+ // COOP/COEP support isn't enabled, or because COOP/COEP don't act to isolate
+ // this worklet to a separate process).
+ options.creationOptions().setDefineSharedArrayBufferConstructor(
+ IsSharedMemoryAllowed());
+
+ return AudioWorkletGlobalScope_Binding::Wrap(
+ aCx, this, this, options, BasePrincipal::Cast(mImpl->Principal()), true,
+ aReflector);
+}
+
+void AudioWorkletGlobalScope::RegisterProcessor(
+ JSContext* aCx, const nsAString& aName,
+ AudioWorkletProcessorConstructor& aProcessorCtor, ErrorResult& aRv) {
+ TRACE_COMMENT("AudioWorkletGlobalScope::RegisterProcessor", "%s",
+ NS_ConvertUTF16toUTF8(aName).get());
+
+ 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.ThrowNotSupportedError("Argument 1 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.ThrowNotSupportedError(
+ "Argument 1 is invalid: a class with the same name is already "
+ "registered.");
+ return;
+ }
+
+ // We know processorConstructor is callable, so not a WindowProxy or Location.
+ JS::Rooted<JSObject*> constructorUnwrapped(
+ aCx, js::CheckedUnwrapStatic(processorConstructor));
+ if (!constructorUnwrapped) {
+ // If the caller's compartment does not have permission to access the
+ // unwrapped constructor then throw.
+ aRv.ThrowSecurityError("Constructor cannot be called");
+ 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>("Argument 2");
+ 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>("processorCtor.prototype");
+ return;
+ }
+ /**
+ * 6. Let parameterDescriptorsValue 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;
+ }
+
+ AudioParamDescriptorMap map;
+ /*
+ * 7. If parameterDescriptorsValue is not undefined
+ */
+ if (!descriptors.isUndefined()) {
+ /*
+ * 7.1. Let parameterDescriptorSequence be the result of the conversion
+ * from parameterDescriptorsValue to an IDL value of type
+ * sequence<AudioParamDescriptor>.
+ */
+ JS::Rooted<JS::Value> objectValue(aCx, descriptors);
+ JS::ForOfIterator iter(aCx);
+ if (!iter.init(objectValue, JS::ForOfIterator::AllowNonIterable)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+ if (!iter.valueIsIterable()) {
+ aRv.ThrowTypeError<MSG_CONVERSION_ERROR>(
+ "AudioWorkletProcessor.parameterDescriptors", "sequence");
+ return;
+ }
+ /*
+ * 7.2 and 7.3 (and substeps)
+ */
+ map = DescriptorsFromJS(aCx, &iter, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ /**
+ * 8. Append the key-value pair name → processorCtor to node name to processor
+ * constructor map of the associated AudioWorkletGlobalScope.
+ */
+ if (!mNameToProcessorMap.InsertOrUpdate(aName, RefPtr{&aProcessorCtor},
+ fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ /**
+ * 9. 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.
+ */
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "AudioWorkletGlobalScope: parameter descriptors",
+ [impl = RefPtr{Impl()}, name = nsString(aName),
+ map = std::move(map)]() mutable {
+ AudioNode* destinationNode =
+ impl->DestinationTrack()->Engine()->NodeMainThread();
+ if (!destinationNode) {
+ return;
+ }
+ destinationNode->Context()->SetParamMapForWorkletName(name, &map);
+ }));
+}
+
+uint64_t AudioWorkletGlobalScope::CurrentFrame() const {
+ AudioNodeTrack* destinationTrack = Impl()->DestinationTrack();
+ GraphTime processedTime = destinationTrack->Graph()->ProcessedTime();
+ return destinationTrack->GraphTimeToTrackTime(processedTime);
+}
+
+double AudioWorkletGlobalScope::CurrentTime() const {
+ return static_cast<double>(CurrentFrame()) / SampleRate();
+}
+
+float AudioWorkletGlobalScope::SampleRate() const {
+ return static_cast<float>(Impl()->DestinationTrack()->mSampleRate);
+}
+
+AudioParamDescriptorMap AudioWorkletGlobalScope::DescriptorsFromJS(
+ JSContext* aCx, JS::ForOfIterator* aIter, ErrorResult& aRv) {
+ AudioParamDescriptorMap res;
+ // To check for duplicates
+ nsTHashSet<nsString> namesSet;
+
+ JS::Rooted<JS::Value> nextValue(aCx);
+ bool done = false;
+ size_t i = 0;
+ while (true) {
+ if (!aIter->next(&nextValue, &done)) {
+ aRv.NoteJSContextException(aCx);
+ return AudioParamDescriptorMap();
+ }
+ if (done) {
+ break;
+ }
+
+ BindingCallContext callCx(aCx, "AudioWorkletGlobalScope.registerProcessor");
+ nsPrintfCString sourceDescription("Element %zu in parameterDescriptors", i);
+ i++;
+ AudioParamDescriptor* descriptor = res.AppendElement(fallible);
+ if (!descriptor) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return AudioParamDescriptorMap();
+ }
+ if (!descriptor->Init(callCx, nextValue, sourceDescription.get())) {
+ aRv.NoteJSContextException(aCx);
+ return AudioParamDescriptorMap();
+ }
+ }
+
+ for (const auto& descriptor : res) {
+ if (namesSet.Contains(descriptor.mName)) {
+ aRv.ThrowNotSupportedError("Duplicated name \""_ns +
+ NS_ConvertUTF16toUTF8(descriptor.mName) +
+ "\" in parameterDescriptors."_ns);
+ return AudioParamDescriptorMap();
+ }
+
+ if (!namesSet.Insert(descriptor.mName, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return AudioParamDescriptorMap();
+ }
+
+ if (descriptor.mMinValue > descriptor.mMaxValue) {
+ aRv.ThrowInvalidStateError(
+ "In parameterDescriptors, "_ns +
+ NS_ConvertUTF16toUTF8(descriptor.mName) +
+ " minValue should be smaller than maxValue."_ns);
+ return AudioParamDescriptorMap();
+ }
+
+ if (descriptor.mDefaultValue < descriptor.mMinValue ||
+ descriptor.mDefaultValue > descriptor.mMaxValue) {
+ aRv.ThrowInvalidStateError(
+ "In parameterDescriptors, "_ns +
+ NS_ConvertUTF16toUTF8(descriptor.mName) +
+ nsLiteralCString(" defaultValue is out of the range defined by "
+ "minValue and maxValue."));
+ return AudioParamDescriptorMap();
+ }
+ }
+
+ return res;
+}
+
+bool AudioWorkletGlobalScope::ConstructProcessor(
+ JSContext* aCx, const nsAString& aName,
+ NotNull<StructuredCloneHolder*> aSerializedOptions,
+ UniqueMessagePortId& aPortIdentifier,
+ JS::MutableHandle<JSObject*> aRetProcessor) {
+ TRACE_COMMENT("AudioWorkletProcessor::ConstructProcessor", "%s",
+ NS_ConvertUTF16toUTF8(aName).get());
+ /**
+ * See
+ * https://webaudio.github.io/web-audio-api/#AudioWorkletProcessor-instantiation
+ */
+ ErrorResult rv;
+ /**
+ * 4. Let deserializedPort be the result of
+ * StructuredDeserialize(serializedPort, the current Realm).
+ */
+ RefPtr<MessagePort> deserializedPort =
+ MessagePort::Create(this, aPortIdentifier, rv);
+ if (NS_WARN_IF(rv.MaybeSetPendingException(aCx))) {
+ return false;
+ }
+ /**
+ * 5. Let deserializedOptions be the result of
+ * StructuredDeserialize(serializedOptions, the current Realm).
+ */
+ JS::CloneDataPolicy cloneDataPolicy;
+ cloneDataPolicy.allowIntraClusterClonableSharedObjects();
+ cloneDataPolicy.allowSharedMemoryObjects();
+
+ JS::Rooted<JS::Value> deserializedOptions(aCx);
+ aSerializedOptions->Read(this, aCx, &deserializedOptions, cloneDataPolicy,
+ rv);
+ if (rv.MaybeSetPendingException(aCx)) {
+ return false;
+ }
+ /**
+ * 6. Let processorCtor be the result of looking up processorName on the
+ * AudioWorkletGlobalScope's node name to processor definition map.
+ */
+ RefPtr<AudioWorkletProcessorConstructor> processorCtor =
+ mNameToProcessorMap.Get(aName);
+ // AudioWorkletNode has already checked the definition exists.
+ // See also https://github.com/WebAudio/web-audio-api/issues/1854
+ MOZ_ASSERT(processorCtor);
+ /**
+ * 7. Store nodeReference and deserializedPort to node reference and
+ * transferred port of this AudioWorkletGlobalScope's pending processor
+ * construction data respectively.
+ */
+ // |nodeReference| is not required here because the "processorerror" event
+ // is thrown by WorkletNodeEngine::ConstructProcessor().
+ mPortForProcessor = std::move(deserializedPort);
+ /**
+ * 8. Construct a callback function from processorCtor with the argument
+ * of deserializedOptions.
+ */
+ // The options were an object before serialization and so will be an object
+ // if deserialization succeeded above. toObject() asserts.
+ JS::Rooted<JSObject*> options(aCx, &deserializedOptions.toObject());
+ RefPtr<AudioWorkletProcessor> processor = processorCtor->Construct(
+ options, rv, "AudioWorkletProcessor construction",
+ CallbackFunction::eRethrowExceptions);
+ // https://github.com/WebAudio/web-audio-api/issues/2096
+ mPortForProcessor = nullptr;
+ if (rv.MaybeSetPendingException(aCx)) {
+ return false;
+ }
+ JS::Rooted<JS::Value> processorVal(aCx);
+ if (NS_WARN_IF(!ToJSValue(aCx, processor, &processorVal))) {
+ return false;
+ }
+ MOZ_ASSERT(processorVal.isObject());
+ aRetProcessor.set(&processorVal.toObject());
+ return true;
+}
+
+RefPtr<MessagePort> AudioWorkletGlobalScope::TakePortForProcessorCtor() {
+ return std::move(mPortForProcessor);
+}
+
+} // namespace mozilla::dom