1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/modules/webaudio/inspector_web_audio_agent.h"
6
7 #include <memory>
8 #include "third_party/blink/renderer/core/page/page.h"
9 #include "third_party/blink/renderer/core/frame/local_frame.h"
10 #include "third_party/blink/renderer/modules/webaudio/base_audio_context.h"
11 #include "third_party/blink/renderer/modules/webaudio/audio_context.h"
12 #include "third_party/blink/renderer/modules/webaudio/audio_graph_tracer.h"
13 #include "third_party/blink/renderer/modules/webaudio/audio_listener.h"
14 #include "third_party/blink/renderer/modules/webaudio/audio_node.h"
15 #include "third_party/blink/renderer/modules/webaudio/audio_param.h"
16
17 namespace blink {
18
19 namespace {
20
GetContextTypeEnum(BaseAudioContext * context)21 String GetContextTypeEnum(BaseAudioContext* context) {
22 return context->HasRealtimeConstraint()
23 ? protocol::WebAudio::ContextTypeEnum::Realtime
24 : protocol::WebAudio::ContextTypeEnum::Offline;
25 }
26
GetContextStateEnum(BaseAudioContext * context)27 String GetContextStateEnum(BaseAudioContext* context) {
28 switch (context->ContextState()) {
29 case BaseAudioContext::AudioContextState::kSuspended:
30 return protocol::WebAudio::ContextStateEnum::Suspended;
31 case BaseAudioContext::AudioContextState::kRunning:
32 return protocol::WebAudio::ContextStateEnum::Running;
33 case BaseAudioContext::AudioContextState::kClosed:
34 return protocol::WebAudio::ContextStateEnum::Closed;
35 default:
36 NOTREACHED();
37 break;
38 }
39 }
40
41 // Strips "Node" from the node name string. For example, "GainNode" will return
42 // "Gain".
StripNodeSuffix(const String & nodeName)43 String StripNodeSuffix(const String& nodeName) {
44 return nodeName.EndsWith("Node") ? nodeName.Left(nodeName.length() - 4)
45 : "Unknown";
46 }
47
48 // Strips out the prefix and returns the actual parameter name. If the name
49 // does not match |NodeName.ParamName| pattern, returns "Unknown" instead.
StripParamPrefix(const String & paramName)50 String StripParamPrefix(const String& paramName) {
51 Vector<String> name_tokens;
52 paramName.Split('.', name_tokens);
53 return name_tokens.size() == 2 ? name_tokens.at(1) : "Unknown";
54 }
55
56 } // namespace
57
58 using protocol::Response;
59
InspectorWebAudioAgent(Page * page)60 InspectorWebAudioAgent::InspectorWebAudioAgent(Page* page)
61 : page_(page),
62 enabled_(&agent_state_, /*default_value=*/false) {
63 }
64
65 InspectorWebAudioAgent::~InspectorWebAudioAgent() = default;
66
Restore()67 void InspectorWebAudioAgent::Restore() {
68 if (!enabled_.Get())
69 return;
70
71 AudioGraphTracer* graph_tracer = AudioGraphTracer::FromPage(page_);
72 graph_tracer->SetInspectorAgent(this);
73 }
74
enable()75 Response InspectorWebAudioAgent::enable() {
76 if (enabled_.Get())
77 return Response::Success();
78 enabled_.Set(true);
79 AudioGraphTracer* graph_tracer = AudioGraphTracer::FromPage(page_);
80 graph_tracer->SetInspectorAgent(this);
81 return Response::Success();
82 }
83
disable()84 Response InspectorWebAudioAgent::disable() {
85 if (!enabled_.Get())
86 return Response::Success();
87 enabled_.Clear();
88 AudioGraphTracer* graph_tracer = AudioGraphTracer::FromPage(page_);
89 graph_tracer->SetInspectorAgent(nullptr);
90 return Response::Success();
91 }
92
getRealtimeData(const protocol::WebAudio::GraphObjectId & contextId,std::unique_ptr<ContextRealtimeData> * out_data)93 Response InspectorWebAudioAgent::getRealtimeData(
94 const protocol::WebAudio::GraphObjectId& contextId,
95 std::unique_ptr<ContextRealtimeData>* out_data) {
96 auto* const graph_tracer = AudioGraphTracer::FromPage(page_);
97 if (!enabled_.Get())
98 return Response::ServerError("Enable agent first.");
99
100 BaseAudioContext* context = graph_tracer->GetContextById(contextId);
101 if (!context)
102 return Response::ServerError("Cannot find BaseAudioContext with such id.");
103
104 if (!context->HasRealtimeConstraint()) {
105 return Response::ServerError(
106 "ContextRealtimeData is only avaliable for an AudioContext.");
107 }
108
109 // The realtime metric collection is only for AudioContext.
110 AudioCallbackMetric metric =
111 static_cast<AudioContext*>(context)->GetCallbackMetric();
112 *out_data = ContextRealtimeData::create()
113 .setCurrentTime(context->currentTime())
114 .setRenderCapacity(metric.render_capacity)
115 .setCallbackIntervalMean(metric.mean_callback_interval)
116 .setCallbackIntervalVariance(metric.variance_callback_interval)
117 .build();
118 return Response::Success();
119 }
120
DidCreateBaseAudioContext(BaseAudioContext * context)121 void InspectorWebAudioAgent::DidCreateBaseAudioContext(
122 BaseAudioContext* context) {
123 GetFrontend()->contextCreated(BuildProtocolContext(context));
124 }
125
WillDestroyBaseAudioContext(BaseAudioContext * context)126 void InspectorWebAudioAgent::WillDestroyBaseAudioContext(
127 BaseAudioContext* context) {
128 GetFrontend()->contextWillBeDestroyed(context->Uuid());
129 }
130
DidChangeBaseAudioContext(BaseAudioContext * context)131 void InspectorWebAudioAgent::DidChangeBaseAudioContext(
132 BaseAudioContext* context) {
133 GetFrontend()->contextChanged(BuildProtocolContext(context));
134 }
135
DidCreateAudioListener(AudioListener * listener)136 void InspectorWebAudioAgent::DidCreateAudioListener(AudioListener* listener) {
137 GetFrontend()->audioListenerCreated(
138 protocol::WebAudio::AudioListener::create()
139 .setListenerId(listener->Uuid())
140 .setContextId(listener->ParentUuid())
141 .build());
142 }
143
WillDestroyAudioListener(AudioListener * listener)144 void InspectorWebAudioAgent::WillDestroyAudioListener(AudioListener* listener) {
145 GetFrontend()->audioListenerWillBeDestroyed(
146 listener->ParentUuid(), listener->Uuid());
147 }
148
DidCreateAudioNode(AudioNode * node)149 void InspectorWebAudioAgent::DidCreateAudioNode(AudioNode* node) {
150 GetFrontend()->audioNodeCreated(
151 protocol::WebAudio::AudioNode::create()
152 .setNodeId(node->Uuid())
153 .setNodeType(StripNodeSuffix(node->GetNodeName()))
154 .setNumberOfInputs(node->numberOfInputs())
155 .setNumberOfOutputs(node->numberOfOutputs())
156 .setChannelCount(node->channelCount())
157 .setChannelCountMode(node->channelCountMode())
158 .setChannelInterpretation(node->channelInterpretation())
159 .setContextId(node->ParentUuid())
160 .build());
161 }
162
WillDestroyAudioNode(AudioNode * node)163 void InspectorWebAudioAgent::WillDestroyAudioNode(AudioNode* node) {
164 GetFrontend()->audioNodeWillBeDestroyed(node->ParentUuid(), node->Uuid());
165 }
166
DidCreateAudioParam(AudioParam * param)167 void InspectorWebAudioAgent::DidCreateAudioParam(AudioParam* param) {
168 GetFrontend()->audioParamCreated(
169 protocol::WebAudio::AudioParam::create()
170 .setParamId(param->Uuid())
171 .setParamType(StripParamPrefix(param->GetParamName()))
172 .setRate(param->automationRate())
173 .setDefaultValue(param->defaultValue())
174 .setMinValue(param->minValue())
175 .setMaxValue(param->maxValue())
176 .setContextId(param->Context()->Uuid())
177 .setNodeId(param->ParentUuid())
178 .build());
179 }
180
WillDestroyAudioParam(AudioParam * param)181 void InspectorWebAudioAgent::WillDestroyAudioParam(AudioParam* param) {
182 GetFrontend()->audioParamWillBeDestroyed(
183 param->Context()->Uuid(), param->ParentUuid(), param->Uuid());
184 }
185
DidConnectNodes(AudioNode * source_node,AudioNode * destination_node,int32_t source_output_index,int32_t destination_input_index)186 void InspectorWebAudioAgent::DidConnectNodes(
187 AudioNode* source_node,
188 AudioNode* destination_node,
189 int32_t source_output_index,
190 int32_t destination_input_index) {
191 GetFrontend()->nodesConnected(
192 source_node->ParentUuid(),
193 source_node->Uuid(),
194 destination_node->Uuid(),
195 source_output_index,
196 destination_input_index);
197 }
198
DidDisconnectNodes(AudioNode * source_node,AudioNode * destination_node,int32_t source_output_index,int32_t destination_input_index)199 void InspectorWebAudioAgent::DidDisconnectNodes(
200 AudioNode* source_node,
201 AudioNode* destination_node,
202 int32_t source_output_index,
203 int32_t destination_input_index) {
204 GetFrontend()->nodesDisconnected(
205 source_node->ParentUuid(),
206 source_node->Uuid(),
207 destination_node ? destination_node->Uuid() : String(),
208 source_output_index,
209 destination_input_index);
210 }
211
DidConnectNodeParam(AudioNode * source_node,AudioParam * destination_param,int32_t source_output_index)212 void InspectorWebAudioAgent::DidConnectNodeParam(
213 AudioNode* source_node,
214 AudioParam* destination_param,
215 int32_t source_output_index) {
216 GetFrontend()->nodeParamConnected(
217 source_node->ParentUuid(),
218 source_node->Uuid(),
219 destination_param->Uuid(),
220 source_output_index);
221 }
222
DidDisconnectNodeParam(AudioNode * source_node,AudioParam * destination_param,int32_t source_output_index)223 void InspectorWebAudioAgent::DidDisconnectNodeParam(
224 AudioNode* source_node,
225 AudioParam* destination_param,
226 int32_t source_output_index) {
227 GetFrontend()->nodeParamDisconnected(
228 source_node->ParentUuid(),
229 source_node->Uuid(),
230 destination_param->Uuid(),
231 source_output_index);
232 }
233
234 std::unique_ptr<protocol::WebAudio::BaseAudioContext>
BuildProtocolContext(BaseAudioContext * context)235 InspectorWebAudioAgent::BuildProtocolContext(BaseAudioContext* context) {
236 return protocol::WebAudio::BaseAudioContext::create()
237 .setContextId(context->Uuid())
238 .setContextType(GetContextTypeEnum(context))
239 .setContextState(GetContextStateEnum(context))
240 .setCallbackBufferSize(context->CallbackBufferSize())
241 .setMaxOutputChannelCount(context->MaxChannelCount())
242 .setSampleRate(context->sampleRate())
243 .build();
244 }
245
Trace(Visitor * visitor)246 void InspectorWebAudioAgent::Trace(Visitor* visitor) {
247 visitor->Trace(page_);
248 InspectorBaseAgent::Trace(visitor);
249 }
250
251 } // namespace blink
252