// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope.h" #include #include #include "base/auto_reset.h" #include "base/stl_util.h" #include "third_party/blink/renderer/bindings/core/v8/idl_types.h" #include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h" #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h" #include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_audio_worklet_processor.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_blink_audio_worklet_process_callback.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_blink_audio_worklet_processor_constructor.h" #include "third_party/blink/renderer/core/workers/worker_thread.h" #include "third_party/blink/renderer/modules/webaudio/audio_worklet_processor_definition.h" #include "third_party/blink/renderer/modules/webaudio/cross_thread_audio_worklet_processor_info.h" #include "third_party/blink/renderer/platform/bindings/callback_method_retriever.h" namespace blink { AudioWorkletGlobalScope::AudioWorkletGlobalScope( std::unique_ptr creation_params, WorkerThread* thread) : WorkletGlobalScope(std::move(creation_params), thread->GetWorkerReportingProxy(), thread) { // Audio is prone to jank introduced by e.g. the garbage collector. Workers // are generally put in a background mode (as they are non-visible). Audio is // an exception here, requiring low-latency behavior similar to any visible // state. GetIsolate()->IsolateInForegroundNotification(); } AudioWorkletGlobalScope::~AudioWorkletGlobalScope() = default; void AudioWorkletGlobalScope::Dispose() { DCHECK(IsContextThread()); is_closing_ = true; WorkletGlobalScope::Dispose(); } void AudioWorkletGlobalScope::registerProcessor( const String& name, V8BlinkAudioWorkletProcessorConstructor* processor_ctor, ExceptionState& exception_state) { DCHECK(IsContextThread()); // 1. If name is an empty string, throw a NotSupportedError. if (name.IsEmpty()) { exception_state.ThrowDOMException( DOMExceptionCode::kNotSupportedError, "The processor name cannot be empty."); return; } // 2. If name already exists as a key in the node name to processor // constructor map, throw a NotSupportedError. if (processor_definition_map_.Contains(name)) { exception_state.ThrowDOMException( DOMExceptionCode::kNotSupportedError, "An AudioWorkletProcessor with name:\"" + name + "\" is already registered."); return; } // 3. If the result of IsConstructor(argument=processorCtor) is false, throw // a TypeError . if (!processor_ctor->IsConstructor()) { exception_state.ThrowTypeError( "The provided class definition of \"" + name + "\" AudioWorkletProcessor is not a constructor."); return; } // 4. Let prototype be the result of Get(O=processorCtor, P="prototype"). // 5. If the result of Type(argument=prototype) is not Object, throw a // TypeError . CallbackMethodRetriever retriever(processor_ctor); retriever.GetPrototypeObject(exception_state); if (exception_state.HadException()) return; // TODO(crbug.com/1077911): Do not extract process() function at the // registration step. v8::Local v8_process = retriever.GetMethodOrThrow("process", exception_state); if (exception_state.HadException()) return; V8BlinkAudioWorkletProcessCallback* process = V8BlinkAudioWorkletProcessCallback::Create(v8_process); // The sufficient information to build a AudioWorkletProcessorDefinition // is collected. The rest of registration process is optional. // (i.e. parameterDescriptors) AudioWorkletProcessorDefinition* definition = AudioWorkletProcessorDefinition::Create(name, processor_ctor, process); v8::Isolate* isolate = processor_ctor->GetIsolate(); v8::Local current_context = isolate->GetCurrentContext(); v8::Local v8_parameter_descriptors; { v8::TryCatch try_catch(isolate); if (!processor_ctor->CallbackObject() ->Get(current_context, V8AtomicString(isolate, "parameterDescriptors")) .ToLocal(&v8_parameter_descriptors)) { exception_state.RethrowV8Exception(try_catch.Exception()); return; } } // 7. If parameterDescriptorsValue is not undefined, execute the following // steps: if (!v8_parameter_descriptors->IsNullOrUndefined()) { // 7.1. Let parameterDescriptorSequence be the result of the conversion // from parameterDescriptorsValue to an IDL value of type // sequence. const HeapVector>& given_param_descriptors = NativeValueTraits>::NativeValue( isolate, v8_parameter_descriptors, exception_state); if (exception_state.HadException()) return; // 7.2. Let paramNames be an empty Array. HeapVector> sanitized_param_descriptors; // 7.3. For each descriptor of parameterDescriptorSequence: HashSet sanitized_names; for (const auto& given_descriptor : given_param_descriptors) { const String new_param_name = given_descriptor->name(); if (!sanitized_names.insert(new_param_name).is_new_entry) { exception_state.ThrowDOMException( DOMExceptionCode::kNotSupportedError, "Found a duplicate name \"" + new_param_name + "\" in parameterDescriptors() from the AudioWorkletProcessor " + "definition of \"" + name + "\"."); return; } // TODO(crbug.com/1078546): The steps 7.3.3 ~ 7.3.6 are missing. sanitized_param_descriptors.push_back(given_descriptor); } definition->SetAudioParamDescriptors(sanitized_param_descriptors); } // 8. Append the key-value pair name → processorCtor to node name to // processor constructor map of the associated AudioWorkletGlobalScope. processor_definition_map_.Set(name, definition); } AudioWorkletProcessor* AudioWorkletGlobalScope::CreateProcessor( const String& name, MessagePortChannel message_port_channel, scoped_refptr node_options) { DCHECK(IsContextThread()); // The registered definition is already checked by AudioWorkletNode // construction process, so the |definition| here must be valid. AudioWorkletProcessorDefinition* definition = FindDefinition(name); DCHECK(definition); ScriptState* script_state = ScriptController()->GetScriptState(); ScriptState::Scope scope(script_state); // V8 object instance construction: this construction process is here to make // the AudioWorkletProcessor class a thin wrapper of v8::Object instance. v8::Isolate* isolate = script_state->GetIsolate(); v8::TryCatch try_catch(isolate); try_catch.SetVerbose(true); // Route errors/exceptions to the dev console. DCHECK(!processor_creation_params_); // There is no way to pass additional constructor arguments that are not // described in Web IDL, the static constructor will look up // |processor_creation_params_| in the global scope to perform the // construction properly. base::AutoReset> processor_creation_extra_param( &processor_creation_params_, std::make_unique( name, std::move(message_port_channel))); ScriptValue options(isolate, ToV8(node_options->Deserialize(isolate), script_state)); ScriptValue instance; if (!definition->ConstructorFunction()->Construct(options).To(&instance)) { return nullptr; } // ToImplWithTypeCheck() may return nullptr when the type does not match. AudioWorkletProcessor* processor = V8AudioWorkletProcessor::ToImplWithTypeCheck(isolate, instance.V8Value()); if (processor) { processor_instances_.push_back(processor); } return processor; } AudioWorkletProcessorDefinition* AudioWorkletGlobalScope::FindDefinition( const String& name) { return processor_definition_map_.at(name); } unsigned AudioWorkletGlobalScope::NumberOfRegisteredDefinitions() { return processor_definition_map_.size(); } std::unique_ptr> AudioWorkletGlobalScope::WorkletProcessorInfoListForSynchronization() { auto processor_info_list = std::make_unique>(); for (auto definition_entry : processor_definition_map_) { if (!definition_entry.value->IsSynchronized()) { definition_entry.value->MarkAsSynchronized(); processor_info_list->emplace_back(*definition_entry.value); } } return processor_info_list; } ProcessorCreationParams* AudioWorkletGlobalScope::GetProcessorCreationParams() { return processor_creation_params_.get(); } void AudioWorkletGlobalScope::SetCurrentFrame(size_t current_frame) { current_frame_ = current_frame; } void AudioWorkletGlobalScope::SetSampleRate(float sample_rate) { sample_rate_ = sample_rate; } double AudioWorkletGlobalScope::currentTime() const { return sample_rate_ > 0.0 ? current_frame_ / static_cast(sample_rate_) : 0.0; } void AudioWorkletGlobalScope::Trace(Visitor* visitor) const { visitor->Trace(processor_definition_map_); visitor->Trace(processor_instances_); WorkletGlobalScope::Trace(visitor); } } // namespace blink