blob: 6af14ccb6d60d36ceacf180e21cfd9cb86132e95 [file] [log] [blame]
// 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 <memory>
#include <utility>
#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<GlobalScopeCreationParams> 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::Function> 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<v8::Context> current_context = isolate->GetCurrentContext();
v8::Local<v8::Value> 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<AudioParamDescriptor>.
const HeapVector<Member<AudioParamDescriptor>>& given_param_descriptors =
NativeValueTraits<IDLSequence<AudioParamDescriptor>>::NativeValue(
isolate, v8_parameter_descriptors, exception_state);
if (exception_state.HadException())
return;
// 7.2. Let paramNames be an empty Array.
HeapVector<Member<AudioParamDescriptor>> sanitized_param_descriptors;
// 7.3. For each descriptor of parameterDescriptorSequence:
HashSet<String> 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<SerializedScriptValue> 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<std::unique_ptr<ProcessorCreationParams>>
processor_creation_extra_param(
&processor_creation_params_,
std::make_unique<ProcessorCreationParams>(
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<Vector<CrossThreadAudioWorkletProcessorInfo>>
AudioWorkletGlobalScope::WorkletProcessorInfoListForSynchronization() {
auto processor_info_list =
std::make_unique<Vector<CrossThreadAudioWorkletProcessorInfo>>();
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<double>(sample_rate_)
: 0.0;
}
void AudioWorkletGlobalScope::Trace(Visitor* visitor) const {
visitor->Trace(processor_definition_map_);
visitor->Trace(processor_instances_);
WorkletGlobalScope::Trace(visitor);
}
} // namespace blink