Bug 1951240 - Implement AudioWorklet.port, r=padenot,webidl,saschanaz

Differential Revision: https://phabricator.services.mozilla.com/D240104
This commit is contained in:
Andrea Marchesini
2025-03-03 14:35:51 +00:00
parent 6f967ea7c1
commit 88dae9c65e
22 changed files with 163 additions and 39 deletions

View File

@@ -60,10 +60,6 @@ DOMInterfaces = {
'implicitJSContext': [ 'buffer' ],
},
'AudioWorklet': {
'nativeType': 'mozilla::dom::Worklet',
},
'AudioWorkletGlobalScope': {
'implicitJSContext': [ 'registerProcessor' ],
},

View File

@@ -19,6 +19,7 @@
#include "mozilla/dom/AnalyserNodeBinding.h"
#include "mozilla/dom/AudioBufferSourceNodeBinding.h"
#include "mozilla/dom/AudioContextBinding.h"
#include "mozilla/dom/AudioWorklet.h"
#include "mozilla/dom/BaseAudioContextBinding.h"
#include "mozilla/dom/BiquadFilterNodeBinding.h"
#include "mozilla/dom/BrowsingContext.h"
@@ -43,7 +44,6 @@
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/StereoPannerNodeBinding.h"
#include "mozilla/dom/WaveShaperNodeBinding.h"
#include "mozilla/dom/Worklet.h"
#include "AudioBuffer.h"
#include "AudioBufferSourceNode.h"
@@ -594,7 +594,7 @@ void AudioContext::GetOutputTimestamp(AudioTimestamp& aTimeStamp) {
}
}
Worklet* AudioContext::GetAudioWorklet(ErrorResult& aRv) {
AudioWorklet* AudioContext::GetAudioWorklet(ErrorResult& aRv) {
if (!mWorklet) {
mWorklet = AudioWorkletImpl::CreateWorklet(this, aRv);
}

View File

@@ -50,6 +50,7 @@ class AudioBufferSourceNode;
class AudioDestinationNode;
class AudioListener;
class AudioNode;
class AudioWorklet;
class BiquadFilterNode;
class BrowsingContext;
class ChannelMergerNode;
@@ -72,7 +73,6 @@ class PannerNode;
class ScriptProcessorNode;
class StereoPannerNode;
class WaveShaperNode;
class Worklet;
class PeriodicWave;
struct PeriodicWaveConstraints;
class Promise;
@@ -202,7 +202,7 @@ class AudioContext final : public DOMEventTargetHelper,
void GetOutputTimestamp(AudioTimestamp& aTimeStamp);
Worklet* GetAudioWorklet(ErrorResult& aRv);
AudioWorklet* GetAudioWorklet(ErrorResult& aRv);
bool IsRunning() const;
@@ -393,7 +393,7 @@ class AudioContext final : public DOMEventTargetHelper,
AudioContextState mAudioContextState;
RefPtr<AudioDestinationNode> mDestination;
RefPtr<AudioListener> mListener;
RefPtr<Worklet> mWorklet;
RefPtr<AudioWorklet> mWorklet;
nsTArray<UniquePtr<WebAudioDecodeJob>> mDecodeJobs;
// This array is used to keep the suspend/close promises alive until
// they are resolved, so we can safely pass them accross threads.

View File

@@ -0,0 +1,36 @@
/* -*- 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 "AudioWorklet.h"
#include "mozilla/dom/MessagePort.h"
#include "mozilla/dom/WorkletImpl.h"
namespace mozilla::dom {
NS_IMPL_ADDREF_INHERITED(AudioWorklet, Worklet)
NS_IMPL_RELEASE_INHERITED(AudioWorklet, Worklet)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioWorklet)
NS_INTERFACE_MAP_END_INHERITING(Worklet)
NS_IMPL_CYCLE_COLLECTION_CLASS(AudioWorklet)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioWorklet, Worklet)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPort)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AudioWorklet, Worklet)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPort)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
AudioWorklet::AudioWorklet(nsPIDOMWindowInner* aWindow,
RefPtr<WorkletImpl> aImpl, nsISupports* aOwnedObject,
MessagePort* aPort)
: Worklet(aWindow, aImpl, aOwnedObject), mPort(aPort) {
MOZ_ASSERT(aPort);
}
} // namespace mozilla::dom

View File

@@ -0,0 +1,34 @@
/* -*- 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/. */
#ifndef mozilla_dom_AudioWorklet_h
#define mozilla_dom_AudioWorklet_h
#include "mozilla/dom/Worklet.h"
namespace mozilla::dom {
class MessagePort;
class AudioWorklet final : public Worklet {
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioWorklet, Worklet)
AudioWorklet(nsPIDOMWindowInner* aWindow, RefPtr<WorkletImpl> aImpl,
nsISupports* aOwnedObject, MessagePort* aPort);
MessagePort* Port() const { return mPort; };
private:
~AudioWorklet() = default;
RefPtr<MessagePort> mPort;
};
} // namespace mozilla::dom
#endif // mozilla_dom_AudioWorklet_h

View File

@@ -26,7 +26,7 @@
namespace mozilla::dom {
NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioWorkletGlobalScope, WorkletGlobalScope,
mNameToProcessorMap);
mNameToProcessorMap, mPort);
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioWorkletGlobalScope)
NS_INTERFACE_MAP_END_INHERITING(WorkletGlobalScope)

View File

@@ -47,6 +47,10 @@ class AudioWorkletGlobalScope final : public WorkletGlobalScope {
float SampleRate() const;
MessagePort* Port() const { return mPort; };
void SetPort(MessagePort* aPort) { mPort = aPort; }
// If successful, returns true and sets aRetProcessor, which will be in the
// compartment for the realm of this global. Returns false on failure.
MOZ_CAN_RUN_SCRIPT
@@ -76,6 +80,8 @@ class AudioWorkletGlobalScope final : public WorkletGlobalScope {
// This does not need to be traversed during cycle-collection because it is
// only set while this AudioWorkletGlobalScope is on the stack.
RefPtr<MessagePort> mPortForProcessor;
RefPtr<MessagePort> mPort;
};
} // namespace dom

View File

@@ -8,17 +8,19 @@
#include "AudioContext.h"
#include "AudioNodeTrack.h"
#include "AudioWorklet.h"
#include "GeckoProfiler.h"
#include "mozilla/dom/AudioWorkletBinding.h"
#include "mozilla/dom/AudioWorkletGlobalScope.h"
#include "mozilla/dom/Worklet.h"
#include "mozilla/dom/MessageChannel.h"
#include "mozilla/dom/MessagePort.h"
#include "mozilla/dom/WorkletThread.h"
#include "nsGlobalWindowInner.h"
namespace mozilla {
/* static */ already_AddRefed<dom::Worklet> AudioWorkletImpl::CreateWorklet(
dom::AudioContext* aContext, ErrorResult& aRv) {
/* static */ already_AddRefed<dom::AudioWorklet>
AudioWorkletImpl::CreateWorklet(dom::AudioContext* aContext, ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread());
nsGlobalWindowInner* window = aContext->GetOwnerWindow();
@@ -32,27 +34,41 @@ namespace mozilla {
return nullptr;
}
RefPtr<dom::MessageChannel> messageChannel =
dom::MessageChannel::Constructor(window, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
dom::UniqueMessagePortId globalScopePortId;
messageChannel->Port2()->CloneAndDisentangle(globalScopePortId);
RefPtr<AudioWorkletImpl> impl =
new AudioWorkletImpl(window, principal, aContext->DestinationTrack());
new AudioWorkletImpl(window, principal, aContext->DestinationTrack(),
std::move(globalScopePortId));
// The Worklet owns a reference to the AudioContext so as to keep the graph
// thread running as long as the Worklet is alive by keeping the
// AudioDestinationNode alive.
return MakeAndAddRef<dom::Worklet>(window, std::move(impl),
ToSupports(aContext));
return MakeAndAddRef<dom::AudioWorklet>(
window, std::move(impl), ToSupports(aContext), messageChannel->Port1());
}
AudioWorkletImpl::AudioWorkletImpl(nsPIDOMWindowInner* aWindow,
nsIPrincipal* aPrincipal,
AudioNodeTrack* aDestinationTrack)
: WorkletImpl(aWindow, aPrincipal), mDestinationTrack(aDestinationTrack) {}
AudioNodeTrack* aDestinationTrack,
dom::UniqueMessagePortId&& aPortIdentifier)
: WorkletImpl(aWindow, aPrincipal),
mDestinationTrack(aDestinationTrack),
mGlobalScopePortIdentifier(std::move(aPortIdentifier)) {}
AudioWorkletImpl::~AudioWorkletImpl() = default;
JSObject* AudioWorkletImpl::WrapWorklet(JSContext* aCx, dom::Worklet* aWorklet,
JS::Handle<JSObject*> aGivenProto) {
MOZ_ASSERT(NS_IsMainThread());
return dom::AudioWorklet_Binding::Wrap(aCx, aWorklet, aGivenProto);
return dom::AudioWorklet_Binding::Wrap(
aCx, static_cast<dom::AudioWorklet*>(aWorklet), aGivenProto);
}
nsresult AudioWorkletImpl::SendControlMessage(
@@ -78,10 +94,23 @@ void AudioWorkletImpl::OnAddModulePromiseSettled() const {
}
already_AddRefed<dom::WorkletGlobalScope>
AudioWorkletImpl::ConstructGlobalScope() {
AudioWorkletImpl::ConstructGlobalScope(JSContext* aCx) {
dom::WorkletThread::AssertIsOnWorkletThread();
return MakeAndAddRef<dom::AudioWorkletGlobalScope>(this);
RefPtr<dom::AudioWorkletGlobalScope> globalScope =
new dom::AudioWorkletGlobalScope(this);
ErrorResult rv;
RefPtr<dom::MessagePort> deserializedPort =
dom::MessagePort::Create(globalScope, mGlobalScopePortIdentifier, rv);
if (NS_WARN_IF(rv.MaybeSetPendingException(aCx))) {
// The exception will be propagated into the global
return globalScope.forget();
}
globalScope->SetPort(deserializedPort);
return globalScope.forget();
}
} // namespace mozilla

View File

@@ -9,6 +9,7 @@
#include "mozilla/dom/WorkletImpl.h"
#include "mozilla/dom/AudioWorkletGlobalScope.h"
#include "mozilla/dom/MessagePort.h"
namespace mozilla {
@@ -16,13 +17,14 @@ class AudioNodeTrack;
namespace dom {
class AudioContext;
}
class AudioWorklet;
} // namespace dom
class AudioWorkletImpl final : public WorkletImpl {
public:
// Methods for parent thread only:
static already_AddRefed<dom::Worklet> CreateWorklet(
static already_AddRefed<dom::AudioWorklet> CreateWorklet(
dom::AudioContext* aContext, ErrorResult& aRv);
JSObject* WrapWorklet(JSContext* aCx, dom::Worklet* aWorklet,
@@ -48,14 +50,17 @@ class AudioWorkletImpl final : public WorkletImpl {
protected:
// Execution thread only.
already_AddRefed<dom::WorkletGlobalScope> ConstructGlobalScope() override;
already_AddRefed<dom::WorkletGlobalScope> ConstructGlobalScope(
JSContext* aCx) override;
private:
AudioWorkletImpl(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
AudioNodeTrack* aDestinationTrack);
AudioNodeTrack* aDestinationTrack,
dom::UniqueMessagePortId&& aPortIdentifier);
~AudioWorkletImpl();
const RefPtr<AudioNodeTrack> mDestinationTrack;
dom::UniqueMessagePortId mGlobalScopePortIdentifier;
};
} // namespace mozilla

View File

@@ -8,6 +8,7 @@
#include "AudioNodeEngine.h"
#include "AudioParamMap.h"
#include "AudioWorklet.h"
#include "AudioWorkletImpl.h"
#include "js/Array.h" // JS::{Get,Set}ArrayLength, JS::NewArrayLength
#include "js/CallAndConstruct.h" // JS::Call, JS::IsCallable

View File

@@ -54,6 +54,7 @@ EXPORTS.mozilla.dom += [
"AudioParamMap.h",
"AudioProcessingEvent.h",
"AudioScheduledSourceNode.h",
"AudioWorklet.h",
"AudioWorkletGlobalScope.h",
"AudioWorkletNode.h",
"AudioWorkletProcessor.h",
@@ -95,6 +96,7 @@ UNIFIED_SOURCES += [
"AudioParamMap.cpp",
"AudioProcessingEvent.cpp",
"AudioScheduledSourceNode.cpp",
"AudioWorklet.cpp",
"AudioWorkletGlobalScope.cpp",
"AudioWorkletImpl.cpp",
"AudioWorkletNode.cpp",

View File

@@ -821,7 +821,8 @@ void MessagePort::ForceClose(const MessagePortIdentifier& aIdentifier) {
mozilla::ipc::PBackgroundChild* actorChild =
mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
if (NS_WARN_IF(!actorChild)) {
MOZ_CRASH("Failed to create a PBackgroundChild actor!");
// We are shutting down this process. This port will be leaked.
return;
}
Unused << actorChild->SendMessagePortForceClose(aIdentifier.uuid(),

View File

@@ -12,4 +12,5 @@
[Exposed=Window, SecureContext]
interface AudioWorklet : Worklet {
readonly attribute MessagePort port;
};

View File

@@ -16,4 +16,5 @@ interface AudioWorkletGlobalScope : WorkletGlobalScope {
readonly attribute unsigned long long currentFrame;
readonly attribute double currentTime;
readonly attribute float sampleRate;
readonly attribute MessagePort port;
};

View File

@@ -27,7 +27,7 @@ class WorkletScriptHandler;
struct WorkletOptions;
enum class CallerType : uint32_t;
class Worklet final : public nsISupports, public nsWrapperCache {
class Worklet : public nsISupports, public nsWrapperCache {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Worklet)
@@ -54,9 +54,10 @@ class Worklet final : public nsISupports, public nsWrapperCache {
return mLocalizedStrings;
}
private:
~Worklet();
protected:
virtual ~Worklet();
private:
WorkletFetchHandler* GetImportFetchHandler(const nsACString& aURI);
void AddImportFetchHandler(const nsACString& aURI,

View File

@@ -91,7 +91,7 @@ dom::WorkletGlobalScope* WorkletImpl::GetGlobalScope() {
jsapi.Init();
JSContext* cx = jsapi.cx();
mGlobalScope = ConstructGlobalScope();
mGlobalScope = ConstructGlobalScope(cx);
JS::Rooted<JSObject*> global(cx);
NS_ENSURE_TRUE(mGlobalScope->WrapGlobalObject(cx, &global), nullptr);

View File

@@ -103,7 +103,8 @@ class WorkletImpl {
WorkletImpl(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal);
virtual ~WorkletImpl();
virtual already_AddRefed<dom::WorkletGlobalScope> ConstructGlobalScope() = 0;
virtual already_AddRefed<dom::WorkletGlobalScope> ConstructGlobalScope(
JSContext* aCx) = 0;
// Modified only in constructor.
ipc::PrincipalInfo mPrincipalInfo;

View File

@@ -31,7 +31,7 @@ PaintWorkletImpl::PaintWorkletImpl(nsPIDOMWindowInner* aWindow,
PaintWorkletImpl::~PaintWorkletImpl() = default;
already_AddRefed<dom::WorkletGlobalScope>
PaintWorkletImpl::ConstructGlobalScope() {
PaintWorkletImpl::ConstructGlobalScope(JSContext* aCx) {
dom::WorkletThread::AssertIsOnWorkletThread();
return MakeAndAddRef<dom::PaintWorkletGlobalScope>(this);

View File

@@ -24,7 +24,8 @@ class PaintWorkletImpl final : public WorkletImpl {
protected:
// Execution thread only.
already_AddRefed<dom::WorkletGlobalScope> ConstructGlobalScope() override;
already_AddRefed<dom::WorkletGlobalScope> ConstructGlobalScope(
JSContext* aCx) override;
private:
PaintWorkletImpl(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal);

View File

@@ -167,12 +167,6 @@
[AudioRenderCapacityEvent interface: attribute underrunRatio]
expected: FAIL
[AudioWorklet interface: attribute port]
expected: FAIL
[AudioWorklet interface: context.audioWorklet must inherit property "port" with the proper type]
expected: FAIL
[AudioContext interface: attribute sinkId]
expected: FAIL

View File

@@ -16,6 +16,19 @@
let filePath = 'processors/port-processor.js';
// AudioWorket to global communication.
audit.define(
'Test postMessage from AudioWorklet to AudioWorkletGlobalScope',
(task, should) => {
context.audioWorklet.port.onmessage = (event) => {
should(event.data,
'The response from AudioWorkletGlobalscope')
.beEqualTo('hello world');
task.done();
};
context.audioWorklet.port.postMessage("hello world");
});
// Creates an AudioWorkletNode and sets an EventHandler on MessagePort
// object. The associated PortProcessor will post a message upon its
// construction. Test if the message is received correctly.

View File

@@ -32,3 +32,5 @@ class PortProcessor extends AudioWorkletProcessor {
}
registerProcessor('port-processor', PortProcessor);
port.onmessage = (event) => port.postMessage(event.data);