tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AudioWorkletNode.cpp (34469B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      6 
      7 #include "AudioWorkletNode.h"
      8 
      9 #include "AudioDestinationNode.h"
     10 #include "AudioNodeEngine.h"
     11 #include "AudioParam.h"
     12 #include "AudioParamMap.h"
     13 #include "AudioWorklet.h"
     14 #include "AudioWorkletImpl.h"
     15 #include "PlayingRefChangeHandler.h"
     16 #include "Tracing.h"
     17 #include "js/Array.h"  // JS::{Get,Set}ArrayLength, JS::NewArrayLength
     18 #include "js/CallAndConstruct.h"  // JS::Call, JS::IsCallable
     19 #include "js/Exception.h"
     20 #include "js/PropertyAndElement.h"  // JS_DefineElement, JS_DefineUCProperty, JS_GetProperty
     21 #include "js/experimental/TypedData.h"  // JS_NewFloat32Array, JS_GetFloat32ArrayData, JS_GetTypedArrayLength, JS_GetArrayBufferViewBuffer
     22 #include "mozilla/ScopeExit.h"
     23 #include "mozilla/Span.h"
     24 #include "mozilla/dom/AudioParamMapBinding.h"
     25 #include "mozilla/dom/AudioWorkletNodeBinding.h"
     26 #include "mozilla/dom/AutoEntryScript.h"
     27 #include "mozilla/dom/ErrorEvent.h"
     28 #include "mozilla/dom/MessageChannel.h"
     29 #include "mozilla/dom/MessagePort.h"
     30 #include "mozilla/dom/RootedDictionary.h"
     31 #include "mozilla/dom/Worklet.h"
     32 #include "nsIScriptGlobalObject.h"
     33 #include "nsPrintfCString.h"
     34 #include "nsReadableUtils.h"
     35 
     36 namespace mozilla::dom {
     37 
     38 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(AudioWorkletNode, AudioNode)
     39 NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioWorkletNode, AudioNode, mPort,
     40                                   mParameters)
     41 
     42 struct NamedAudioParamTimeline {
     43  explicit NamedAudioParamTimeline(const AudioParamDescriptor& aParamDescriptor)
     44      : mName(aParamDescriptor.mName),
     45        mTimeline(aParamDescriptor.mDefaultValue) {}
     46  const nsString mName;
     47  AudioParamTimeline mTimeline;
     48 };
     49 
     50 struct ProcessorErrorDetails {
     51  ProcessorErrorDetails() : mLineno(0), mColno(0) {}
     52  // Line number (1-origin).
     53  unsigned mLineno;
     54  // Column number in UTF-16 code units (1-origin).
     55  unsigned mColno;
     56  nsCString mFilename;
     57  nsString mMessage;
     58 };
     59 
     60 class WorkletNodeEngine final : public AudioNodeEngine {
     61 public:
     62  WorkletNodeEngine(AudioWorkletNode* aNode,
     63                    AudioDestinationNode* aDestinationNode,
     64                    nsTArray<NamedAudioParamTimeline>&& aParamTimelines,
     65                    const Optional<Sequence<uint32_t>>& aOutputChannelCount)
     66      : AudioNodeEngine(aNode),
     67        mDestination(aDestinationNode->Track()),
     68        mParamTimelines(std::move(aParamTimelines)) {
     69    if (aOutputChannelCount.WasPassed()) {
     70      mOutputChannelCount = aOutputChannelCount.Value();
     71    }
     72  }
     73 
     74  MOZ_CAN_RUN_SCRIPT
     75  void ConstructProcessor(AudioWorkletImpl* aWorkletImpl,
     76                          const nsAString& aName,
     77                          NotNull<StructuredCloneHolder*> aSerializedOptions,
     78                          UniqueMessagePortId& aPortIdentifier,
     79                          AudioNodeTrack* aTrack);
     80 
     81  void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override {
     82    MOZ_ASSERT(mDestination);
     83    aEvent.ConvertToTicks(mDestination);
     84 
     85    if (aIndex < mParamTimelines.Length()) {
     86      mParamTimelines[aIndex].mTimeline.InsertEvent<int64_t>(aEvent);
     87    } else {
     88      NS_ERROR("Bad WorkletNodeEngine timeline event index");
     89    }
     90  }
     91 
     92  void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
     93                    const AudioBlock& aInput, AudioBlock* aOutput,
     94                    bool* aFinished) override {
     95    MOZ_ASSERT(InputCount() <= 1);
     96    MOZ_ASSERT(OutputCount() <= 1);
     97    TRACE("WorkletNodeEngine::ProcessBlock");
     98    ProcessBlocksOnPorts(aTrack, aFrom, Span(&aInput, InputCount()),
     99                         Span(aOutput, OutputCount()), aFinished);
    100  }
    101 
    102  void ProcessBlocksOnPorts(AudioNodeTrack* aTrack, GraphTime aFrom,
    103                            Span<const AudioBlock> aInput,
    104                            Span<AudioBlock> aOutput, bool* aFinished) override;
    105 
    106  void OnGraphThreadDone() override { ReleaseJSResources(); }
    107 
    108  bool IsActive() const override { return mKeepEngineActive; }
    109 
    110  // Vector<T> supports non-memmovable types such as PersistentRooted
    111  // (without any need to jump through hoops like
    112  // MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE for nsTArray).
    113  // PersistentRooted is used because these AudioWorkletGlobalScope scope
    114  // objects may be kept alive as long as the AudioWorkletNode in the
    115  // main-thread global.
    116  struct Channels {
    117    Vector<JS::PersistentRooted<JSObject*>, GUESS_AUDIO_CHANNELS>
    118        mFloat32Arrays;
    119    JS::PersistentRooted<JSObject*> mJSArray;
    120    // For SetArrayElements():
    121    operator JS::Handle<JSObject*>() const { return mJSArray; }
    122  };
    123  struct Ports {
    124    Vector<Channels, 1> mPorts;
    125    JS::PersistentRooted<JSObject*> mJSArray;
    126  };
    127  struct ParameterValues {
    128    Vector<JS::PersistentRooted<JSObject*>> mFloat32Arrays;
    129    JS::PersistentRooted<JSObject*> mJSObject;
    130  };
    131 
    132 private:
    133  size_t ParameterCount() { return mParamTimelines.Length(); }
    134  void SendProcessorError(AudioNodeTrack* aTrack, JSContext* aCx);
    135  bool CallProcess(AudioNodeTrack* aTrack, JSContext* aCx,
    136                   JS::Handle<JS::Value> aCallable);
    137  void ProduceSilence(AudioNodeTrack* aTrack, Span<AudioBlock> aOutput);
    138  void SendErrorToMainThread(AudioNodeTrack* aTrack,
    139                             const ProcessorErrorDetails& aDetails);
    140 
    141  void ReleaseJSResources() {
    142    mInputs.mPorts.clearAndFree();
    143    mOutputs.mPorts.clearAndFree();
    144    mParameters.mFloat32Arrays.clearAndFree();
    145    mInputs.mJSArray.reset();
    146    mOutputs.mJSArray.reset();
    147    mParameters.mJSObject.reset();
    148    mGlobal = nullptr;
    149    // This is equivalent to setting [[callable process]] to false.
    150    mProcessor.reset();
    151  }
    152 
    153  nsCString mProcessorName;
    154  RefPtr<AudioNodeTrack> mDestination;
    155  nsTArray<uint32_t> mOutputChannelCount;
    156  nsTArray<NamedAudioParamTimeline> mParamTimelines;
    157  // The AudioWorkletGlobalScope-associated objects referenced from
    158  // WorkletNodeEngine are typically kept alive as long as the
    159  // AudioWorkletNode in the main-thread global.  The objects must be released
    160  // on the rendering thread, which usually happens simply because
    161  // AudioWorkletNode is such that the last AudioNodeTrack reference is
    162  // released by the MTG.  That occurs on the rendering thread except during
    163  // process shutdown, in which case NotifyForcedShutdown() is called on the
    164  // rendering thread.
    165  //
    166  // mInputs, mOutputs and mParameters keep references to all objects passed to
    167  // process(), for reuse of the same objects.  The JS objects are all in the
    168  // compartment of the realm of mGlobal.  Properties on the objects may be
    169  // replaced by script, so don't assume that getting indexed properties on the
    170  // JS arrays will return the same objects.  Only objects and buffers created
    171  // by the implementation are modified or read by the implementation.
    172  Ports mInputs;
    173  Ports mOutputs;
    174  ParameterValues mParameters;
    175 
    176  RefPtr<AudioWorkletGlobalScope> mGlobal;
    177  JS::PersistentRooted<JSObject*> mProcessor;
    178 
    179  // mProcessorIsActive is named [[active source]] in the spec.
    180  // It is initially true and so at least the first process()
    181  // call will not be skipped when there are no active inputs.
    182  bool mProcessorIsActive = true;
    183  // mKeepEngineActive ensures another call to ProcessBlocksOnPorts(), even if
    184  // there are no active inputs.  Its transitions to false lag those of
    185  // mProcessorIsActive by one call to ProcessBlocksOnPorts() so that
    186  // downstream engines can addref their nodes before this engine's node is
    187  // released.
    188  bool mKeepEngineActive = true;
    189 };
    190 
    191 void WorkletNodeEngine::SendErrorToMainThread(
    192    AudioNodeTrack* aTrack, const ProcessorErrorDetails& aDetails) {
    193  RefPtr<AudioNodeTrack> track = aTrack;
    194  NS_DispatchToMainThread(NS_NewRunnableFunction(
    195      "WorkletNodeEngine::SendProcessorError",
    196      [track = std::move(track), aDetails]() mutable {
    197        AudioWorkletNode* node =
    198            static_cast<AudioWorkletNode*>(track->Engine()->NodeMainThread());
    199        if (!node) {
    200          return;
    201        }
    202        node->DispatchProcessorErrorEvent(aDetails);
    203      }));
    204 }
    205 
    206 void WorkletNodeEngine::SendProcessorError(AudioNodeTrack* aTrack,
    207                                           JSContext* aCx) {
    208  // Note that once an exception is thrown, the processor will output silence
    209  // throughout its lifetime.
    210  ReleaseJSResources();
    211  // The processor errored out while getting a context, try to tell the node
    212  // anyways.
    213  if (!aCx || !JS_IsExceptionPending(aCx)) {
    214    ProcessorErrorDetails details;
    215    details.mMessage.Assign(u"Unknown processor error");
    216    SendErrorToMainThread(aTrack, details);
    217    return;
    218  }
    219 
    220  JS::ExceptionStack exnStack(aCx);
    221  if (JS::StealPendingExceptionStack(aCx, &exnStack)) {
    222    JS::ErrorReportBuilder jsReport(aCx);
    223    if (!jsReport.init(aCx, exnStack,
    224                       JS::ErrorReportBuilder::WithSideEffects)) {
    225      ProcessorErrorDetails details;
    226      details.mMessage.Assign(u"Unknown processor error");
    227      SendErrorToMainThread(aTrack, details);
    228      // Set the exception and stack back to have it in the console with a stack
    229      // trace.
    230      JS::SetPendingExceptionStack(aCx, exnStack);
    231      return;
    232    }
    233 
    234    ProcessorErrorDetails details;
    235    details.mFilename.Assign(jsReport.report()->filename.c_str());
    236    xpc::ErrorReport::ErrorReportToMessageString(jsReport.report(),
    237                                                 details.mMessage);
    238    details.mLineno = jsReport.report()->lineno;
    239    details.mColno = jsReport.report()->column.oneOriginValue();
    240    MOZ_ASSERT(!jsReport.report()->isMuted);
    241 
    242    SendErrorToMainThread(aTrack, details);
    243 
    244    // Set the exception and stack back to have it in the console with a stack
    245    // trace.
    246    JS::SetPendingExceptionStack(aCx, exnStack);
    247  } else {
    248    NS_WARNING("No exception, but processor errored out?");
    249  }
    250 }
    251 
    252 void WorkletNodeEngine::ConstructProcessor(
    253    AudioWorkletImpl* aWorkletImpl, const nsAString& aName,
    254    NotNull<StructuredCloneHolder*> aSerializedOptions,
    255    UniqueMessagePortId& aPortIdentifier, AudioNodeTrack* aTrack) {
    256  MOZ_ASSERT(mInputs.mPorts.empty() && mOutputs.mPorts.empty());
    257  RefPtr<AudioWorkletGlobalScope> global = aWorkletImpl->GetGlobalScope();
    258  if (!global) {
    259    // A global was previously used to register this kind of processor.  If it
    260    // no longer exists now, that is because the document is going away and so
    261    // there is no need to send an error.
    262    return;
    263  }
    264  AutoJSAPI api;
    265  if (NS_WARN_IF(!api.Init(global))) {
    266    SendProcessorError(aTrack, nullptr);
    267    return;
    268  }
    269  mProcessorName = NS_ConvertUTF16toUTF8(aName);
    270  JSContext* cx = api.cx();
    271  mProcessor.init(cx);
    272  if (!global->ConstructProcessor(cx, aName, aSerializedOptions,
    273                                  aPortIdentifier, &mProcessor) ||
    274      // mInputs and mOutputs outer arrays are fixed length and so much of the
    275      // initialization need only be performed once (i.e. here).
    276      NS_WARN_IF(!mInputs.mPorts.growBy(InputCount())) ||
    277      NS_WARN_IF(!mOutputs.mPorts.growBy(OutputCount()))) {
    278    SendProcessorError(aTrack, cx);
    279    return;
    280  }
    281  mGlobal = std::move(global);
    282  mInputs.mJSArray.init(cx);
    283  mOutputs.mJSArray.init(cx);
    284  for (auto& port : mInputs.mPorts) {
    285    port.mJSArray.init(cx);
    286  }
    287  for (auto& port : mOutputs.mPorts) {
    288    port.mJSArray.init(cx);
    289  }
    290  JSObject* object = JS_NewPlainObject(cx);
    291  if (NS_WARN_IF(!object)) {
    292    SendProcessorError(aTrack, cx);
    293    return;
    294  }
    295 
    296  mParameters.mJSObject.init(cx, object);
    297  if (NS_WARN_IF(!mParameters.mFloat32Arrays.growBy(ParameterCount()))) {
    298    SendProcessorError(aTrack, cx);
    299    return;
    300  }
    301  for (size_t i = 0; i < mParamTimelines.Length(); i++) {
    302    auto& float32ArraysRef = mParameters.mFloat32Arrays;
    303    float32ArraysRef[i].init(cx);
    304    JSObject* array = JS_NewFloat32Array(cx, WEBAUDIO_BLOCK_SIZE);
    305    if (NS_WARN_IF(!array)) {
    306      SendProcessorError(aTrack, cx);
    307      return;
    308    }
    309 
    310    float32ArraysRef[i] = array;
    311    if (NS_WARN_IF(!JS_DefineUCProperty(
    312            cx, mParameters.mJSObject, mParamTimelines[i].mName.get(),
    313            mParamTimelines[i].mName.Length(), float32ArraysRef[i],
    314            JSPROP_ENUMERATE))) {
    315      SendProcessorError(aTrack, cx);
    316      return;
    317    }
    318  }
    319  if (NS_WARN_IF(!JS_FreezeObject(cx, mParameters.mJSObject))) {
    320    SendProcessorError(aTrack, cx);
    321    return;
    322  }
    323 }
    324 
    325 // Type T should support the length() and operator[]() methods and the return
    326 // type of |operator[]() const| should support conversion to Handle<JSObject*>.
    327 template <typename T>
    328 static bool SetArrayElements(JSContext* aCx, const T& aElements,
    329                             JS::Handle<JSObject*> aArray) {
    330  for (size_t i = 0; i < aElements.length(); ++i) {
    331    if (!JS_DefineElement(aCx, aArray, i, aElements[i], JSPROP_ENUMERATE)) {
    332      return false;
    333    }
    334  }
    335 
    336  return true;
    337 }
    338 
    339 template <typename T>
    340 static bool PrepareArray(JSContext* aCx, const T& aElements,
    341                         JS::MutableHandle<JSObject*> aArray) {
    342  size_t length = aElements.length();
    343  if (aArray) {
    344    // Attempt to reuse.
    345    uint32_t oldLength;
    346    if (JS::GetArrayLength(aCx, aArray, &oldLength) &&
    347        (oldLength == length || JS::SetArrayLength(aCx, aArray, length)) &&
    348        SetArrayElements(aCx, aElements, aArray)) {
    349      return true;
    350    }
    351    // Script may have frozen the array.  Try again with a new Array.
    352    JS_ClearPendingException(aCx);
    353  }
    354  JSObject* array = JS::NewArrayObject(aCx, length);
    355  if (NS_WARN_IF(!array)) {
    356    return false;
    357  }
    358  aArray.set(array);
    359  return SetArrayElements(aCx, aElements, aArray);
    360 }
    361 
    362 enum class ArrayElementInit { None, Zero };
    363 
    364 // Exactly when to create new Float32Array and Array objects is not specified.
    365 // This approach aims to minimize garbage creation, while continuing to
    366 // function after objects are modified by content.
    367 // See https://github.com/WebAudio/web-audio-api/issues/1934 and
    368 // https://github.com/WebAudio/web-audio-api/issues/1933
    369 static bool PrepareBufferArrays(JSContext* aCx, Span<const AudioBlock> aBlocks,
    370                                WorkletNodeEngine::Ports* aPorts,
    371                                ArrayElementInit aInit) {
    372  MOZ_ASSERT(aBlocks.Length() == aPorts->mPorts.length());
    373  for (size_t i = 0; i < aBlocks.Length(); ++i) {
    374    size_t channelCount = aBlocks[i].ChannelCount();
    375    WorkletNodeEngine::Channels& portRef = aPorts->mPorts[i];
    376 
    377    auto& float32ArraysRef = portRef.mFloat32Arrays;
    378    for (auto& channelRef : float32ArraysRef) {
    379      size_t length = JS_GetTypedArrayLength(channelRef);
    380      if (length != WEBAUDIO_BLOCK_SIZE) {
    381        // Script has detached array buffers.  Create new objects.
    382        JSObject* array = JS_NewFloat32Array(aCx, WEBAUDIO_BLOCK_SIZE);
    383        if (NS_WARN_IF(!array)) {
    384          return false;
    385        }
    386        channelRef = array;
    387      } else if (aInit == ArrayElementInit::Zero) {
    388        // Need only zero existing arrays as new arrays are already zeroed.
    389        JS::AutoCheckCannotGC nogc;
    390        bool isShared;
    391        float* elementData =
    392            JS_GetFloat32ArrayData(channelRef, &isShared, nogc);
    393        MOZ_ASSERT(!isShared);  // Was created as unshared
    394        std::fill_n(elementData, WEBAUDIO_BLOCK_SIZE, 0.0f);
    395      }
    396    }
    397    // Enlarge if necessary...
    398    if (NS_WARN_IF(!float32ArraysRef.reserve(channelCount))) {
    399      return false;
    400    }
    401    while (float32ArraysRef.length() < channelCount) {
    402      JSObject* array = JS_NewFloat32Array(aCx, WEBAUDIO_BLOCK_SIZE);
    403      if (NS_WARN_IF(!array)) {
    404        return false;
    405      }
    406      float32ArraysRef.infallibleEmplaceBack(aCx, array);
    407    }
    408    // ... or shrink if necessary.
    409    float32ArraysRef.shrinkTo(channelCount);
    410 
    411    if (NS_WARN_IF(!PrepareArray(aCx, float32ArraysRef, &portRef.mJSArray))) {
    412      return false;
    413    }
    414  }
    415 
    416  return !(NS_WARN_IF(!PrepareArray(aCx, aPorts->mPorts, &aPorts->mJSArray)));
    417 }
    418 
    419 // This runs JS script.  MediaTrackGraph control messages, which would
    420 // potentially destroy the WorkletNodeEngine and its AudioNodeTrack, cannot
    421 // be triggered by script.  They are not run from an nsIThread event loop and
    422 // do not run until after ProcessBlocksOnPorts() has returned.
    423 bool WorkletNodeEngine::CallProcess(AudioNodeTrack* aTrack, JSContext* aCx,
    424                                    JS::Handle<JS::Value> aCallable) {
    425  TRACE_COMMENT("AudioWorkletNodeEngine::CallProcess", mProcessorName.get());
    426 
    427  JS::RootedVector<JS::Value> argv(aCx);
    428  if (NS_WARN_IF(!argv.resize(3))) {
    429    return false;
    430  }
    431  argv[0].setObject(*mInputs.mJSArray);
    432  argv[1].setObject(*mOutputs.mJSArray);
    433  argv[2].setObject(*mParameters.mJSObject);
    434  JS::Rooted<JS::Value> rval(aCx);
    435  if (!JS::Call(aCx, mProcessor, aCallable, argv, &rval)) {
    436    return false;
    437  }
    438 
    439  mProcessorIsActive = JS::ToBoolean(rval);
    440  // Transitions of mProcessorIsActive to false do not trigger
    441  // PlayingRefChangeHandler::RELEASE until silence is produced in the next
    442  // block.  This allows downstream engines receiving this non-silence block
    443  // to take a reference to their nodes before this engine's node releases its
    444  // down node references.
    445  if (mProcessorIsActive && !mKeepEngineActive) {
    446    mKeepEngineActive = true;
    447    RefPtr<PlayingRefChangeHandler> refchanged =
    448        new PlayingRefChangeHandler(aTrack, PlayingRefChangeHandler::ADDREF);
    449    aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
    450  }
    451  return true;
    452 }
    453 
    454 void WorkletNodeEngine::ProduceSilence(AudioNodeTrack* aTrack,
    455                                       Span<AudioBlock> aOutput) {
    456  if (mKeepEngineActive) {
    457    mKeepEngineActive = false;
    458    aTrack->ScheduleCheckForInactive();
    459    RefPtr<PlayingRefChangeHandler> refchanged =
    460        new PlayingRefChangeHandler(aTrack, PlayingRefChangeHandler::RELEASE);
    461    aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
    462  }
    463  for (AudioBlock& output : aOutput) {
    464    output.SetNull(WEBAUDIO_BLOCK_SIZE);
    465  }
    466 }
    467 
    468 void WorkletNodeEngine::ProcessBlocksOnPorts(AudioNodeTrack* aTrack,
    469                                             GraphTime aFrom,
    470                                             Span<const AudioBlock> aInput,
    471                                             Span<AudioBlock> aOutput,
    472                                             bool* aFinished) {
    473  MOZ_ASSERT(aInput.Length() == InputCount());
    474  MOZ_ASSERT(aOutput.Length() == OutputCount());
    475  TRACE("WorkletNodeEngine::ProcessBlocksOnPorts");
    476 
    477  bool isSilent = true;
    478  if (mProcessor) {
    479    if (mProcessorIsActive) {
    480      isSilent = false;  // call process()
    481    } else {             // [[active source]] is false.
    482      // Call process() only if an input is actively processing.
    483      for (const AudioBlock& input : aInput) {
    484        if (!input.IsNull()) {
    485          isSilent = false;
    486          break;
    487        }
    488      }
    489    }
    490  }
    491  if (isSilent) {
    492    ProduceSilence(aTrack, aOutput);
    493    return;
    494  }
    495 
    496  if (!mOutputChannelCount.IsEmpty()) {
    497    MOZ_ASSERT(mOutputChannelCount.Length() == aOutput.Length());
    498    for (size_t o = 0; o < aOutput.Length(); ++o) {
    499      aOutput[o].AllocateChannels(mOutputChannelCount[o]);
    500    }
    501  } else if (aInput.Length() == 1 && aOutput.Length() == 1) {
    502    uint32_t channelCount = std::max(aInput[0].ChannelCount(), 1U);
    503    aOutput[0].AllocateChannels(channelCount);
    504  } else {
    505    for (AudioBlock& output : aOutput) {
    506      output.AllocateChannels(1);
    507    }
    508  }
    509 
    510  AutoEntryScript aes(mGlobal, "Worklet Process");
    511  JSContext* cx = aes.cx();
    512  auto produceSilenceWithError = MakeScopeExit([this, aTrack, cx, &aOutput] {
    513    SendProcessorError(aTrack, cx);
    514    ProduceSilence(aTrack, aOutput);
    515  });
    516 
    517  JS::Rooted<JS::Value> process(cx);
    518  if (!JS_GetProperty(cx, mProcessor, "process", &process) ||
    519      !process.isObject() || !JS::IsCallable(&process.toObject()) ||
    520      !PrepareBufferArrays(cx, aInput, &mInputs, ArrayElementInit::None) ||
    521      !PrepareBufferArrays(cx, aOutput, &mOutputs, ArrayElementInit::Zero)) {
    522    // process() not callable or OOM.
    523    return;
    524  }
    525 
    526  // Copy input values to JS objects.
    527  for (size_t i = 0; i < aInput.Length(); ++i) {
    528    const AudioBlock& input = aInput[i];
    529    size_t channelCount = input.ChannelCount();
    530    if (channelCount == 0) {
    531      // Null blocks have AUDIO_FORMAT_SILENCE.
    532      // Don't call ChannelData<float>().
    533      continue;
    534    }
    535    float volume = input.mVolume;
    536    const auto& channelData = input.ChannelData<float>();
    537    const auto& float32Arrays = mInputs.mPorts[i].mFloat32Arrays;
    538    JS::AutoCheckCannotGC nogc;
    539    for (size_t c = 0; c < channelCount; ++c) {
    540      bool isShared;
    541      float* dest = JS_GetFloat32ArrayData(float32Arrays[c], &isShared, nogc);
    542      MOZ_ASSERT(!isShared);  // Was created as unshared
    543      AudioBlockCopyChannelWithScale(channelData[c], volume, dest);
    544    }
    545  }
    546 
    547  TrackTime tick = mDestination->GraphTimeToTrackTime(aFrom);
    548  // Compute and copy parameter values to JS objects.
    549  for (size_t i = 0; i < mParamTimelines.Length(); ++i) {
    550    const auto& float32Arrays = mParameters.mFloat32Arrays[i];
    551    size_t length = JS_GetTypedArrayLength(float32Arrays);
    552 
    553    // If the Float32Array that is supposed to hold the values for a particular
    554    // AudioParam has been detached, error out. This is being worked on in
    555    // https://github.com/WebAudio/web-audio-api/issues/1933 and
    556    // https://bugzilla.mozilla.org/show_bug.cgi?id=1619486
    557    if (length != WEBAUDIO_BLOCK_SIZE) {
    558      return;
    559    }
    560    JS::AutoCheckCannotGC nogc;
    561    bool isShared;
    562    float* dest = JS_GetFloat32ArrayData(float32Arrays, &isShared, nogc);
    563    MOZ_ASSERT(!isShared);  // Was created as unshared
    564 
    565    size_t frames =
    566        mParamTimelines[i].mTimeline.HasSimpleValue() ? 1 : WEBAUDIO_BLOCK_SIZE;
    567    mParamTimelines[i].mTimeline.GetValuesAtTime(tick, dest, frames);
    568    // https://bugzilla.mozilla.org/show_bug.cgi?id=1616599
    569    if (frames == 1) {
    570      std::fill_n(dest + 1, WEBAUDIO_BLOCK_SIZE - 1, dest[0]);
    571    }
    572  }
    573 
    574  if (!CallProcess(aTrack, cx, process)) {
    575    // An exception occurred.
    576    /**
    577     * https://webaudio.github.io/web-audio-api/#dom-audioworkletnode-onprocessorerror
    578     * Note that once an exception is thrown, the processor will output silence
    579     * throughout its lifetime.
    580     */
    581    return;
    582  }
    583 
    584  // Copy output values from JS objects.
    585  for (size_t o = 0; o < aOutput.Length(); ++o) {
    586    AudioBlock* output = &aOutput[o];
    587    size_t channelCount = output->ChannelCount();
    588    const auto& float32Arrays = mOutputs.mPorts[o].mFloat32Arrays;
    589    for (size_t c = 0; c < channelCount; ++c) {
    590      size_t length = JS_GetTypedArrayLength(float32Arrays[c]);
    591      if (length != WEBAUDIO_BLOCK_SIZE) {
    592        // ArrayBuffer has been detached.  Behavior is unspecified.
    593        // https://github.com/WebAudio/web-audio-api/issues/1933 and
    594        // https://bugzilla.mozilla.org/show_bug.cgi?id=1619486
    595        return;
    596      }
    597      JS::AutoCheckCannotGC nogc;
    598      bool isShared;
    599      const float* src =
    600          JS_GetFloat32ArrayData(float32Arrays[c], &isShared, nogc);
    601      MOZ_ASSERT(!isShared);  // Was created as unshared
    602      PodCopy(output->ChannelFloatsForWrite(c), src, WEBAUDIO_BLOCK_SIZE);
    603    }
    604  }
    605 
    606  produceSilenceWithError.release();  // have output and no error
    607 }
    608 
    609 AudioWorkletNode::AudioWorkletNode(AudioContext* aAudioContext,
    610                                   const nsAString& aName,
    611                                   const AudioWorkletNodeOptions& aOptions)
    612    : AudioNode(aAudioContext, 2, ChannelCountMode::Max,
    613                ChannelInterpretation::Speakers),
    614      mNodeName(aName),
    615      mInputCount(aOptions.mNumberOfInputs),
    616      mOutputCount(aOptions.mNumberOfOutputs) {}
    617 
    618 void AudioWorkletNode::InitializeParameters(
    619    nsTArray<NamedAudioParamTimeline>* aParamTimelines, ErrorResult& aRv) {
    620  MOZ_ASSERT(!mParameters, "Only initialize the `parameters` attribute once.");
    621  MOZ_ASSERT(aParamTimelines);
    622 
    623  AudioContext* context = Context();
    624  const AudioParamDescriptorMap* parameterDescriptors =
    625      context->GetParamMapForWorkletName(mNodeName);
    626  MOZ_ASSERT(parameterDescriptors);
    627 
    628  size_t audioParamIndex = 0;
    629  aParamTimelines->SetCapacity(parameterDescriptors->Length());
    630 
    631  for (size_t i = 0; i < parameterDescriptors->Length(); i++) {
    632    auto& paramEntry = (*parameterDescriptors)[i];
    633    CreateAudioParam(audioParamIndex++, paramEntry.mName,
    634                     paramEntry.mDefaultValue, paramEntry.mMinValue,
    635                     paramEntry.mMaxValue);
    636    aParamTimelines->AppendElement(paramEntry);
    637  }
    638 }
    639 
    640 void AudioWorkletNode::SendParameterData(
    641    const Optional<Record<nsString, double>>& aParameterData) {
    642  MOZ_ASSERT(mTrack, "This method only works if the track has been created.");
    643  nsAutoString name;
    644  if (aParameterData.WasPassed()) {
    645    const auto& paramData = aParameterData.Value();
    646    for (const auto& paramDataEntry : paramData.Entries()) {
    647      for (auto& audioParam : mParams) {
    648        audioParam->GetName(name);
    649        if (paramDataEntry.mKey.Equals(name)) {
    650          audioParam->SetInitialValue(paramDataEntry.mValue);
    651        }
    652      }
    653    }
    654  }
    655 }
    656 
    657 /* static */
    658 already_AddRefed<AudioWorkletNode> AudioWorkletNode::Constructor(
    659    const GlobalObject& aGlobal, AudioContext& aAudioContext,
    660    const nsAString& aName, const AudioWorkletNodeOptions& aOptions,
    661    ErrorResult& aRv) {
    662  TRACE_COMMENT("AudioWorkletNode::Constructor", "%s",
    663                NS_ConvertUTF16toUTF8(aName).get());
    664  /**
    665   * 1. If nodeName does not exist as a key in the BaseAudioContext’s node
    666   *    name to parameter descriptor map, throw a InvalidStateError exception
    667   *    and abort these steps.
    668   */
    669  const AudioParamDescriptorMap* parameterDescriptors =
    670      aAudioContext.GetParamMapForWorkletName(aName);
    671  if (!parameterDescriptors) {
    672    // Not using nsPrintfCString in case aName has embedded nulls.
    673    aRv.ThrowInvalidStateError("Unknown AudioWorklet name '"_ns +
    674                               NS_ConvertUTF16toUTF8(aName) + "'"_ns);
    675    return nullptr;
    676  }
    677 
    678  // See https://github.com/WebAudio/web-audio-api/issues/2074 for ordering.
    679  RefPtr<AudioWorkletNode> audioWorkletNode =
    680      new AudioWorkletNode(&aAudioContext, aName, aOptions);
    681  audioWorkletNode->Initialize(aOptions, aRv);
    682  if (NS_WARN_IF(aRv.Failed())) {
    683    return nullptr;
    684  }
    685 
    686  /**
    687   * 3. Configure input, output and output channels of node with options.
    688   */
    689  if (aOptions.mNumberOfInputs == 0 && aOptions.mNumberOfOutputs == 0) {
    690    aRv.ThrowNotSupportedError(
    691        "Must have nonzero numbers of inputs or outputs");
    692    return nullptr;
    693  }
    694 
    695  if (aOptions.mOutputChannelCount.WasPassed()) {
    696    /**
    697     * 1. If any value in outputChannelCount is zero or greater than the
    698     *    implementation’s maximum number of channels, throw a
    699     *    NotSupportedError and abort the remaining steps.
    700     */
    701    for (uint32_t channelCount : aOptions.mOutputChannelCount.Value()) {
    702      if (channelCount == 0 || channelCount > WebAudioUtils::MaxChannelCount) {
    703        aRv.ThrowNotSupportedError(
    704            nsPrintfCString("Channel count (%u) must be in the range [1, max "
    705                            "supported channel count]",
    706                            channelCount));
    707        return nullptr;
    708      }
    709    }
    710    /**
    711     * 2. If the length of outputChannelCount does not equal numberOfOutputs,
    712     *    throw an IndexSizeError and abort the remaining steps.
    713     */
    714    if (aOptions.mOutputChannelCount.Value().Length() !=
    715        aOptions.mNumberOfOutputs) {
    716      aRv.ThrowIndexSizeError(
    717          nsPrintfCString("Length of outputChannelCount (%zu) does not match "
    718                          "numberOfOutputs (%u)",
    719                          aOptions.mOutputChannelCount.Value().Length(),
    720                          aOptions.mNumberOfOutputs));
    721      return nullptr;
    722    }
    723  }
    724  // MTG does not support more than UINT16_MAX inputs or outputs.
    725  if (aOptions.mNumberOfInputs > UINT16_MAX) {
    726    aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("numberOfInputs");
    727    return nullptr;
    728  }
    729  if (aOptions.mNumberOfOutputs > UINT16_MAX) {
    730    aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("numberOfOutputs");
    731    return nullptr;
    732  }
    733 
    734  /**
    735   * 4. Let messageChannel be a new MessageChannel.
    736   */
    737  RefPtr<MessageChannel> messageChannel =
    738      MessageChannel::Constructor(aGlobal, aRv);
    739  if (NS_WARN_IF(aRv.Failed())) {
    740    return nullptr;
    741  }
    742  /* 5. Let nodePort be the value of messageChannel’s port1 attribute.
    743   * 6. Let processorPortOnThisSide be the value of messageChannel’s port2
    744   *    attribute.
    745   * 7. Let serializedProcessorPort be the result of
    746   *    StructuredSerializeWithTransfer(processorPortOnThisSide,
    747   *                                    « processorPortOnThisSide »).
    748   */
    749  UniqueMessagePortId processorPortId;
    750  messageChannel->Port2()->CloneAndDisentangle(processorPortId);
    751  /**
    752   * 8. Convert options dictionary to optionsObject.
    753   */
    754  JSContext* cx = aGlobal.Context();
    755  JS::Rooted<JS::Value> optionsVal(cx);
    756  if (NS_WARN_IF(!ToJSValue(cx, aOptions, &optionsVal))) {
    757    aRv.NoteJSContextException(cx);
    758    return nullptr;
    759  }
    760 
    761  /**
    762   * 9. Let serializedOptions be the result of
    763   *    StructuredSerialize(optionsObject).
    764   */
    765 
    766  // This context and the worklet are part of the same agent cluster and they
    767  // can share memory.
    768  JS::CloneDataPolicy cloneDataPolicy;
    769  cloneDataPolicy.allowIntraClusterClonableSharedObjects();
    770  cloneDataPolicy.allowSharedMemoryObjects();
    771 
    772  // StructuredCloneHolder does not have a move constructor.  Instead allocate
    773  // memory so that the pointer can be passed to the rendering thread.
    774  UniquePtr<StructuredCloneHolder> serializedOptions =
    775      MakeUnique<StructuredCloneHolder>(
    776          StructuredCloneHolder::CloningSupported,
    777          StructuredCloneHolder::TransferringNotSupported,
    778          JS::StructuredCloneScope::SameProcess);
    779  serializedOptions->Write(cx, optionsVal, JS::UndefinedHandleValue,
    780                           cloneDataPolicy, aRv);
    781  if (NS_WARN_IF(aRv.Failed())) {
    782    return nullptr;
    783  }
    784  /**
    785   * 10. Set node’s port to nodePort.
    786   */
    787  audioWorkletNode->mPort = messageChannel->Port1();
    788 
    789  /**
    790   * 11. Let parameterDescriptors be the result of retrieval of nodeName from
    791   * node name to parameter descriptor map.
    792   */
    793  nsTArray<NamedAudioParamTimeline> paramTimelines;
    794  audioWorkletNode->InitializeParameters(&paramTimelines, aRv);
    795  if (NS_WARN_IF(aRv.Failed())) {
    796    return nullptr;
    797  }
    798 
    799  auto engine = new WorkletNodeEngine(
    800      audioWorkletNode, aAudioContext.Destination(), std::move(paramTimelines),
    801      aOptions.mOutputChannelCount);
    802  audioWorkletNode->mTrack = AudioNodeTrack::Create(
    803      &aAudioContext, engine, AudioNodeTrack::NO_TRACK_FLAGS,
    804      aAudioContext.Graph());
    805 
    806  audioWorkletNode->SendParameterData(aOptions.mParameterData);
    807 
    808  /**
    809   * 12. Queue a control message to invoke the constructor of the
    810   *     corresponding AudioWorkletProcessor with the processor construction
    811   *     data that consists of: nodeName, node, serializedOptions, and
    812   *     serializedProcessorPort.
    813   */
    814  Worklet* worklet = aAudioContext.GetAudioWorklet(aRv);
    815  MOZ_ASSERT(worklet, "Worklet already existed and so getter shouldn't fail.");
    816  auto workletImpl = static_cast<AudioWorkletImpl*>(worklet->Impl());
    817  audioWorkletNode->mTrack->SendRunnable(NS_NewRunnableFunction(
    818      "WorkletNodeEngine::ConstructProcessor",
    819  // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT.
    820  // See bug 1535398.
    821  //
    822  // Note that clang and gcc have mutually incompatible rules about whether
    823  // attributes should come before or after the `mutable` keyword here, so
    824  // use a compatibility hack until we can switch to the standardized
    825  // [[attr]] syntax (bug 1627007).
    826 #ifdef __clang__
    827 #  define AND_MUTABLE(macro) macro mutable
    828 #else
    829 #  define AND_MUTABLE(macro) mutable macro
    830 #endif
    831      [track = audioWorkletNode->mTrack,
    832       workletImpl = RefPtr<AudioWorkletImpl>(workletImpl),
    833       name = nsString(aName), options = std::move(serializedOptions),
    834       portId = std::move(processorPortId)]()
    835          AND_MUTABLE(MOZ_CAN_RUN_SCRIPT_BOUNDARY) {
    836            auto engine = static_cast<WorkletNodeEngine*>(track->Engine());
    837            engine->ConstructProcessor(
    838                workletImpl, name, WrapNotNull(options.get()), portId, track);
    839          }));
    840 #undef AND_MUTABLE
    841 
    842  // [[active source]] is initially true and so at least the first process()
    843  // call will not be skipped when there are no active inputs.
    844  audioWorkletNode->MarkActive();
    845 
    846  return audioWorkletNode.forget();
    847 }
    848 
    849 AudioParamMap* AudioWorkletNode::GetParameters(ErrorResult& aRv) {
    850  if (!mParameters) {
    851    RefPtr<AudioParamMap> parameters = new AudioParamMap(this);
    852    nsAutoString name;
    853    for (const auto& audioParam : mParams) {
    854      audioParam->GetName(name);
    855      AudioParamMap_Binding::MaplikeHelpers::Set(parameters, name, *audioParam,
    856                                                 aRv);
    857      if (NS_WARN_IF(aRv.Failed())) {
    858        return nullptr;
    859      }
    860    }
    861    mParameters = std::move(parameters);
    862  }
    863  return mParameters.get();
    864 }
    865 
    866 void AudioWorkletNode::DispatchProcessorErrorEvent(
    867    const ProcessorErrorDetails& aDetails) {
    868  TRACE("AudioWorkletNode::DispatchProcessorErrorEvent");
    869  if (HasListenersFor(nsGkAtoms::onprocessorerror)) {
    870    RootedDictionary<ErrorEventInit> init(RootingCx());
    871    init.mMessage = aDetails.mMessage;
    872    init.mFilename = aDetails.mFilename;
    873    init.mLineno = aDetails.mLineno;
    874    init.mColno = aDetails.mColno;
    875    RefPtr<ErrorEvent> errorEvent =
    876        ErrorEvent::Constructor(this, u"processorerror"_ns, init);
    877    MOZ_ASSERT(errorEvent);
    878    DispatchTrustedEvent(errorEvent);
    879  }
    880 }
    881 
    882 JSObject* AudioWorkletNode::WrapObject(JSContext* aCx,
    883                                       JS::Handle<JSObject*> aGivenProto) {
    884  return AudioWorkletNode_Binding::Wrap(aCx, this, aGivenProto);
    885 }
    886 
    887 size_t AudioWorkletNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
    888  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
    889  return amount;
    890 }
    891 
    892 size_t AudioWorkletNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
    893  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
    894 }
    895 
    896 }  // namespace mozilla::dom