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