1 /*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "src/inspector/v8-runtime-agent-impl.h"
32
33 #include <inttypes.h>
34
35 #include "../../third_party/inspector_protocol/crdtp/json.h"
36 #include "src/debug/debug-interface.h"
37 #include "src/inspector/injected-script.h"
38 #include "src/inspector/inspected-context.h"
39 #include "src/inspector/protocol/Protocol.h"
40 #include "src/inspector/remote-object-id.h"
41 #include "src/inspector/v8-console-message.h"
42 #include "src/inspector/v8-debugger-agent-impl.h"
43 #include "src/inspector/v8-debugger.h"
44 #include "src/inspector/v8-inspector-impl.h"
45 #include "src/inspector/v8-inspector-session-impl.h"
46 #include "src/inspector/v8-stack-trace-impl.h"
47 #include "src/inspector/v8-value-utils.h"
48 #include "src/tracing/trace-event.h"
49
50 #include "include/v8-inspector.h"
51
52 namespace v8_inspector {
53
54 namespace V8RuntimeAgentImplState {
55 static const char customObjectFormatterEnabled[] =
56 "customObjectFormatterEnabled";
57 static const char runtimeEnabled[] = "runtimeEnabled";
58 static const char bindings[] = "bindings";
59 static const char globalBindingsKey[] = "";
60 } // namespace V8RuntimeAgentImplState
61
62 using protocol::Runtime::RemoteObject;
63
64 namespace {
65
66 template <typename ProtocolCallback>
67 class EvaluateCallbackWrapper : public EvaluateCallback {
68 public:
wrap(std::unique_ptr<ProtocolCallback> callback)69 static std::unique_ptr<EvaluateCallback> wrap(
70 std::unique_ptr<ProtocolCallback> callback) {
71 return std::unique_ptr<EvaluateCallback>(
72 new EvaluateCallbackWrapper(std::move(callback)));
73 }
sendSuccess(std::unique_ptr<protocol::Runtime::RemoteObject> result,protocol::Maybe<protocol::Runtime::ExceptionDetails> exceptionDetails)74 void sendSuccess(std::unique_ptr<protocol::Runtime::RemoteObject> result,
75 protocol::Maybe<protocol::Runtime::ExceptionDetails>
76 exceptionDetails) override {
77 return m_callback->sendSuccess(std::move(result),
78 std::move(exceptionDetails));
79 }
sendFailure(const protocol::DispatchResponse & response)80 void sendFailure(const protocol::DispatchResponse& response) override {
81 return m_callback->sendFailure(response);
82 }
83
84 private:
EvaluateCallbackWrapper(std::unique_ptr<ProtocolCallback> callback)85 explicit EvaluateCallbackWrapper(std::unique_ptr<ProtocolCallback> callback)
86 : m_callback(std::move(callback)) {}
87
88 std::unique_ptr<ProtocolCallback> m_callback;
89 };
90
91 template <typename ProtocolCallback>
wrapEvaluateResultAsync(InjectedScript * injectedScript,v8::MaybeLocal<v8::Value> maybeResultValue,const v8::TryCatch & tryCatch,const String16 & objectGroup,WrapMode wrapMode,ProtocolCallback * callback)92 bool wrapEvaluateResultAsync(InjectedScript* injectedScript,
93 v8::MaybeLocal<v8::Value> maybeResultValue,
94 const v8::TryCatch& tryCatch,
95 const String16& objectGroup, WrapMode wrapMode,
96 ProtocolCallback* callback) {
97 std::unique_ptr<RemoteObject> result;
98 Maybe<protocol::Runtime::ExceptionDetails> exceptionDetails;
99
100 Response response = injectedScript->wrapEvaluateResult(
101 maybeResultValue, tryCatch, objectGroup, wrapMode, &result,
102 &exceptionDetails);
103 if (response.IsSuccess()) {
104 callback->sendSuccess(std::move(result), std::move(exceptionDetails));
105 return true;
106 }
107 callback->sendFailure(response);
108 return false;
109 }
110
innerCallFunctionOn(V8InspectorSessionImpl * session,InjectedScript::Scope & scope,v8::Local<v8::Value> recv,const String16 & expression,Maybe<protocol::Array<protocol::Runtime::CallArgument>> optionalArguments,bool silent,WrapMode wrapMode,bool userGesture,bool awaitPromise,const String16 & objectGroup,std::unique_ptr<V8RuntimeAgentImpl::CallFunctionOnCallback> callback)111 void innerCallFunctionOn(
112 V8InspectorSessionImpl* session,
113 InjectedScript::Scope& scope, // NOLINT(runtime/references)
114 v8::Local<v8::Value> recv, const String16& expression,
115 Maybe<protocol::Array<protocol::Runtime::CallArgument>> optionalArguments,
116 bool silent, WrapMode wrapMode, bool userGesture, bool awaitPromise,
117 const String16& objectGroup,
118 std::unique_ptr<V8RuntimeAgentImpl::CallFunctionOnCallback> callback) {
119 V8InspectorImpl* inspector = session->inspector();
120
121 std::unique_ptr<v8::Local<v8::Value>[]> argv = nullptr;
122 int argc = 0;
123 if (optionalArguments.isJust()) {
124 protocol::Array<protocol::Runtime::CallArgument>* arguments =
125 optionalArguments.fromJust();
126 argc = static_cast<int>(arguments->size());
127 argv.reset(new v8::Local<v8::Value>[argc]);
128 for (int i = 0; i < argc; ++i) {
129 v8::Local<v8::Value> argumentValue;
130 Response response = scope.injectedScript()->resolveCallArgument(
131 (*arguments)[i].get(), &argumentValue);
132 if (!response.IsSuccess()) {
133 callback->sendFailure(response);
134 return;
135 }
136 argv[i] = argumentValue;
137 }
138 }
139
140 if (silent) scope.ignoreExceptionsAndMuteConsole();
141 if (userGesture) scope.pretendUserGesture();
142
143 // Temporarily enable allow evals for inspector.
144 scope.allowCodeGenerationFromStrings();
145
146 v8::MaybeLocal<v8::Value> maybeFunctionValue;
147 v8::Local<v8::Script> functionScript;
148 if (inspector
149 ->compileScript(scope.context(), "(" + expression + ")", String16())
150 .ToLocal(&functionScript)) {
151 v8::MicrotasksScope microtasksScope(inspector->isolate(),
152 v8::MicrotasksScope::kRunMicrotasks);
153 maybeFunctionValue = functionScript->Run(scope.context());
154 }
155 // Re-initialize after running client's code, as it could have destroyed
156 // context or session.
157 Response response = scope.initialize();
158 if (!response.IsSuccess()) {
159 callback->sendFailure(response);
160 return;
161 }
162
163 if (scope.tryCatch().HasCaught()) {
164 wrapEvaluateResultAsync(scope.injectedScript(), maybeFunctionValue,
165 scope.tryCatch(), objectGroup, WrapMode::kNoPreview,
166 callback.get());
167 return;
168 }
169
170 v8::Local<v8::Value> functionValue;
171 if (!maybeFunctionValue.ToLocal(&functionValue) ||
172 !functionValue->IsFunction()) {
173 callback->sendFailure(Response::ServerError(
174 "Given expression does not evaluate to a function"));
175 return;
176 }
177
178 v8::MaybeLocal<v8::Value> maybeResultValue;
179 {
180 v8::MicrotasksScope microtasksScope(inspector->isolate(),
181 v8::MicrotasksScope::kRunMicrotasks);
182 maybeResultValue = functionValue.As<v8::Function>()->Call(
183 scope.context(), recv, argc, argv.get());
184 }
185 // Re-initialize after running client's code, as it could have destroyed
186 // context or session.
187 response = scope.initialize();
188 if (!response.IsSuccess()) {
189 callback->sendFailure(response);
190 return;
191 }
192
193 if (!awaitPromise || scope.tryCatch().HasCaught()) {
194 wrapEvaluateResultAsync(scope.injectedScript(), maybeResultValue,
195 scope.tryCatch(), objectGroup, wrapMode,
196 callback.get());
197 return;
198 }
199
200 scope.injectedScript()->addPromiseCallback(
201 session, maybeResultValue, objectGroup, wrapMode, false /* replMode */,
202 EvaluateCallbackWrapper<V8RuntimeAgentImpl::CallFunctionOnCallback>::wrap(
203 std::move(callback)));
204 }
205
ensureContext(V8InspectorImpl * inspector,int contextGroupId,Maybe<int> executionContextId,int * contextId)206 Response ensureContext(V8InspectorImpl* inspector, int contextGroupId,
207 Maybe<int> executionContextId, int* contextId) {
208 if (executionContextId.isJust()) {
209 *contextId = executionContextId.fromJust();
210 } else {
211 v8::HandleScope handles(inspector->isolate());
212 v8::Local<v8::Context> defaultContext =
213 inspector->client()->ensureDefaultContextInGroup(contextGroupId);
214 if (defaultContext.IsEmpty())
215 return Response::ServerError("Cannot find default execution context");
216 *contextId = InspectedContext::contextId(defaultContext);
217 }
218 return Response::Success();
219 }
220
221 } // namespace
222
V8RuntimeAgentImpl(V8InspectorSessionImpl * session,protocol::FrontendChannel * FrontendChannel,protocol::DictionaryValue * state)223 V8RuntimeAgentImpl::V8RuntimeAgentImpl(
224 V8InspectorSessionImpl* session, protocol::FrontendChannel* FrontendChannel,
225 protocol::DictionaryValue* state)
226 : m_session(session),
227 m_state(state),
228 m_frontend(FrontendChannel),
229 m_inspector(session->inspector()),
230 m_enabled(false) {}
231
232 V8RuntimeAgentImpl::~V8RuntimeAgentImpl() = default;
233
evaluate(const String16 & expression,Maybe<String16> objectGroup,Maybe<bool> includeCommandLineAPI,Maybe<bool> silent,Maybe<int> executionContextId,Maybe<bool> returnByValue,Maybe<bool> generatePreview,Maybe<bool> userGesture,Maybe<bool> maybeAwaitPromise,Maybe<bool> throwOnSideEffect,Maybe<double> timeout,Maybe<bool> disableBreaks,Maybe<bool> maybeReplMode,Maybe<bool> allowUnsafeEvalBlockedByCSP,std::unique_ptr<EvaluateCallback> callback)234 void V8RuntimeAgentImpl::evaluate(
235 const String16& expression, Maybe<String16> objectGroup,
236 Maybe<bool> includeCommandLineAPI, Maybe<bool> silent,
237 Maybe<int> executionContextId, Maybe<bool> returnByValue,
238 Maybe<bool> generatePreview, Maybe<bool> userGesture,
239 Maybe<bool> maybeAwaitPromise, Maybe<bool> throwOnSideEffect,
240 Maybe<double> timeout, Maybe<bool> disableBreaks, Maybe<bool> maybeReplMode,
241 Maybe<bool> allowUnsafeEvalBlockedByCSP,
242 std::unique_ptr<EvaluateCallback> callback) {
243 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
244 "EvaluateScript");
245 int contextId = 0;
246 Response response = ensureContext(m_inspector, m_session->contextGroupId(),
247 std::move(executionContextId), &contextId);
248 if (!response.IsSuccess()) {
249 callback->sendFailure(response);
250 return;
251 }
252
253 InjectedScript::ContextScope scope(m_session, contextId);
254 response = scope.initialize();
255 if (!response.IsSuccess()) {
256 callback->sendFailure(response);
257 return;
258 }
259
260 if (silent.fromMaybe(false)) scope.ignoreExceptionsAndMuteConsole();
261 if (userGesture.fromMaybe(false)) scope.pretendUserGesture();
262
263 if (includeCommandLineAPI.fromMaybe(false)) scope.installCommandLineAPI();
264
265 const bool replMode = maybeReplMode.fromMaybe(false);
266
267 if (allowUnsafeEvalBlockedByCSP.fromMaybe(true)) {
268 // Temporarily enable allow evals for inspector.
269 scope.allowCodeGenerationFromStrings();
270 }
271 v8::MaybeLocal<v8::Value> maybeResultValue;
272 {
273 V8InspectorImpl::EvaluateScope evaluateScope(scope);
274 if (timeout.isJust()) {
275 response = evaluateScope.setTimeout(timeout.fromJust() / 1000.0);
276 if (!response.IsSuccess()) {
277 callback->sendFailure(response);
278 return;
279 }
280 }
281 v8::MicrotasksScope microtasksScope(m_inspector->isolate(),
282 v8::MicrotasksScope::kRunMicrotasks);
283 v8::debug::EvaluateGlobalMode mode =
284 v8::debug::EvaluateGlobalMode::kDefault;
285 if (throwOnSideEffect.fromMaybe(false)) {
286 mode = v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect;
287 } else if (disableBreaks.fromMaybe(false)) {
288 mode = v8::debug::EvaluateGlobalMode::kDisableBreaks;
289 }
290 const v8::Local<v8::String> source =
291 toV8String(m_inspector->isolate(), expression);
292 maybeResultValue = v8::debug::EvaluateGlobal(m_inspector->isolate(), source,
293 mode, replMode);
294 } // Run microtasks before returning result.
295
296 // Re-initialize after running client's code, as it could have destroyed
297 // context or session.
298 response = scope.initialize();
299 if (!response.IsSuccess()) {
300 callback->sendFailure(response);
301 return;
302 }
303
304 WrapMode mode = generatePreview.fromMaybe(false) ? WrapMode::kWithPreview
305 : WrapMode::kNoPreview;
306 if (returnByValue.fromMaybe(false)) mode = WrapMode::kForceValue;
307
308 // REPL mode always returns a promise that must be awaited.
309 const bool await = replMode || maybeAwaitPromise.fromMaybe(false);
310 if (!await || scope.tryCatch().HasCaught()) {
311 wrapEvaluateResultAsync(scope.injectedScript(), maybeResultValue,
312 scope.tryCatch(), objectGroup.fromMaybe(""), mode,
313 callback.get());
314 return;
315 }
316 scope.injectedScript()->addPromiseCallback(
317 m_session, maybeResultValue, objectGroup.fromMaybe(""), mode, replMode,
318 EvaluateCallbackWrapper<EvaluateCallback>::wrap(std::move(callback)));
319 }
320
awaitPromise(const String16 & promiseObjectId,Maybe<bool> returnByValue,Maybe<bool> generatePreview,std::unique_ptr<AwaitPromiseCallback> callback)321 void V8RuntimeAgentImpl::awaitPromise(
322 const String16& promiseObjectId, Maybe<bool> returnByValue,
323 Maybe<bool> generatePreview,
324 std::unique_ptr<AwaitPromiseCallback> callback) {
325 InjectedScript::ObjectScope scope(m_session, promiseObjectId);
326 Response response = scope.initialize();
327 if (!response.IsSuccess()) {
328 callback->sendFailure(response);
329 return;
330 }
331 if (!scope.object()->IsPromise()) {
332 callback->sendFailure(
333 Response::ServerError("Could not find promise with given id"));
334 return;
335 }
336 WrapMode mode = generatePreview.fromMaybe(false) ? WrapMode::kWithPreview
337 : WrapMode::kNoPreview;
338 if (returnByValue.fromMaybe(false)) mode = WrapMode::kForceValue;
339 scope.injectedScript()->addPromiseCallback(
340 m_session, scope.object(), scope.objectGroupName(), mode,
341 false /* replMode */,
342 EvaluateCallbackWrapper<AwaitPromiseCallback>::wrap(std::move(callback)));
343 }
344
callFunctionOn(const String16 & expression,Maybe<String16> objectId,Maybe<protocol::Array<protocol::Runtime::CallArgument>> optionalArguments,Maybe<bool> silent,Maybe<bool> returnByValue,Maybe<bool> generatePreview,Maybe<bool> userGesture,Maybe<bool> awaitPromise,Maybe<int> executionContextId,Maybe<String16> objectGroup,std::unique_ptr<CallFunctionOnCallback> callback)345 void V8RuntimeAgentImpl::callFunctionOn(
346 const String16& expression, Maybe<String16> objectId,
347 Maybe<protocol::Array<protocol::Runtime::CallArgument>> optionalArguments,
348 Maybe<bool> silent, Maybe<bool> returnByValue, Maybe<bool> generatePreview,
349 Maybe<bool> userGesture, Maybe<bool> awaitPromise,
350 Maybe<int> executionContextId, Maybe<String16> objectGroup,
351 std::unique_ptr<CallFunctionOnCallback> callback) {
352 if (objectId.isJust() && executionContextId.isJust()) {
353 callback->sendFailure(Response::ServerError(
354 "ObjectId must not be specified together with executionContextId"));
355 return;
356 }
357 if (!objectId.isJust() && !executionContextId.isJust()) {
358 callback->sendFailure(Response::ServerError(
359 "Either ObjectId or executionContextId must be specified"));
360 return;
361 }
362 WrapMode mode = generatePreview.fromMaybe(false) ? WrapMode::kWithPreview
363 : WrapMode::kNoPreview;
364 if (returnByValue.fromMaybe(false)) mode = WrapMode::kForceValue;
365 if (objectId.isJust()) {
366 InjectedScript::ObjectScope scope(m_session, objectId.fromJust());
367 Response response = scope.initialize();
368 if (!response.IsSuccess()) {
369 callback->sendFailure(response);
370 return;
371 }
372 innerCallFunctionOn(m_session, scope, scope.object(), expression,
373 std::move(optionalArguments), silent.fromMaybe(false),
374 mode, userGesture.fromMaybe(false),
375 awaitPromise.fromMaybe(false),
376 objectGroup.isJust() ? objectGroup.fromMaybe(String16())
377 : scope.objectGroupName(),
378 std::move(callback));
379 } else {
380 int contextId = 0;
381 Response response =
382 ensureContext(m_inspector, m_session->contextGroupId(),
383 std::move(executionContextId.fromJust()), &contextId);
384 if (!response.IsSuccess()) {
385 callback->sendFailure(response);
386 return;
387 }
388 InjectedScript::ContextScope scope(m_session, contextId);
389 response = scope.initialize();
390 if (!response.IsSuccess()) {
391 callback->sendFailure(response);
392 return;
393 }
394 innerCallFunctionOn(m_session, scope, scope.context()->Global(), expression,
395 std::move(optionalArguments), silent.fromMaybe(false),
396 mode, userGesture.fromMaybe(false),
397 awaitPromise.fromMaybe(false),
398 objectGroup.fromMaybe(""), std::move(callback));
399 }
400 }
401
getProperties(const String16 & objectId,Maybe<bool> ownProperties,Maybe<bool> accessorPropertiesOnly,Maybe<bool> generatePreview,std::unique_ptr<protocol::Array<protocol::Runtime::PropertyDescriptor>> * result,Maybe<protocol::Array<protocol::Runtime::InternalPropertyDescriptor>> * internalProperties,Maybe<protocol::Array<protocol::Runtime::PrivatePropertyDescriptor>> * privateProperties,Maybe<protocol::Runtime::ExceptionDetails> * exceptionDetails)402 Response V8RuntimeAgentImpl::getProperties(
403 const String16& objectId, Maybe<bool> ownProperties,
404 Maybe<bool> accessorPropertiesOnly, Maybe<bool> generatePreview,
405 std::unique_ptr<protocol::Array<protocol::Runtime::PropertyDescriptor>>*
406 result,
407 Maybe<protocol::Array<protocol::Runtime::InternalPropertyDescriptor>>*
408 internalProperties,
409 Maybe<protocol::Array<protocol::Runtime::PrivatePropertyDescriptor>>*
410 privateProperties,
411 Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) {
412 using protocol::Runtime::InternalPropertyDescriptor;
413 using protocol::Runtime::PrivatePropertyDescriptor;
414
415 InjectedScript::ObjectScope scope(m_session, objectId);
416 Response response = scope.initialize();
417 if (!response.IsSuccess()) return response;
418
419 scope.ignoreExceptionsAndMuteConsole();
420 v8::MicrotasksScope microtasks_scope(m_inspector->isolate(),
421 v8::MicrotasksScope::kRunMicrotasks);
422 if (!scope.object()->IsObject())
423 return Response::ServerError("Value with given id is not an object");
424
425 v8::Local<v8::Object> object = scope.object().As<v8::Object>();
426 response = scope.injectedScript()->getProperties(
427 object, scope.objectGroupName(), ownProperties.fromMaybe(false),
428 accessorPropertiesOnly.fromMaybe(false),
429 generatePreview.fromMaybe(false) ? WrapMode::kWithPreview
430 : WrapMode::kNoPreview,
431 result, exceptionDetails);
432 if (!response.IsSuccess()) return response;
433 if (exceptionDetails->isJust() || accessorPropertiesOnly.fromMaybe(false))
434 return Response::Success();
435 std::unique_ptr<protocol::Array<InternalPropertyDescriptor>>
436 internalPropertiesProtocolArray;
437 std::unique_ptr<protocol::Array<PrivatePropertyDescriptor>>
438 privatePropertiesProtocolArray;
439 response = scope.injectedScript()->getInternalAndPrivateProperties(
440 object, scope.objectGroupName(), &internalPropertiesProtocolArray,
441 &privatePropertiesProtocolArray);
442 if (!response.IsSuccess()) return response;
443 if (!internalPropertiesProtocolArray->empty())
444 *internalProperties = std::move(internalPropertiesProtocolArray);
445 if (!privatePropertiesProtocolArray->empty())
446 *privateProperties = std::move(privatePropertiesProtocolArray);
447 return Response::Success();
448 }
449
releaseObject(const String16 & objectId)450 Response V8RuntimeAgentImpl::releaseObject(const String16& objectId) {
451 InjectedScript::ObjectScope scope(m_session, objectId);
452 Response response = scope.initialize();
453 if (!response.IsSuccess()) return response;
454 scope.injectedScript()->releaseObject(objectId);
455 return Response::Success();
456 }
457
releaseObjectGroup(const String16 & objectGroup)458 Response V8RuntimeAgentImpl::releaseObjectGroup(const String16& objectGroup) {
459 m_session->releaseObjectGroup(objectGroup);
460 return Response::Success();
461 }
462
runIfWaitingForDebugger()463 Response V8RuntimeAgentImpl::runIfWaitingForDebugger() {
464 m_inspector->client()->runIfWaitingForDebugger(m_session->contextGroupId());
465 return Response::Success();
466 }
467
setCustomObjectFormatterEnabled(bool enabled)468 Response V8RuntimeAgentImpl::setCustomObjectFormatterEnabled(bool enabled) {
469 m_state->setBoolean(V8RuntimeAgentImplState::customObjectFormatterEnabled,
470 enabled);
471 if (!m_enabled) return Response::ServerError("Runtime agent is not enabled");
472 m_session->setCustomObjectFormatterEnabled(enabled);
473 return Response::Success();
474 }
475
setMaxCallStackSizeToCapture(int size)476 Response V8RuntimeAgentImpl::setMaxCallStackSizeToCapture(int size) {
477 if (size < 0) {
478 return Response::ServerError(
479 "maxCallStackSizeToCapture should be non-negative");
480 }
481 V8StackTraceImpl::maxCallStackSizeToCapture = size;
482 return Response::Success();
483 }
484
discardConsoleEntries()485 Response V8RuntimeAgentImpl::discardConsoleEntries() {
486 V8ConsoleMessageStorage* storage =
487 m_inspector->ensureConsoleMessageStorage(m_session->contextGroupId());
488 storage->clear();
489 return Response::Success();
490 }
491
compileScript(const String16 & expression,const String16 & sourceURL,bool persistScript,Maybe<int> executionContextId,Maybe<String16> * scriptId,Maybe<protocol::Runtime::ExceptionDetails> * exceptionDetails)492 Response V8RuntimeAgentImpl::compileScript(
493 const String16& expression, const String16& sourceURL, bool persistScript,
494 Maybe<int> executionContextId, Maybe<String16>* scriptId,
495 Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) {
496 if (!m_enabled) return Response::ServerError("Runtime agent is not enabled");
497
498 int contextId = 0;
499 Response response = ensureContext(m_inspector, m_session->contextGroupId(),
500 std::move(executionContextId), &contextId);
501 if (!response.IsSuccess()) return response;
502 InjectedScript::ContextScope scope(m_session, contextId);
503 response = scope.initialize();
504 if (!response.IsSuccess()) return response;
505
506 if (!persistScript) m_inspector->debugger()->muteScriptParsedEvents();
507 v8::Local<v8::Script> script;
508 bool isOk = m_inspector->compileScript(scope.context(), expression, sourceURL)
509 .ToLocal(&script);
510 if (!persistScript) m_inspector->debugger()->unmuteScriptParsedEvents();
511 if (!isOk) {
512 if (scope.tryCatch().HasCaught()) {
513 response = scope.injectedScript()->createExceptionDetails(
514 scope.tryCatch(), String16(), exceptionDetails);
515 if (!response.IsSuccess()) return response;
516 return Response::Success();
517 } else {
518 return Response::ServerError("Script compilation failed");
519 }
520 }
521
522 if (!persistScript) return Response::Success();
523
524 String16 scriptValueId =
525 String16::fromInteger(script->GetUnboundScript()->GetId());
526 std::unique_ptr<v8::Global<v8::Script>> global(
527 new v8::Global<v8::Script>(m_inspector->isolate(), script));
528 m_compiledScripts[scriptValueId] = std::move(global);
529 *scriptId = scriptValueId;
530 return Response::Success();
531 }
532
runScript(const String16 & scriptId,Maybe<int> executionContextId,Maybe<String16> objectGroup,Maybe<bool> silent,Maybe<bool> includeCommandLineAPI,Maybe<bool> returnByValue,Maybe<bool> generatePreview,Maybe<bool> awaitPromise,std::unique_ptr<RunScriptCallback> callback)533 void V8RuntimeAgentImpl::runScript(
534 const String16& scriptId, Maybe<int> executionContextId,
535 Maybe<String16> objectGroup, Maybe<bool> silent,
536 Maybe<bool> includeCommandLineAPI, Maybe<bool> returnByValue,
537 Maybe<bool> generatePreview, Maybe<bool> awaitPromise,
538 std::unique_ptr<RunScriptCallback> callback) {
539 if (!m_enabled) {
540 callback->sendFailure(
541 Response::ServerError("Runtime agent is not enabled"));
542 return;
543 }
544
545 auto it = m_compiledScripts.find(scriptId);
546 if (it == m_compiledScripts.end()) {
547 callback->sendFailure(Response::ServerError("No script with given id"));
548 return;
549 }
550
551 int contextId = 0;
552 Response response = ensureContext(m_inspector, m_session->contextGroupId(),
553 std::move(executionContextId), &contextId);
554 if (!response.IsSuccess()) {
555 callback->sendFailure(response);
556 return;
557 }
558
559 InjectedScript::ContextScope scope(m_session, contextId);
560 response = scope.initialize();
561 if (!response.IsSuccess()) {
562 callback->sendFailure(response);
563 return;
564 }
565
566 if (silent.fromMaybe(false)) scope.ignoreExceptionsAndMuteConsole();
567
568 std::unique_ptr<v8::Global<v8::Script>> scriptWrapper = std::move(it->second);
569 m_compiledScripts.erase(it);
570 v8::Local<v8::Script> script = scriptWrapper->Get(m_inspector->isolate());
571 if (script.IsEmpty()) {
572 callback->sendFailure(Response::ServerError("Script execution failed"));
573 return;
574 }
575
576 if (includeCommandLineAPI.fromMaybe(false)) scope.installCommandLineAPI();
577
578 v8::MaybeLocal<v8::Value> maybeResultValue;
579 {
580 v8::MicrotasksScope microtasksScope(m_inspector->isolate(),
581 v8::MicrotasksScope::kRunMicrotasks);
582 maybeResultValue = script->Run(scope.context());
583 }
584
585 // Re-initialize after running client's code, as it could have destroyed
586 // context or session.
587 response = scope.initialize();
588 if (!response.IsSuccess()) {
589 callback->sendFailure(response);
590 return;
591 }
592
593 WrapMode mode = generatePreview.fromMaybe(false) ? WrapMode::kWithPreview
594 : WrapMode::kNoPreview;
595 if (returnByValue.fromMaybe(false)) mode = WrapMode::kForceValue;
596 if (!awaitPromise.fromMaybe(false) || scope.tryCatch().HasCaught()) {
597 wrapEvaluateResultAsync(scope.injectedScript(), maybeResultValue,
598 scope.tryCatch(), objectGroup.fromMaybe(""), mode,
599 callback.get());
600 return;
601 }
602 scope.injectedScript()->addPromiseCallback(
603 m_session, maybeResultValue.ToLocalChecked(), objectGroup.fromMaybe(""),
604 mode, false /* replMode */,
605 EvaluateCallbackWrapper<RunScriptCallback>::wrap(std::move(callback)));
606 }
607
queryObjects(const String16 & prototypeObjectId,Maybe<String16> objectGroup,std::unique_ptr<protocol::Runtime::RemoteObject> * objects)608 Response V8RuntimeAgentImpl::queryObjects(
609 const String16& prototypeObjectId, Maybe<String16> objectGroup,
610 std::unique_ptr<protocol::Runtime::RemoteObject>* objects) {
611 InjectedScript::ObjectScope scope(m_session, prototypeObjectId);
612 Response response = scope.initialize();
613 if (!response.IsSuccess()) return response;
614 if (!scope.object()->IsObject()) {
615 return Response::ServerError("Prototype should be instance of Object");
616 }
617 v8::Local<v8::Array> resultArray = m_inspector->debugger()->queryObjects(
618 scope.context(), v8::Local<v8::Object>::Cast(scope.object()));
619 return scope.injectedScript()->wrapObject(
620 resultArray, objectGroup.fromMaybe(scope.objectGroupName()),
621 WrapMode::kNoPreview, objects);
622 }
623
globalLexicalScopeNames(Maybe<int> executionContextId,std::unique_ptr<protocol::Array<String16>> * outNames)624 Response V8RuntimeAgentImpl::globalLexicalScopeNames(
625 Maybe<int> executionContextId,
626 std::unique_ptr<protocol::Array<String16>>* outNames) {
627 int contextId = 0;
628 Response response = ensureContext(m_inspector, m_session->contextGroupId(),
629 std::move(executionContextId), &contextId);
630 if (!response.IsSuccess()) return response;
631
632 InjectedScript::ContextScope scope(m_session, contextId);
633 response = scope.initialize();
634 if (!response.IsSuccess()) return response;
635
636 v8::PersistentValueVector<v8::String> names(m_inspector->isolate());
637 v8::debug::GlobalLexicalScopeNames(scope.context(), &names);
638 *outNames = std::make_unique<protocol::Array<String16>>();
639 for (size_t i = 0; i < names.Size(); ++i) {
640 (*outNames)->emplace_back(
641 toProtocolString(m_inspector->isolate(), names.Get(i)));
642 }
643 return Response::Success();
644 }
645
getIsolateId(String16 * outIsolateId)646 Response V8RuntimeAgentImpl::getIsolateId(String16* outIsolateId) {
647 char buf[40];
648 std::snprintf(buf, sizeof(buf), "%" PRIx64, m_inspector->isolateId());
649 *outIsolateId = buf;
650 return Response::Success();
651 }
652
getHeapUsage(double * out_usedSize,double * out_totalSize)653 Response V8RuntimeAgentImpl::getHeapUsage(double* out_usedSize,
654 double* out_totalSize) {
655 v8::HeapStatistics stats;
656 m_inspector->isolate()->GetHeapStatistics(&stats);
657 *out_usedSize = stats.used_heap_size();
658 *out_totalSize = stats.total_heap_size();
659 return Response::Success();
660 }
661
terminateExecution(std::unique_ptr<TerminateExecutionCallback> callback)662 void V8RuntimeAgentImpl::terminateExecution(
663 std::unique_ptr<TerminateExecutionCallback> callback) {
664 m_inspector->debugger()->terminateExecution(std::move(callback));
665 }
666
667 namespace {
getOrCreateDictionary(protocol::DictionaryValue * dict,const String16 & key)668 protocol::DictionaryValue* getOrCreateDictionary(
669 protocol::DictionaryValue* dict, const String16& key) {
670 if (protocol::DictionaryValue* bindings = dict->getObject(key))
671 return bindings;
672 dict->setObject(key, protocol::DictionaryValue::create());
673 return dict->getObject(key);
674 }
675 } // namespace
676
addBinding(const String16 & name,Maybe<int> executionContextId,Maybe<String16> executionContextName)677 Response V8RuntimeAgentImpl::addBinding(const String16& name,
678 Maybe<int> executionContextId,
679 Maybe<String16> executionContextName) {
680 if (m_activeBindings.count(name)) return Response::Success();
681 if (executionContextId.isJust()) {
682 if (executionContextName.isJust()) {
683 return Response::InvalidParams(
684 "executionContextName is mutually exclusive with executionContextId");
685 }
686 int contextId = executionContextId.fromJust();
687 InspectedContext* context =
688 m_inspector->getContext(m_session->contextGroupId(), contextId);
689 if (!context) {
690 return Response::InvalidParams(
691 "Cannot find execution context with given executionContextId");
692 }
693 addBinding(context, name);
694 return Response::Success();
695 }
696
697 // If it's a globally exposed binding, i.e. no context name specified, use
698 // a special value for the context name.
699 String16 contextKey = V8RuntimeAgentImplState::globalBindingsKey;
700 if (executionContextName.isJust()) {
701 contextKey = executionContextName.fromJust();
702 if (contextKey == V8RuntimeAgentImplState::globalBindingsKey) {
703 return Response::InvalidParams("Invalid executionContextName");
704 }
705 }
706 // Only persist non context-specific bindings, as contextIds don't make
707 // any sense when state is restored in a different process.
708 protocol::DictionaryValue* bindings =
709 getOrCreateDictionary(m_state, V8RuntimeAgentImplState::bindings);
710 protocol::DictionaryValue* contextBindings =
711 getOrCreateDictionary(bindings, contextKey);
712 contextBindings->setBoolean(name, true);
713
714 m_inspector->forEachContext(
715 m_session->contextGroupId(),
716 [&name, &executionContextName, this](InspectedContext* context) {
717 if (executionContextName.isJust() &&
718 executionContextName.fromJust() != context->humanReadableName())
719 return;
720 addBinding(context, name);
721 });
722 return Response::Success();
723 }
724
bindingCallback(const v8::FunctionCallbackInfo<v8::Value> & info)725 void V8RuntimeAgentImpl::bindingCallback(
726 const v8::FunctionCallbackInfo<v8::Value>& info) {
727 v8::Isolate* isolate = info.GetIsolate();
728 if (info.Length() != 1 || !info[0]->IsString()) {
729 info.GetIsolate()->ThrowException(toV8String(
730 isolate, "Invalid arguments: should be exactly one string."));
731 return;
732 }
733 V8InspectorImpl* inspector =
734 static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate));
735 int contextId = InspectedContext::contextId(isolate->GetCurrentContext());
736 int contextGroupId = inspector->contextGroupId(contextId);
737
738 String16 name =
739 toProtocolString(isolate, v8::Local<v8::String>::Cast(info.Data()));
740 String16 payload =
741 toProtocolString(isolate, v8::Local<v8::String>::Cast(info[0]));
742
743 inspector->forEachSession(
744 contextGroupId,
745 [&name, &payload, &contextId](V8InspectorSessionImpl* session) {
746 session->runtimeAgent()->bindingCalled(name, payload, contextId);
747 });
748 }
749
addBinding(InspectedContext * context,const String16 & name)750 void V8RuntimeAgentImpl::addBinding(InspectedContext* context,
751 const String16& name) {
752 v8::HandleScope handles(m_inspector->isolate());
753 v8::Local<v8::Context> localContext = context->context();
754 v8::Local<v8::Object> global = localContext->Global();
755 v8::Local<v8::String> v8Name = toV8String(m_inspector->isolate(), name);
756 v8::Local<v8::Value> functionValue;
757 v8::MicrotasksScope microtasks(m_inspector->isolate(),
758 v8::MicrotasksScope::kDoNotRunMicrotasks);
759 if (v8::Function::New(localContext, bindingCallback, v8Name)
760 .ToLocal(&functionValue)) {
761 v8::Maybe<bool> success = global->Set(localContext, v8Name, functionValue);
762 USE(success);
763 m_activeBindings.insert(name);
764 }
765 }
766
removeBinding(const String16 & name)767 Response V8RuntimeAgentImpl::removeBinding(const String16& name) {
768 protocol::DictionaryValue* bindings =
769 m_state->getObject(V8RuntimeAgentImplState::bindings);
770 if (bindings) bindings->remove(name);
771 m_activeBindings.erase(name);
772 return Response::Success();
773 }
774
bindingCalled(const String16 & name,const String16 & payload,int executionContextId)775 void V8RuntimeAgentImpl::bindingCalled(const String16& name,
776 const String16& payload,
777 int executionContextId) {
778 if (!m_activeBindings.count(name)) return;
779 m_frontend.bindingCalled(name, payload, executionContextId);
780 }
781
addBindings(InspectedContext * context)782 void V8RuntimeAgentImpl::addBindings(InspectedContext* context) {
783 const String16 contextName = context->humanReadableName();
784 if (!m_enabled) return;
785 protocol::DictionaryValue* bindings =
786 m_state->getObject(V8RuntimeAgentImplState::bindings);
787 if (!bindings) return;
788 protocol::DictionaryValue* globalBindings =
789 bindings->getObject(V8RuntimeAgentImplState::globalBindingsKey);
790 if (globalBindings) {
791 for (size_t i = 0; i < globalBindings->size(); ++i)
792 addBinding(context, globalBindings->at(i).first);
793 }
794 protocol::DictionaryValue* contextBindings =
795 contextName.isEmpty() ? nullptr : bindings->getObject(contextName);
796 if (contextBindings) {
797 for (size_t i = 0; i < contextBindings->size(); ++i)
798 addBinding(context, contextBindings->at(i).first);
799 }
800 }
801
restore()802 void V8RuntimeAgentImpl::restore() {
803 if (!m_state->booleanProperty(V8RuntimeAgentImplState::runtimeEnabled, false))
804 return;
805 m_frontend.executionContextsCleared();
806 enable();
807 if (m_state->booleanProperty(
808 V8RuntimeAgentImplState::customObjectFormatterEnabled, false))
809 m_session->setCustomObjectFormatterEnabled(true);
810
811 m_inspector->forEachContext(
812 m_session->contextGroupId(),
813 [this](InspectedContext* context) { addBindings(context); });
814 }
815
enable()816 Response V8RuntimeAgentImpl::enable() {
817 if (m_enabled) return Response::Success();
818 m_inspector->client()->beginEnsureAllContextsInGroup(
819 m_session->contextGroupId());
820 m_enabled = true;
821 m_state->setBoolean(V8RuntimeAgentImplState::runtimeEnabled, true);
822 m_inspector->enableStackCapturingIfNeeded();
823 m_session->reportAllContexts(this);
824 V8ConsoleMessageStorage* storage =
825 m_inspector->ensureConsoleMessageStorage(m_session->contextGroupId());
826 for (const auto& message : storage->messages()) {
827 if (!reportMessage(message.get(), false)) break;
828 }
829 return Response::Success();
830 }
831
disable()832 Response V8RuntimeAgentImpl::disable() {
833 if (!m_enabled) return Response::Success();
834 m_enabled = false;
835 m_state->setBoolean(V8RuntimeAgentImplState::runtimeEnabled, false);
836 m_state->remove(V8RuntimeAgentImplState::bindings);
837 m_inspector->disableStackCapturingIfNeeded();
838 m_session->setCustomObjectFormatterEnabled(false);
839 reset();
840 m_inspector->client()->endEnsureAllContextsInGroup(
841 m_session->contextGroupId());
842 if (m_session->debuggerAgent() && !m_session->debuggerAgent()->enabled()) {
843 m_session->debuggerAgent()->setAsyncCallStackDepth(0);
844 }
845 return Response::Success();
846 }
847
reset()848 void V8RuntimeAgentImpl::reset() {
849 m_compiledScripts.clear();
850 if (m_enabled) {
851 int sessionId = m_session->sessionId();
852 m_inspector->forEachContext(m_session->contextGroupId(),
853 [&sessionId](InspectedContext* context) {
854 context->setReported(sessionId, false);
855 });
856 m_frontend.executionContextsCleared();
857 }
858 }
859
reportExecutionContextCreated(InspectedContext * context)860 void V8RuntimeAgentImpl::reportExecutionContextCreated(
861 InspectedContext* context) {
862 if (!m_enabled) return;
863 context->setReported(m_session->sessionId(), true);
864 std::unique_ptr<protocol::Runtime::ExecutionContextDescription> description =
865 protocol::Runtime::ExecutionContextDescription::create()
866 .setId(context->contextId())
867 .setName(context->humanReadableName())
868 .setOrigin(context->origin())
869 .build();
870 const String16& aux = context->auxData();
871 if (!aux.isEmpty()) {
872 std::vector<uint8_t> cbor;
873 v8_crdtp::json::ConvertJSONToCBOR(
874 v8_crdtp::span<uint16_t>(aux.characters16(), aux.length()), &cbor);
875 description->setAuxData(protocol::DictionaryValue::cast(
876 protocol::Value::parseBinary(cbor.data(), cbor.size())));
877 }
878 m_frontend.executionContextCreated(std::move(description));
879 }
880
reportExecutionContextDestroyed(InspectedContext * context)881 void V8RuntimeAgentImpl::reportExecutionContextDestroyed(
882 InspectedContext* context) {
883 if (m_enabled && context->isReported(m_session->sessionId())) {
884 context->setReported(m_session->sessionId(), false);
885 m_frontend.executionContextDestroyed(context->contextId());
886 }
887 }
888
inspect(std::unique_ptr<protocol::Runtime::RemoteObject> objectToInspect,std::unique_ptr<protocol::DictionaryValue> hints)889 void V8RuntimeAgentImpl::inspect(
890 std::unique_ptr<protocol::Runtime::RemoteObject> objectToInspect,
891 std::unique_ptr<protocol::DictionaryValue> hints) {
892 if (m_enabled)
893 m_frontend.inspectRequested(std::move(objectToInspect), std::move(hints));
894 }
895
messageAdded(V8ConsoleMessage * message)896 void V8RuntimeAgentImpl::messageAdded(V8ConsoleMessage* message) {
897 if (m_enabled) reportMessage(message, true);
898 }
899
reportMessage(V8ConsoleMessage * message,bool generatePreview)900 bool V8RuntimeAgentImpl::reportMessage(V8ConsoleMessage* message,
901 bool generatePreview) {
902 message->reportToFrontend(&m_frontend, m_session, generatePreview);
903 m_frontend.flush();
904 return m_inspector->hasConsoleMessageStorage(m_session->contextGroupId());
905 }
906 } // namespace v8_inspector
907