tor-browser

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

AudioWorkletGlobalScope.cpp (12665B)


      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 http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "AudioWorkletGlobalScope.h"
      8 
      9 #include "AudioNodeEngine.h"
     10 #include "AudioNodeTrack.h"
     11 #include "AudioWorkletImpl.h"
     12 #include "Tracing.h"
     13 #include "js/ForOfIterator.h"
     14 #include "js/PropertyAndElement.h"  // JS_GetProperty
     15 #include "jsapi.h"
     16 #include "mozilla/BasePrincipal.h"
     17 #include "mozilla/dom/AudioParamDescriptorBinding.h"
     18 #include "mozilla/dom/AudioWorkletGlobalScopeBinding.h"
     19 #include "mozilla/dom/AudioWorkletProcessor.h"
     20 #include "mozilla/dom/BindingCallContext.h"
     21 #include "mozilla/dom/MessagePort.h"
     22 #include "mozilla/dom/StructuredCloneHolder.h"
     23 #include "nsPrintfCString.h"
     24 #include "nsTHashSet.h"
     25 
     26 namespace mozilla::dom {
     27 
     28 NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioWorkletGlobalScope, WorkletGlobalScope,
     29                                   mNameToProcessorMap, mPort);
     30 
     31 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioWorkletGlobalScope)
     32 NS_INTERFACE_MAP_END_INHERITING(WorkletGlobalScope)
     33 
     34 NS_IMPL_ADDREF_INHERITED(AudioWorkletGlobalScope, WorkletGlobalScope)
     35 NS_IMPL_RELEASE_INHERITED(AudioWorkletGlobalScope, WorkletGlobalScope)
     36 
     37 AudioWorkletGlobalScope::AudioWorkletGlobalScope(AudioWorkletImpl* aImpl)
     38    : WorkletGlobalScope(aImpl) {}
     39 
     40 AudioWorkletImpl* AudioWorkletGlobalScope::Impl() const {
     41  return static_cast<AudioWorkletImpl*>(mImpl.get());
     42 }
     43 
     44 bool AudioWorkletGlobalScope::WrapGlobalObject(
     45    JSContext* aCx, JS::MutableHandle<JSObject*> aReflector) {
     46  // |this| is being exposed to JS and content script will soon be running.
     47  // The graph needs a handle on the JSContext so it can interrupt JS.
     48  Impl()->DestinationTrack()->Graph()->NotifyJSContext(aCx);
     49 
     50  JS::RealmOptions options = CreateRealmOptions();
     51  return AudioWorkletGlobalScope_Binding::Wrap(
     52      aCx, this, this, options, BasePrincipal::Cast(mImpl->Principal()),
     53      aReflector);
     54 }
     55 
     56 void AudioWorkletGlobalScope::RegisterProcessor(
     57    JSContext* aCx, const nsAString& aName,
     58    AudioWorkletProcessorConstructor& aProcessorCtor, ErrorResult& aRv) {
     59  TRACE_COMMENT("AudioWorkletGlobalScope::RegisterProcessor", "%s",
     60                NS_ConvertUTF16toUTF8(aName).get());
     61 
     62  JS::Rooted<JSObject*> processorConstructor(aCx,
     63                                             aProcessorCtor.CallableOrNull());
     64 
     65  /**
     66   * 1. If the name is the empty string, throw a NotSupportedError
     67   *    exception and abort these steps because the empty string is not
     68   *    a valid key.
     69   */
     70  if (aName.IsEmpty()) {
     71    aRv.ThrowNotSupportedError("Argument 1 should not be an empty string.");
     72    return;
     73  }
     74 
     75  /**
     76   * 2. If the name exists as a key in the node name to processor
     77   *    definition map, throw a NotSupportedError exception and abort
     78   *    these steps because registering a definition with a duplicated
     79   *    key is not allowed.
     80   */
     81  if (mNameToProcessorMap.GetWeak(aName)) {
     82    // Duplicate names are not allowed
     83    aRv.ThrowNotSupportedError(
     84        "Argument 1 is invalid: a class with the same name is already "
     85        "registered.");
     86    return;
     87  }
     88 
     89  // We know processorConstructor is callable, so not a WindowProxy or Location.
     90  JS::Rooted<JSObject*> constructorUnwrapped(
     91      aCx, js::CheckedUnwrapStatic(processorConstructor));
     92  if (!constructorUnwrapped) {
     93    // If the caller's compartment does not have permission to access the
     94    // unwrapped constructor then throw.
     95    aRv.ThrowSecurityError("Constructor cannot be called");
     96    return;
     97  }
     98 
     99  /**
    100   * 3. If the result of IsConstructor(argument=processorCtor) is false,
    101   *    throw a TypeError and abort these steps.
    102   */
    103  if (!JS::IsConstructor(constructorUnwrapped)) {
    104    aRv.ThrowTypeError<MSG_NOT_CONSTRUCTOR>("Argument 2");
    105    return;
    106  }
    107 
    108  /**
    109   * 4. Let prototype be the result of Get(O=processorCtor, P="prototype").
    110   */
    111  // The .prototype on the constructor passed could be an "expando" of a
    112  // wrapper. So we should get it from wrapper instead of the underlying
    113  // object.
    114  JS::Rooted<JS::Value> prototype(aCx);
    115  if (!JS_GetProperty(aCx, processorConstructor, "prototype", &prototype)) {
    116    aRv.NoteJSContextException(aCx);
    117    return;
    118  }
    119 
    120  /**
    121   * 5. If the result of Type(argument=prototype) is not Object, throw a
    122   *    TypeError and abort all these steps.
    123   */
    124  if (!prototype.isObject()) {
    125    aRv.ThrowTypeError<MSG_NOT_OBJECT>("processorCtor.prototype");
    126    return;
    127  }
    128  /**
    129   * 6. Let parameterDescriptorsValue be the result of Get(O=processorCtor,
    130   *    P="parameterDescriptors").
    131   */
    132  JS::Rooted<JS::Value> descriptors(aCx);
    133  if (!JS_GetProperty(aCx, processorConstructor, "parameterDescriptors",
    134                      &descriptors)) {
    135    aRv.NoteJSContextException(aCx);
    136    return;
    137  }
    138 
    139  AudioParamDescriptorMap map;
    140  /*
    141   * 7. If parameterDescriptorsValue is not undefined
    142   */
    143  if (!descriptors.isUndefined()) {
    144    /*
    145     * 7.1. Let parameterDescriptorSequence be the result of the conversion
    146     *    from parameterDescriptorsValue to an IDL value of type
    147     *    sequence<AudioParamDescriptor>.
    148     */
    149    JS::Rooted<JS::Value> objectValue(aCx, descriptors);
    150    JS::ForOfIterator iter(aCx);
    151    if (!iter.init(objectValue, JS::ForOfIterator::AllowNonIterable)) {
    152      aRv.NoteJSContextException(aCx);
    153      return;
    154    }
    155    if (!iter.valueIsIterable()) {
    156      aRv.ThrowTypeError<MSG_CONVERSION_ERROR>(
    157          "AudioWorkletProcessor.parameterDescriptors", "sequence");
    158      return;
    159    }
    160    /*
    161     * 7.2 and 7.3 (and substeps)
    162     */
    163    map = DescriptorsFromJS(aCx, &iter, aRv);
    164    if (aRv.Failed()) {
    165      return;
    166    }
    167  }
    168 
    169  /**
    170   * 8. Append the key-value pair name → processorCtor to node name to processor
    171   * constructor map of the associated AudioWorkletGlobalScope.
    172   */
    173  if (!mNameToProcessorMap.InsertOrUpdate(aName, RefPtr{&aProcessorCtor},
    174                                          fallible)) {
    175    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    176    return;
    177  }
    178 
    179  /**
    180   * 9. Queue a task to the control thread to add the key-value pair
    181   *     (name - descriptors) to the node name to parameter descriptor
    182   *     map of the associated BaseAudioContext.
    183   */
    184  NS_DispatchToMainThread(NS_NewRunnableFunction(
    185      "AudioWorkletGlobalScope: parameter descriptors",
    186      [impl = RefPtr{Impl()}, name = nsString(aName),
    187       map = std::move(map)]() mutable {
    188        AudioNode* destinationNode =
    189            impl->DestinationTrack()->Engine()->NodeMainThread();
    190        if (!destinationNode) {
    191          return;
    192        }
    193        destinationNode->Context()->SetParamMapForWorkletName(name, &map);
    194      }));
    195 }
    196 
    197 uint64_t AudioWorkletGlobalScope::CurrentFrame() const {
    198  AudioNodeTrack* destinationTrack = Impl()->DestinationTrack();
    199  GraphTime processedTime = destinationTrack->Graph()->ProcessedTime();
    200  return destinationTrack->GraphTimeToTrackTime(processedTime);
    201 }
    202 
    203 double AudioWorkletGlobalScope::CurrentTime() const {
    204  return static_cast<double>(CurrentFrame()) / SampleRate();
    205 }
    206 
    207 float AudioWorkletGlobalScope::SampleRate() const {
    208  return static_cast<float>(Impl()->DestinationTrack()->mSampleRate);
    209 }
    210 
    211 AudioParamDescriptorMap AudioWorkletGlobalScope::DescriptorsFromJS(
    212    JSContext* aCx, JS::ForOfIterator* aIter, ErrorResult& aRv) {
    213  AudioParamDescriptorMap res;
    214  // To check for duplicates
    215  nsTHashSet<nsString> namesSet;
    216 
    217  JS::Rooted<JS::Value> nextValue(aCx);
    218  bool done = false;
    219  size_t i = 0;
    220  while (true) {
    221    if (!aIter->next(&nextValue, &done)) {
    222      aRv.NoteJSContextException(aCx);
    223      return AudioParamDescriptorMap();
    224    }
    225    if (done) {
    226      break;
    227    }
    228 
    229    BindingCallContext callCx(aCx, "AudioWorkletGlobalScope.registerProcessor");
    230    nsPrintfCString sourceDescription("Element %zu in parameterDescriptors", i);
    231    i++;
    232    AudioParamDescriptor* descriptor = res.AppendElement(fallible);
    233    if (!descriptor) {
    234      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    235      return AudioParamDescriptorMap();
    236    }
    237    if (!descriptor->Init(callCx, nextValue, sourceDescription.get())) {
    238      aRv.NoteJSContextException(aCx);
    239      return AudioParamDescriptorMap();
    240    }
    241  }
    242 
    243  for (const auto& descriptor : res) {
    244    if (namesSet.Contains(descriptor.mName)) {
    245      aRv.ThrowNotSupportedError("Duplicated name \""_ns +
    246                                 NS_ConvertUTF16toUTF8(descriptor.mName) +
    247                                 "\" in parameterDescriptors."_ns);
    248      return AudioParamDescriptorMap();
    249    }
    250 
    251    if (!namesSet.Insert(descriptor.mName, fallible)) {
    252      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    253      return AudioParamDescriptorMap();
    254    }
    255 
    256    if (descriptor.mMinValue > descriptor.mMaxValue) {
    257      aRv.ThrowInvalidStateError(
    258          "In parameterDescriptors, "_ns +
    259          NS_ConvertUTF16toUTF8(descriptor.mName) +
    260          " minValue should be smaller than maxValue."_ns);
    261      return AudioParamDescriptorMap();
    262    }
    263 
    264    if (descriptor.mDefaultValue < descriptor.mMinValue ||
    265        descriptor.mDefaultValue > descriptor.mMaxValue) {
    266      aRv.ThrowInvalidStateError(
    267          "In parameterDescriptors, "_ns +
    268          NS_ConvertUTF16toUTF8(descriptor.mName) +
    269          nsLiteralCString(" defaultValue is out of the range defined by "
    270                           "minValue and maxValue."));
    271      return AudioParamDescriptorMap();
    272    }
    273  }
    274 
    275  return res;
    276 }
    277 
    278 bool AudioWorkletGlobalScope::ConstructProcessor(
    279    JSContext* aCx, const nsAString& aName,
    280    NotNull<StructuredCloneHolder*> aSerializedOptions,
    281    UniqueMessagePortId& aPortIdentifier,
    282    JS::MutableHandle<JSObject*> aRetProcessor) {
    283  TRACE_COMMENT("AudioWorkletProcessor::ConstructProcessor", "%s",
    284                NS_ConvertUTF16toUTF8(aName).get());
    285  /**
    286   * See
    287   * https://webaudio.github.io/web-audio-api/#AudioWorkletProcessor-instantiation
    288   */
    289  ErrorResult rv;
    290  /**
    291   * 4. Let deserializedPort be the result of
    292   *    StructuredDeserialize(serializedPort, the current Realm).
    293   */
    294  RefPtr<MessagePort> deserializedPort =
    295      MessagePort::Create(this, aPortIdentifier, rv);
    296  if (NS_WARN_IF(rv.MaybeSetPendingException(aCx))) {
    297    return false;
    298  }
    299  /**
    300   * 5. Let deserializedOptions be the result of
    301   *    StructuredDeserialize(serializedOptions, the current Realm).
    302   */
    303  JS::CloneDataPolicy cloneDataPolicy;
    304  cloneDataPolicy.allowIntraClusterClonableSharedObjects();
    305  cloneDataPolicy.allowSharedMemoryObjects();
    306 
    307  JS::Rooted<JS::Value> deserializedOptions(aCx);
    308  aSerializedOptions->Read(this, aCx, &deserializedOptions, cloneDataPolicy,
    309                           rv);
    310  if (rv.MaybeSetPendingException(aCx)) {
    311    return false;
    312  }
    313  /**
    314   * 6. Let processorCtor be the result of looking up processorName on the
    315   *    AudioWorkletGlobalScope's node name to processor definition map.
    316   */
    317  RefPtr<AudioWorkletProcessorConstructor> processorCtor =
    318      mNameToProcessorMap.Get(aName);
    319  // AudioWorkletNode has already checked the definition exists.
    320  // See also https://github.com/WebAudio/web-audio-api/issues/1854
    321  MOZ_ASSERT(processorCtor);
    322  /**
    323   * 7. Store nodeReference and deserializedPort to node reference and
    324   *    transferred port of this AudioWorkletGlobalScope's pending processor
    325   *    construction data respectively.
    326   */
    327  // |nodeReference| is not required here because the "processorerror" event
    328  // is thrown by WorkletNodeEngine::ConstructProcessor().
    329  mPortForProcessor = std::move(deserializedPort);
    330  /**
    331   * 8. Construct a callback function from processorCtor with the argument
    332   *    of deserializedOptions.
    333   */
    334  // The options were an object before serialization and so will be an object
    335  // if deserialization succeeded above.  toObject() asserts.
    336  JS::Rooted<JSObject*> options(aCx, &deserializedOptions.toObject());
    337  RefPtr<AudioWorkletProcessor> processor = processorCtor->Construct(
    338      options, rv, "AudioWorkletProcessor construction",
    339      CallbackFunction::eRethrowExceptions);
    340  // https://github.com/WebAudio/web-audio-api/issues/2096
    341  mPortForProcessor = nullptr;
    342  if (rv.MaybeSetPendingException(aCx)) {
    343    return false;
    344  }
    345  JS::Rooted<JS::Value> processorVal(aCx);
    346  if (NS_WARN_IF(!ToJSValue(aCx, processor, &processorVal))) {
    347    return false;
    348  }
    349  MOZ_ASSERT(processorVal.isObject());
    350  aRetProcessor.set(&processorVal.toObject());
    351  return true;
    352 }
    353 
    354 RefPtr<MessagePort> AudioWorkletGlobalScope::TakePortForProcessorCtor() {
    355  return std::move(mPortForProcessor);
    356 }
    357 
    358 }  // namespace mozilla::dom