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 "src/inspector/injected-script.h"
34 #include "src/inspector/inspected-context.h"
35 #include "src/inspector/protocol/Protocol.h"
36 #include "src/inspector/remote-object-id.h"
37 #include "src/inspector/string-util.h"
38 #include "src/inspector/v8-console-message.h"
39 #include "src/inspector/v8-debugger-agent-impl.h"
40 #include "src/inspector/v8-debugger.h"
41 #include "src/inspector/v8-inspector-impl.h"
42 #include "src/inspector/v8-inspector-session-impl.h"
43 #include "src/inspector/v8-stack-trace-impl.h"
44 #include "src/tracing/trace-event.h"
45 
46 #include "include/v8-inspector.h"
47 
48 namespace v8_inspector {
49 
50 namespace V8RuntimeAgentImplState {
51 static const char customObjectFormatterEnabled[] =
52     "customObjectFormatterEnabled";
53 static const char runtimeEnabled[] = "runtimeEnabled";
54 };
55 
56 using protocol::Runtime::RemoteObject;
57 
58 namespace {
59 
60 template <typename Callback>
61 class ProtocolPromiseHandler {
62  public:
add(V8InspectorImpl * inspector,v8::Local<v8::Context> context,v8::MaybeLocal<v8::Value> value,const String16 & notPromiseError,int contextGroupId,int executionContextId,const String16 & objectGroup,bool returnByValue,bool generatePreview,std::unique_ptr<Callback> callback)63   static void add(V8InspectorImpl* inspector, v8::Local<v8::Context> context,
64                   v8::MaybeLocal<v8::Value> value,
65                   const String16& notPromiseError, int contextGroupId,
66                   int executionContextId, const String16& objectGroup,
67                   bool returnByValue, bool generatePreview,
68                   std::unique_ptr<Callback> callback) {
69     if (value.IsEmpty()) {
70       callback->sendFailure(Response::InternalError());
71       return;
72     }
73     if (!value.ToLocalChecked()->IsPromise()) {
74       callback->sendFailure(Response::Error(notPromiseError));
75       return;
76     }
77     v8::MicrotasksScope microtasks_scope(inspector->isolate(),
78                                          v8::MicrotasksScope::kRunMicrotasks);
79     v8::Local<v8::Promise> promise =
80         v8::Local<v8::Promise>::Cast(value.ToLocalChecked());
81     Callback* rawCallback = callback.get();
82     ProtocolPromiseHandler<Callback>* handler = new ProtocolPromiseHandler(
83         inspector, contextGroupId, executionContextId, objectGroup,
84         returnByValue, generatePreview, std::move(callback));
85     v8::Local<v8::Value> wrapper = handler->m_wrapper.Get(inspector->isolate());
86 
87     v8::Local<v8::Function> thenCallbackFunction =
88         v8::Function::New(context, thenCallback, wrapper, 0,
89                           v8::ConstructorBehavior::kThrow)
90             .ToLocalChecked();
91     if (promise->Then(context, thenCallbackFunction).IsEmpty()) {
92       rawCallback->sendFailure(Response::InternalError());
93       return;
94     }
95     v8::Local<v8::Function> catchCallbackFunction =
96         v8::Function::New(context, catchCallback, wrapper, 0,
97                           v8::ConstructorBehavior::kThrow)
98             .ToLocalChecked();
99     if (promise->Catch(context, catchCallbackFunction).IsEmpty()) {
100       rawCallback->sendFailure(Response::InternalError());
101       return;
102     }
103   }
104 
105  private:
thenCallback(const v8::FunctionCallbackInfo<v8::Value> & info)106   static void thenCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
107     ProtocolPromiseHandler<Callback>* handler =
108         static_cast<ProtocolPromiseHandler<Callback>*>(
109             info.Data().As<v8::External>()->Value());
110     DCHECK(handler);
111     v8::Local<v8::Value> value =
112         info.Length() > 0
113             ? info[0]
114             : v8::Local<v8::Value>::Cast(v8::Undefined(info.GetIsolate()));
115     std::unique_ptr<protocol::Runtime::RemoteObject> wrappedValue(
116         handler->wrapObject(value));
117     if (!wrappedValue) return;
118     handler->m_callback->sendSuccess(
119         std::move(wrappedValue), Maybe<protocol::Runtime::ExceptionDetails>());
120   }
121 
catchCallback(const v8::FunctionCallbackInfo<v8::Value> & info)122   static void catchCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
123     ProtocolPromiseHandler<Callback>* handler =
124         static_cast<ProtocolPromiseHandler<Callback>*>(
125             info.Data().As<v8::External>()->Value());
126     DCHECK(handler);
127     v8::Local<v8::Value> value =
128         info.Length() > 0
129             ? info[0]
130             : v8::Local<v8::Value>::Cast(v8::Undefined(info.GetIsolate()));
131 
132     std::unique_ptr<protocol::Runtime::RemoteObject> wrappedValue(
133         handler->wrapObject(value));
134     if (!wrappedValue) return;
135 
136     std::unique_ptr<V8StackTraceImpl> stack =
137         handler->m_inspector->debugger()->captureStackTrace(true);
138     std::unique_ptr<protocol::Runtime::ExceptionDetails> exceptionDetails =
139         protocol::Runtime::ExceptionDetails::create()
140             .setExceptionId(handler->m_inspector->nextExceptionId())
141             .setText("Uncaught (in promise)")
142             .setLineNumber(stack && !stack->isEmpty() ? stack->topLineNumber()
143                                                       : 0)
144             .setColumnNumber(
145                 stack && !stack->isEmpty() ? stack->topColumnNumber() : 0)
146             .setException(wrappedValue->clone())
147             .build();
148     if (stack)
149       exceptionDetails->setStackTrace(stack->buildInspectorObjectImpl());
150     if (stack && !stack->isEmpty())
151       exceptionDetails->setScriptId(toString16(stack->topScriptId()));
152     handler->m_callback->sendSuccess(std::move(wrappedValue),
153                                      std::move(exceptionDetails));
154   }
155 
ProtocolPromiseHandler(V8InspectorImpl * inspector,int contextGroupId,int executionContextId,const String16 & objectGroup,bool returnByValue,bool generatePreview,std::unique_ptr<Callback> callback)156   ProtocolPromiseHandler(V8InspectorImpl* inspector, int contextGroupId,
157                          int executionContextId, const String16& objectGroup,
158                          bool returnByValue, bool generatePreview,
159                          std::unique_ptr<Callback> callback)
160       : m_inspector(inspector),
161         m_contextGroupId(contextGroupId),
162         m_executionContextId(executionContextId),
163         m_objectGroup(objectGroup),
164         m_returnByValue(returnByValue),
165         m_generatePreview(generatePreview),
166         m_callback(std::move(callback)),
167         m_wrapper(inspector->isolate(),
168                   v8::External::New(inspector->isolate(), this)) {
169     m_wrapper.SetWeak(this, cleanup, v8::WeakCallbackType::kParameter);
170   }
171 
cleanup(const v8::WeakCallbackInfo<ProtocolPromiseHandler<Callback>> & data)172   static void cleanup(
173       const v8::WeakCallbackInfo<ProtocolPromiseHandler<Callback>>& data) {
174     if (!data.GetParameter()->m_wrapper.IsEmpty()) {
175       data.GetParameter()->m_wrapper.Reset();
176       data.SetSecondPassCallback(cleanup);
177     } else {
178       data.GetParameter()->m_callback->sendFailure(
179           Response::Error("Promise was collected"));
180       delete data.GetParameter();
181     }
182   }
183 
wrapObject(v8::Local<v8::Value> value)184   std::unique_ptr<protocol::Runtime::RemoteObject> wrapObject(
185       v8::Local<v8::Value> value) {
186     InjectedScript::ContextScope scope(m_inspector, m_contextGroupId,
187                                        m_executionContextId);
188     Response response = scope.initialize();
189     if (!response.isSuccess()) {
190       m_callback->sendFailure(response);
191       return nullptr;
192     }
193     std::unique_ptr<protocol::Runtime::RemoteObject> wrappedValue;
194     response = scope.injectedScript()->wrapObject(
195         value, m_objectGroup, m_returnByValue, m_generatePreview,
196         &wrappedValue);
197     if (!response.isSuccess()) {
198       m_callback->sendFailure(response);
199       return nullptr;
200     }
201     return wrappedValue;
202   }
203 
204   V8InspectorImpl* m_inspector;
205   int m_contextGroupId;
206   int m_executionContextId;
207   String16 m_objectGroup;
208   bool m_returnByValue;
209   bool m_generatePreview;
210   std::unique_ptr<Callback> m_callback;
211   v8::Global<v8::External> m_wrapper;
212 };
213 
214 template <typename Callback>
wrapEvaluateResultAsync(InjectedScript * injectedScript,v8::MaybeLocal<v8::Value> maybeResultValue,const v8::TryCatch & tryCatch,const String16 & objectGroup,bool returnByValue,bool generatePreview,Callback * callback)215 bool wrapEvaluateResultAsync(InjectedScript* injectedScript,
216                              v8::MaybeLocal<v8::Value> maybeResultValue,
217                              const v8::TryCatch& tryCatch,
218                              const String16& objectGroup, bool returnByValue,
219                              bool generatePreview, Callback* callback) {
220   std::unique_ptr<RemoteObject> result;
221   Maybe<protocol::Runtime::ExceptionDetails> exceptionDetails;
222 
223   Response response = injectedScript->wrapEvaluateResult(
224       maybeResultValue, tryCatch, objectGroup, returnByValue, generatePreview,
225       &result, &exceptionDetails);
226   if (response.isSuccess()) {
227     callback->sendSuccess(std::move(result), std::move(exceptionDetails));
228     return true;
229   }
230   callback->sendFailure(response);
231   return false;
232 }
233 
ensureContext(V8InspectorImpl * inspector,int contextGroupId,Maybe<int> executionContextId,int * contextId)234 Response ensureContext(V8InspectorImpl* inspector, int contextGroupId,
235                        Maybe<int> executionContextId, int* contextId) {
236   if (executionContextId.isJust()) {
237     *contextId = executionContextId.fromJust();
238   } else {
239     v8::HandleScope handles(inspector->isolate());
240     v8::Local<v8::Context> defaultContext =
241         inspector->client()->ensureDefaultContextInGroup(contextGroupId);
242     if (defaultContext.IsEmpty())
243       return Response::Error("Cannot find default execution context");
244     *contextId = V8Debugger::contextId(defaultContext);
245   }
246   return Response::OK();
247 }
248 
249 }  // namespace
250 
V8RuntimeAgentImpl(V8InspectorSessionImpl * session,protocol::FrontendChannel * FrontendChannel,protocol::DictionaryValue * state)251 V8RuntimeAgentImpl::V8RuntimeAgentImpl(
252     V8InspectorSessionImpl* session, protocol::FrontendChannel* FrontendChannel,
253     protocol::DictionaryValue* state)
254     : m_session(session),
255       m_state(state),
256       m_frontend(FrontendChannel),
257       m_inspector(session->inspector()),
258       m_enabled(false) {}
259 
~V8RuntimeAgentImpl()260 V8RuntimeAgentImpl::~V8RuntimeAgentImpl() {}
261 
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> awaitPromise,std::unique_ptr<EvaluateCallback> callback)262 void V8RuntimeAgentImpl::evaluate(
263     const String16& expression, Maybe<String16> objectGroup,
264     Maybe<bool> includeCommandLineAPI, Maybe<bool> silent,
265     Maybe<int> executionContextId, Maybe<bool> returnByValue,
266     Maybe<bool> generatePreview, Maybe<bool> userGesture,
267     Maybe<bool> awaitPromise, std::unique_ptr<EvaluateCallback> callback) {
268   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
269                "EvaluateScript");
270   int contextId = 0;
271   Response response = ensureContext(m_inspector, m_session->contextGroupId(),
272                                     std::move(executionContextId), &contextId);
273   if (!response.isSuccess()) {
274     callback->sendFailure(response);
275     return;
276   }
277 
278   InjectedScript::ContextScope scope(m_inspector, m_session->contextGroupId(),
279                                      contextId);
280   response = scope.initialize();
281   if (!response.isSuccess()) {
282     callback->sendFailure(response);
283     return;
284   }
285 
286   if (silent.fromMaybe(false)) scope.ignoreExceptionsAndMuteConsole();
287   if (userGesture.fromMaybe(false)) scope.pretendUserGesture();
288 
289   if (includeCommandLineAPI.fromMaybe(false)) scope.installCommandLineAPI();
290 
291   bool evalIsDisabled = !scope.context()->IsCodeGenerationFromStringsAllowed();
292   // Temporarily enable allow evals for inspector.
293   if (evalIsDisabled) scope.context()->AllowCodeGenerationFromStrings(true);
294 
295   v8::MaybeLocal<v8::Value> maybeResultValue;
296   v8::Local<v8::Script> script = m_inspector->compileScript(
297       scope.context(), toV8String(m_inspector->isolate(), expression),
298       String16(), false);
299   if (!script.IsEmpty())
300     maybeResultValue = m_inspector->runCompiledScript(scope.context(), script);
301 
302   if (evalIsDisabled) scope.context()->AllowCodeGenerationFromStrings(false);
303 
304   // Re-initialize after running client's code, as it could have destroyed
305   // context or session.
306   response = scope.initialize();
307   if (!response.isSuccess()) {
308     callback->sendFailure(response);
309     return;
310   }
311 
312   if (!awaitPromise.fromMaybe(false) || scope.tryCatch().HasCaught()) {
313     wrapEvaluateResultAsync(scope.injectedScript(), maybeResultValue,
314                             scope.tryCatch(), objectGroup.fromMaybe(""),
315                             returnByValue.fromMaybe(false),
316                             generatePreview.fromMaybe(false), callback.get());
317     return;
318   }
319   ProtocolPromiseHandler<EvaluateCallback>::add(
320       m_inspector, scope.context(), maybeResultValue,
321       "Result of the evaluation is not a promise", m_session->contextGroupId(),
322       scope.injectedScript()->context()->contextId(), objectGroup.fromMaybe(""),
323       returnByValue.fromMaybe(false), generatePreview.fromMaybe(false),
324       std::move(callback));
325 }
326 
awaitPromise(const String16 & promiseObjectId,Maybe<bool> returnByValue,Maybe<bool> generatePreview,std::unique_ptr<AwaitPromiseCallback> callback)327 void V8RuntimeAgentImpl::awaitPromise(
328     const String16& promiseObjectId, Maybe<bool> returnByValue,
329     Maybe<bool> generatePreview,
330     std::unique_ptr<AwaitPromiseCallback> callback) {
331   InjectedScript::ObjectScope scope(m_inspector, m_session->contextGroupId(),
332                                     promiseObjectId);
333   Response response = scope.initialize();
334   if (!response.isSuccess()) {
335     callback->sendFailure(response);
336     return;
337   }
338   ProtocolPromiseHandler<AwaitPromiseCallback>::add(
339       m_inspector, scope.context(), scope.object(),
340       "Could not find promise with given id", m_session->contextGroupId(),
341       scope.injectedScript()->context()->contextId(), scope.objectGroupName(),
342       returnByValue.fromMaybe(false), generatePreview.fromMaybe(false),
343       std::move(callback));
344 }
345 
callFunctionOn(const String16 & objectId,const String16 & expression,Maybe<protocol::Array<protocol::Runtime::CallArgument>> optionalArguments,Maybe<bool> silent,Maybe<bool> returnByValue,Maybe<bool> generatePreview,Maybe<bool> userGesture,Maybe<bool> awaitPromise,std::unique_ptr<CallFunctionOnCallback> callback)346 void V8RuntimeAgentImpl::callFunctionOn(
347     const String16& objectId, const String16& expression,
348     Maybe<protocol::Array<protocol::Runtime::CallArgument>> optionalArguments,
349     Maybe<bool> silent, Maybe<bool> returnByValue, Maybe<bool> generatePreview,
350     Maybe<bool> userGesture, Maybe<bool> awaitPromise,
351     std::unique_ptr<CallFunctionOnCallback> callback) {
352   InjectedScript::ObjectScope scope(m_inspector, m_session->contextGroupId(),
353                                     objectId);
354   Response response = scope.initialize();
355   if (!response.isSuccess()) {
356     callback->sendFailure(response);
357     return;
358   }
359 
360   std::unique_ptr<v8::Local<v8::Value>[]> argv = nullptr;
361   int argc = 0;
362   if (optionalArguments.isJust()) {
363     protocol::Array<protocol::Runtime::CallArgument>* arguments =
364         optionalArguments.fromJust();
365     argc = static_cast<int>(arguments->length());
366     argv.reset(new v8::Local<v8::Value>[argc]);
367     for (int i = 0; i < argc; ++i) {
368       v8::Local<v8::Value> argumentValue;
369       response = scope.injectedScript()->resolveCallArgument(arguments->get(i),
370                                                              &argumentValue);
371       if (!response.isSuccess()) {
372         callback->sendFailure(response);
373         return;
374       }
375       argv[i] = argumentValue;
376     }
377   }
378 
379   if (silent.fromMaybe(false)) scope.ignoreExceptionsAndMuteConsole();
380   if (userGesture.fromMaybe(false)) scope.pretendUserGesture();
381 
382   v8::MaybeLocal<v8::Value> maybeFunctionValue =
383       m_inspector->compileAndRunInternalScript(
384           scope.context(),
385           toV8String(m_inspector->isolate(), "(" + expression + ")"));
386   // Re-initialize after running client's code, as it could have destroyed
387   // context or session.
388   response = scope.initialize();
389   if (!response.isSuccess()) {
390     callback->sendFailure(response);
391     return;
392   }
393 
394   if (scope.tryCatch().HasCaught()) {
395     wrapEvaluateResultAsync(scope.injectedScript(), maybeFunctionValue,
396                             scope.tryCatch(), scope.objectGroupName(), false,
397                             false, callback.get());
398     return;
399   }
400 
401   v8::Local<v8::Value> functionValue;
402   if (!maybeFunctionValue.ToLocal(&functionValue) ||
403       !functionValue->IsFunction()) {
404     callback->sendFailure(
405         Response::Error("Given expression does not evaluate to a function"));
406     return;
407   }
408 
409   v8::MaybeLocal<v8::Value> maybeResultValue = m_inspector->callFunction(
410       functionValue.As<v8::Function>(), scope.context(), scope.object(), argc,
411       argv.get());
412   // Re-initialize after running client's code, as it could have destroyed
413   // context or session.
414   response = scope.initialize();
415   if (!response.isSuccess()) {
416     callback->sendFailure(response);
417     return;
418   }
419 
420   if (!awaitPromise.fromMaybe(false) || scope.tryCatch().HasCaught()) {
421     wrapEvaluateResultAsync(scope.injectedScript(), maybeResultValue,
422                             scope.tryCatch(), scope.objectGroupName(),
423                             returnByValue.fromMaybe(false),
424                             generatePreview.fromMaybe(false), callback.get());
425     return;
426   }
427 
428   ProtocolPromiseHandler<CallFunctionOnCallback>::add(
429       m_inspector, scope.context(), maybeResultValue,
430       "Result of the function call is not a promise",
431       m_session->contextGroupId(),
432       scope.injectedScript()->context()->contextId(), scope.objectGroupName(),
433       returnByValue.fromMaybe(false), generatePreview.fromMaybe(false),
434       std::move(callback));
435 }
436 
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::Runtime::ExceptionDetails> * exceptionDetails)437 Response V8RuntimeAgentImpl::getProperties(
438     const String16& objectId, Maybe<bool> ownProperties,
439     Maybe<bool> accessorPropertiesOnly, Maybe<bool> generatePreview,
440     std::unique_ptr<protocol::Array<protocol::Runtime::PropertyDescriptor>>*
441         result,
442     Maybe<protocol::Array<protocol::Runtime::InternalPropertyDescriptor>>*
443         internalProperties,
444     Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) {
445   using protocol::Runtime::InternalPropertyDescriptor;
446 
447   InjectedScript::ObjectScope scope(m_inspector, m_session->contextGroupId(),
448                                     objectId);
449   Response response = scope.initialize();
450   if (!response.isSuccess()) return response;
451 
452   scope.ignoreExceptionsAndMuteConsole();
453   if (!scope.object()->IsObject())
454     return Response::Error("Value with given id is not an object");
455 
456   v8::Local<v8::Object> object = scope.object().As<v8::Object>();
457   response = scope.injectedScript()->getProperties(
458       object, scope.objectGroupName(), ownProperties.fromMaybe(false),
459       accessorPropertiesOnly.fromMaybe(false), generatePreview.fromMaybe(false),
460       result, exceptionDetails);
461   if (!response.isSuccess()) return response;
462   if (exceptionDetails->isJust() || accessorPropertiesOnly.fromMaybe(false))
463     return Response::OK();
464   v8::Local<v8::Array> propertiesArray;
465   if (!m_inspector->debugger()
466            ->internalProperties(scope.context(), scope.object())
467            .ToLocal(&propertiesArray)) {
468     return Response::InternalError();
469   }
470   std::unique_ptr<protocol::Array<InternalPropertyDescriptor>>
471       propertiesProtocolArray =
472           protocol::Array<InternalPropertyDescriptor>::create();
473   for (uint32_t i = 0; i < propertiesArray->Length(); i += 2) {
474     v8::Local<v8::Value> name;
475     if (!propertiesArray->Get(scope.context(), i).ToLocal(&name) ||
476         !name->IsString()) {
477       return Response::InternalError();
478     }
479     v8::Local<v8::Value> value;
480     if (!propertiesArray->Get(scope.context(), i + 1).ToLocal(&value))
481       return Response::InternalError();
482     std::unique_ptr<RemoteObject> wrappedValue;
483     protocol::Response response = scope.injectedScript()->wrapObject(
484         value, scope.objectGroupName(), false, false, &wrappedValue);
485     if (!response.isSuccess()) return response;
486     propertiesProtocolArray->addItem(
487         InternalPropertyDescriptor::create()
488             .setName(toProtocolString(name.As<v8::String>()))
489             .setValue(std::move(wrappedValue))
490             .build());
491   }
492   if (propertiesProtocolArray->length())
493     *internalProperties = std::move(propertiesProtocolArray);
494   return Response::OK();
495 }
496 
releaseObject(const String16 & objectId)497 Response V8RuntimeAgentImpl::releaseObject(const String16& objectId) {
498   InjectedScript::ObjectScope scope(m_inspector, m_session->contextGroupId(),
499                                     objectId);
500   Response response = scope.initialize();
501   if (!response.isSuccess()) return response;
502   scope.injectedScript()->releaseObject(objectId);
503   return Response::OK();
504 }
505 
releaseObjectGroup(const String16 & objectGroup)506 Response V8RuntimeAgentImpl::releaseObjectGroup(const String16& objectGroup) {
507   m_session->releaseObjectGroup(objectGroup);
508   return Response::OK();
509 }
510 
runIfWaitingForDebugger()511 Response V8RuntimeAgentImpl::runIfWaitingForDebugger() {
512   m_inspector->client()->runIfWaitingForDebugger(m_session->contextGroupId());
513   return Response::OK();
514 }
515 
setCustomObjectFormatterEnabled(bool enabled)516 Response V8RuntimeAgentImpl::setCustomObjectFormatterEnabled(bool enabled) {
517   m_state->setBoolean(V8RuntimeAgentImplState::customObjectFormatterEnabled,
518                       enabled);
519   m_session->setCustomObjectFormatterEnabled(enabled);
520   return Response::OK();
521 }
522 
discardConsoleEntries()523 Response V8RuntimeAgentImpl::discardConsoleEntries() {
524   V8ConsoleMessageStorage* storage =
525       m_inspector->ensureConsoleMessageStorage(m_session->contextGroupId());
526   storage->clear();
527   return Response::OK();
528 }
529 
compileScript(const String16 & expression,const String16 & sourceURL,bool persistScript,Maybe<int> executionContextId,Maybe<String16> * scriptId,Maybe<protocol::Runtime::ExceptionDetails> * exceptionDetails)530 Response V8RuntimeAgentImpl::compileScript(
531     const String16& expression, const String16& sourceURL, bool persistScript,
532     Maybe<int> executionContextId, Maybe<String16>* scriptId,
533     Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) {
534   if (!m_enabled) return Response::Error("Runtime agent is not enabled");
535 
536   int contextId = 0;
537   Response response = ensureContext(m_inspector, m_session->contextGroupId(),
538                                     std::move(executionContextId), &contextId);
539   if (!response.isSuccess()) return response;
540   InjectedScript::ContextScope scope(m_inspector, m_session->contextGroupId(),
541                                      contextId);
542   response = scope.initialize();
543   if (!response.isSuccess()) return response;
544 
545   if (!persistScript) m_inspector->debugger()->muteScriptParsedEvents();
546   v8::Local<v8::Script> script = m_inspector->compileScript(
547       scope.context(), toV8String(m_inspector->isolate(), expression),
548       sourceURL, false);
549   if (!persistScript) m_inspector->debugger()->unmuteScriptParsedEvents();
550   if (script.IsEmpty()) {
551     if (scope.tryCatch().HasCaught()) {
552       response = scope.injectedScript()->createExceptionDetails(
553           scope.tryCatch(), String16(), false, exceptionDetails);
554       if (!response.isSuccess()) return response;
555       return Response::OK();
556     } else {
557       return Response::Error("Script compilation failed");
558     }
559   }
560 
561   if (!persistScript) return Response::OK();
562 
563   String16 scriptValueId =
564       String16::fromInteger(script->GetUnboundScript()->GetId());
565   std::unique_ptr<v8::Global<v8::Script>> global(
566       new v8::Global<v8::Script>(m_inspector->isolate(), script));
567   m_compiledScripts[scriptValueId] = std::move(global);
568   *scriptId = scriptValueId;
569   return Response::OK();
570 }
571 
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)572 void V8RuntimeAgentImpl::runScript(
573     const String16& scriptId, Maybe<int> executionContextId,
574     Maybe<String16> objectGroup, Maybe<bool> silent,
575     Maybe<bool> includeCommandLineAPI, Maybe<bool> returnByValue,
576     Maybe<bool> generatePreview, Maybe<bool> awaitPromise,
577     std::unique_ptr<RunScriptCallback> callback) {
578   if (!m_enabled) {
579     callback->sendFailure(Response::Error("Runtime agent is not enabled"));
580     return;
581   }
582 
583   auto it = m_compiledScripts.find(scriptId);
584   if (it == m_compiledScripts.end()) {
585     callback->sendFailure(Response::Error("No script with given id"));
586     return;
587   }
588 
589   int contextId = 0;
590   Response response = ensureContext(m_inspector, m_session->contextGroupId(),
591                                     std::move(executionContextId), &contextId);
592   if (!response.isSuccess()) {
593     callback->sendFailure(response);
594     return;
595   }
596 
597   InjectedScript::ContextScope scope(m_inspector, m_session->contextGroupId(),
598                                      contextId);
599   response = scope.initialize();
600   if (!response.isSuccess()) {
601     callback->sendFailure(response);
602     return;
603   }
604 
605   if (silent.fromMaybe(false)) scope.ignoreExceptionsAndMuteConsole();
606 
607   std::unique_ptr<v8::Global<v8::Script>> scriptWrapper = std::move(it->second);
608   m_compiledScripts.erase(it);
609   v8::Local<v8::Script> script = scriptWrapper->Get(m_inspector->isolate());
610   if (script.IsEmpty()) {
611     callback->sendFailure(Response::Error("Script execution failed"));
612     return;
613   }
614 
615   if (includeCommandLineAPI.fromMaybe(false)) scope.installCommandLineAPI();
616 
617   v8::MaybeLocal<v8::Value> maybeResultValue =
618       m_inspector->runCompiledScript(scope.context(), script);
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   if (!awaitPromise.fromMaybe(false) || scope.tryCatch().HasCaught()) {
629     wrapEvaluateResultAsync(scope.injectedScript(), maybeResultValue,
630                             scope.tryCatch(), objectGroup.fromMaybe(""),
631                             returnByValue.fromMaybe(false),
632                             generatePreview.fromMaybe(false), callback.get());
633     return;
634   }
635   ProtocolPromiseHandler<RunScriptCallback>::add(
636       m_inspector, scope.context(), maybeResultValue.ToLocalChecked(),
637       "Result of the script execution is not a promise",
638       m_session->contextGroupId(),
639       scope.injectedScript()->context()->contextId(), objectGroup.fromMaybe(""),
640       returnByValue.fromMaybe(false), generatePreview.fromMaybe(false),
641       std::move(callback));
642 }
643 
restore()644 void V8RuntimeAgentImpl::restore() {
645   if (!m_state->booleanProperty(V8RuntimeAgentImplState::runtimeEnabled, false))
646     return;
647   m_frontend.executionContextsCleared();
648   enable();
649   if (m_state->booleanProperty(
650           V8RuntimeAgentImplState::customObjectFormatterEnabled, false))
651     m_session->setCustomObjectFormatterEnabled(true);
652 }
653 
enable()654 Response V8RuntimeAgentImpl::enable() {
655   if (m_enabled) return Response::OK();
656   m_inspector->client()->beginEnsureAllContextsInGroup(
657       m_session->contextGroupId());
658   m_enabled = true;
659   m_state->setBoolean(V8RuntimeAgentImplState::runtimeEnabled, true);
660   m_inspector->enableStackCapturingIfNeeded();
661   m_session->reportAllContexts(this);
662   V8ConsoleMessageStorage* storage =
663       m_inspector->ensureConsoleMessageStorage(m_session->contextGroupId());
664   for (const auto& message : storage->messages()) {
665     if (!reportMessage(message.get(), false)) break;
666   }
667   return Response::OK();
668 }
669 
disable()670 Response V8RuntimeAgentImpl::disable() {
671   if (!m_enabled) return Response::OK();
672   m_enabled = false;
673   m_state->setBoolean(V8RuntimeAgentImplState::runtimeEnabled, false);
674   m_inspector->disableStackCapturingIfNeeded();
675   m_session->discardInjectedScripts();
676   reset();
677   m_inspector->client()->endEnsureAllContextsInGroup(
678       m_session->contextGroupId());
679   return Response::OK();
680 }
681 
reset()682 void V8RuntimeAgentImpl::reset() {
683   m_compiledScripts.clear();
684   if (m_enabled) {
685     if (const V8InspectorImpl::ContextByIdMap* contexts =
686             m_inspector->contextGroup(m_session->contextGroupId())) {
687       for (auto& idContext : *contexts) idContext.second->setReported(false);
688     }
689     m_frontend.executionContextsCleared();
690   }
691 }
692 
reportExecutionContextCreated(InspectedContext * context)693 void V8RuntimeAgentImpl::reportExecutionContextCreated(
694     InspectedContext* context) {
695   if (!m_enabled) return;
696   context->setReported(true);
697   std::unique_ptr<protocol::Runtime::ExecutionContextDescription> description =
698       protocol::Runtime::ExecutionContextDescription::create()
699           .setId(context->contextId())
700           .setName(context->humanReadableName())
701           .setOrigin(context->origin())
702           .build();
703   if (!context->auxData().isEmpty())
704     description->setAuxData(protocol::DictionaryValue::cast(
705         protocol::parseJSON(context->auxData())));
706   m_frontend.executionContextCreated(std::move(description));
707 }
708 
reportExecutionContextDestroyed(InspectedContext * context)709 void V8RuntimeAgentImpl::reportExecutionContextDestroyed(
710     InspectedContext* context) {
711   if (m_enabled && context->isReported()) {
712     context->setReported(false);
713     m_frontend.executionContextDestroyed(context->contextId());
714   }
715 }
716 
inspect(std::unique_ptr<protocol::Runtime::RemoteObject> objectToInspect,std::unique_ptr<protocol::DictionaryValue> hints)717 void V8RuntimeAgentImpl::inspect(
718     std::unique_ptr<protocol::Runtime::RemoteObject> objectToInspect,
719     std::unique_ptr<protocol::DictionaryValue> hints) {
720   if (m_enabled)
721     m_frontend.inspectRequested(std::move(objectToInspect), std::move(hints));
722 }
723 
messageAdded(V8ConsoleMessage * message)724 void V8RuntimeAgentImpl::messageAdded(V8ConsoleMessage* message) {
725   if (m_enabled) reportMessage(message, true);
726 }
727 
reportMessage(V8ConsoleMessage * message,bool generatePreview)728 bool V8RuntimeAgentImpl::reportMessage(V8ConsoleMessage* message,
729                                        bool generatePreview) {
730   message->reportToFrontend(&m_frontend, m_session, generatePreview);
731   m_frontend.flush();
732   return m_inspector->hasConsoleMessageStorage(m_session->contextGroupId());
733 }
734 
735 }  // namespace v8_inspector
736