Bug 1558124 - When an exception happens in the AudioWorkletGlobalScope, fire `onprocessorerror`. r=karlt

Differential Revision: https://phabricator.services.mozilla.com/D64766

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Paul Adenot 2020-03-18 10:53:56 +00:00
Родитель 2fef3e1da9
Коммит 3514c96161
2 изменённых файлов: 93 добавлений и 4 удалений

Просмотреть файл

@ -10,10 +10,15 @@
#include "js/Array.h" // JS::{Get,Set}ArrayLength, JS::NewArrayLength
#include "mozilla/dom/AudioWorkletNodeBinding.h"
#include "mozilla/dom/AudioParamMapBinding.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/dom/ErrorEvent.h"
#include "nsIScriptGlobalObject.h"
#include "AudioParam.h"
#include "AudioDestinationNode.h"
#include "mozilla/dom/MessageChannel.h"
#include "mozilla/dom/MessagePort.h"
#include "nsReadableUtils.h"
#include "mozilla/Span.h"
#include "PlayingRefChangeHandler.h"
#include "nsPrintfCString.h"
@ -32,6 +37,14 @@ struct NamedAudioParamTimeline {
AudioParamTimeline mTimeline;
};
struct ProcessorErrorDetails {
ProcessorErrorDetails() : mLineno(0), mColno(0) {}
unsigned mLineno;
unsigned mColno;
nsString mFilename;
nsString mMessage;
};
class WorkletNodeEngine final : public AudioNodeEngine {
public:
WorkletNodeEngine(AudioWorkletNode* aNode,
@ -109,6 +122,8 @@ class WorkletNodeEngine final : public AudioNodeEngine {
bool CallProcess(AudioNodeTrack* aTrack, JSContext* aCx,
JS::Handle<JS::Value> aCallable);
void ProduceSilence(AudioNodeTrack* aTrack, Span<AudioBlock> aOutput);
void SendErrorToMainThread(AudioNodeTrack* aTrack,
const ProcessorErrorDetails& aDetails);
void ReleaseJSResources() {
mInputs.mPorts.clearAndFree();
@ -159,13 +174,70 @@ class WorkletNodeEngine final : public AudioNodeEngine {
bool mKeepEngineActive = true;
};
void WorkletNodeEngine::SendErrorToMainThread(
AudioNodeTrack* aTrack, const ProcessorErrorDetails& aDetails) {
RefPtr<AudioNodeTrack> track = aTrack;
NS_DispatchToMainThread(NS_NewRunnableFunction(
"WorkletNodeEngine::SendProcessorError",
[track = std::move(track), aDetails]() mutable {
AudioWorkletNode* node =
static_cast<AudioWorkletNode*>(track->Engine()->NodeMainThread());
if (!node) {
return;
}
node->DispatchProcessorErrorEvent(aDetails);
}));
}
void WorkletNodeEngine::SendProcessorError(AudioNodeTrack* aTrack,
JSContext* aCx) {
/**
* Note that once an exception is thrown, the processor will output silence
* throughout its lifetime.
*/
// Note that once an exception is thrown, the processor will output silence
// throughout its lifetime.
ReleaseJSResources();
// The processor errored out while getting a context, try to tell the node
// anyways.
if (!aCx) {
ProcessorErrorDetails details;
details.mMessage.Assign(u"Unknown processor error");
SendErrorToMainThread(aTrack, details);
return;
}
js::ErrorReport jsReport(aCx);
JS::Rooted<JS::Value> exn(aCx);
JS::Rooted<JSObject*> exnStack(aCx);
if (JS_GetPendingException(aCx, &exn)) {
exnStack.set(JS::GetPendingExceptionStack(aCx));
JS_ClearPendingException(aCx);
if (!jsReport.init(aCx, exn, js::ErrorReport::WithSideEffects)) {
ProcessorErrorDetails details;
details.mMessage.Assign(u"Unknown processor error");
SendErrorToMainThread(aTrack, details);
// Set the exception and stack back to have it in the console with a stack
// trace.
JS::SetPendingExceptionAndStack(aCx, exn, exnStack);
return;
}
ProcessorErrorDetails details;
CopyUTF8toUTF16(mozilla::MakeStringSpan(jsReport.report()->filename),
details.mFilename);
xpc::ErrorReport::ErrorReportToMessageString(jsReport.report(),
details.mMessage);
details.mLineno = jsReport.report()->lineno;
details.mColno = jsReport.report()->column;
MOZ_ASSERT(!jsReport.report()->isMuted);
SendErrorToMainThread(aTrack, details);
// Set the exception and stack back to have it in the console with a stack
// trace.
JS::SetPendingExceptionAndStack(aCx, exn, exnStack);
} else {
NS_WARNING("No exception, but processor errored out?");
}
}
void WorkletNodeEngine::ConstructProcessor(
@ -754,6 +826,21 @@ AudioParamMap* AudioWorkletNode::GetParameters(ErrorResult& aRv) const {
return mParameters.get();
}
void AudioWorkletNode::DispatchProcessorErrorEvent(
const ProcessorErrorDetails& aDetails) {
if (HasListenersFor(nsGkAtoms::onprocessorerror)) {
RootedDictionary<ErrorEventInit> init(RootingCx());
init.mMessage = aDetails.mMessage;
init.mFilename = aDetails.mFilename;
init.mLineno = aDetails.mLineno;
init.mColno = aDetails.mColno;
RefPtr<ErrorEvent> errorEvent = ErrorEvent::Constructor(
this, NS_LITERAL_STRING("processorerror"), init);
MOZ_ASSERT(errorEvent);
DispatchTrustedEvent(errorEvent);
}
}
JSObject* AudioWorkletNode::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return AudioWorkletNode_Binding::Wrap(aCx, this, aGivenProto);

Просмотреть файл

@ -16,6 +16,7 @@ class AudioParamMap;
struct AudioWorkletNodeOptions;
class MessagePort;
struct NamedAudioParamTimeline;
struct ProcessorErrorDetails;
class AudioWorkletNode : public AudioNode {
public:
@ -40,6 +41,7 @@ class AudioWorkletNode : public AudioNode {
uint16_t NumberOfInputs() const override { return mInputCount; }
uint16_t NumberOfOutputs() const override { return mOutputCount; }
const char* NodeType() const override { return "AudioWorkletNode"; }
void DispatchProcessorErrorEvent(const ProcessorErrorDetails& aDetails);
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;