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