• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/inspector/v8-console.h"
6 
7 #include "src/base/macros.h"
8 #include "src/inspector/injected-script.h"
9 #include "src/inspector/inspected-context.h"
10 #include "src/inspector/string-util.h"
11 #include "src/inspector/v8-console-message.h"
12 #include "src/inspector/v8-debugger-agent-impl.h"
13 #include "src/inspector/v8-inspector-impl.h"
14 #include "src/inspector/v8-inspector-session-impl.h"
15 #include "src/inspector/v8-profiler-agent-impl.h"
16 #include "src/inspector/v8-runtime-agent-impl.h"
17 #include "src/inspector/v8-stack-trace-impl.h"
18 #include "src/inspector/v8-value-copier.h"
19 
20 #include "include/v8-inspector.h"
21 
22 namespace v8_inspector {
23 
24 namespace {
25 
inspectedContextPrivateKey(v8::Isolate * isolate)26 v8::Local<v8::Private> inspectedContextPrivateKey(v8::Isolate* isolate) {
27   return v8::Private::ForApi(
28       isolate, toV8StringInternalized(isolate, "V8Console#InspectedContext"));
29 }
30 
31 class ConsoleHelper {
32  public:
ConsoleHelper(const v8::FunctionCallbackInfo<v8::Value> & info)33   explicit ConsoleHelper(const v8::FunctionCallbackInfo<v8::Value>& info)
34       : m_info(info),
35         m_isolate(info.GetIsolate()),
36         m_context(info.GetIsolate()->GetCurrentContext()),
37         m_inspectedContext(nullptr),
38         m_inspectorClient(nullptr) {}
39 
ensureConsole()40   v8::Local<v8::Object> ensureConsole() {
41     if (m_console.IsEmpty()) {
42       DCHECK(!m_info.Data().IsEmpty());
43       DCHECK(!m_info.Data()->IsUndefined());
44       m_console = m_info.Data().As<v8::Object>();
45     }
46     return m_console;
47   }
48 
ensureInspectedContext()49   InspectedContext* ensureInspectedContext() {
50     if (m_inspectedContext) return m_inspectedContext;
51     v8::Local<v8::Object> console = ensureConsole();
52 
53     v8::Local<v8::Private> key = inspectedContextPrivateKey(m_isolate);
54     v8::Local<v8::Value> inspectedContextValue;
55     if (!console->GetPrivate(m_context, key).ToLocal(&inspectedContextValue))
56       return nullptr;
57     DCHECK(inspectedContextValue->IsExternal());
58     m_inspectedContext = static_cast<InspectedContext*>(
59         inspectedContextValue.As<v8::External>()->Value());
60     return m_inspectedContext;
61   }
62 
ensureDebuggerClient()63   V8InspectorClient* ensureDebuggerClient() {
64     if (m_inspectorClient) return m_inspectorClient;
65     InspectedContext* inspectedContext = ensureInspectedContext();
66     if (!inspectedContext) return nullptr;
67     m_inspectorClient = inspectedContext->inspector()->client();
68     return m_inspectorClient;
69   }
70 
reportCall(ConsoleAPIType type)71   void reportCall(ConsoleAPIType type) {
72     if (!m_info.Length()) return;
73     std::vector<v8::Local<v8::Value>> arguments;
74     for (int i = 0; i < m_info.Length(); ++i) arguments.push_back(m_info[i]);
75     reportCall(type, arguments);
76   }
77 
reportCallWithDefaultArgument(ConsoleAPIType type,const String16 & message)78   void reportCallWithDefaultArgument(ConsoleAPIType type,
79                                      const String16& message) {
80     std::vector<v8::Local<v8::Value>> arguments;
81     for (int i = 0; i < m_info.Length(); ++i) arguments.push_back(m_info[i]);
82     if (!m_info.Length()) arguments.push_back(toV8String(m_isolate, message));
83     reportCall(type, arguments);
84   }
85 
reportCallWithArgument(ConsoleAPIType type,const String16 & message)86   void reportCallWithArgument(ConsoleAPIType type, const String16& message) {
87     std::vector<v8::Local<v8::Value>> arguments(1,
88                                                 toV8String(m_isolate, message));
89     reportCall(type, arguments);
90   }
91 
reportCall(ConsoleAPIType type,const std::vector<v8::Local<v8::Value>> & arguments)92   void reportCall(ConsoleAPIType type,
93                   const std::vector<v8::Local<v8::Value>>& arguments) {
94     InspectedContext* inspectedContext = ensureInspectedContext();
95     if (!inspectedContext) return;
96     int contextGroupId = inspectedContext->contextGroupId();
97     V8InspectorImpl* inspector = inspectedContext->inspector();
98     std::unique_ptr<V8ConsoleMessage> message =
99         V8ConsoleMessage::createForConsoleAPI(
100             inspector->client()->currentTimeMS(), type, arguments,
101             inspector->debugger()->captureStackTrace(false), inspectedContext);
102     inspector->ensureConsoleMessageStorage(contextGroupId)
103         ->addMessage(std::move(message));
104   }
105 
reportDeprecatedCall(const char * id,const String16 & message)106   void reportDeprecatedCall(const char* id, const String16& message) {
107     if (checkAndSetPrivateFlagOnConsole(id, false)) return;
108     std::vector<v8::Local<v8::Value>> arguments(1,
109                                                 toV8String(m_isolate, message));
110     reportCall(ConsoleAPIType::kWarning, arguments);
111   }
112 
firstArgToBoolean(bool defaultValue)113   bool firstArgToBoolean(bool defaultValue) {
114     if (m_info.Length() < 1) return defaultValue;
115     if (m_info[0]->IsBoolean()) return m_info[0].As<v8::Boolean>()->Value();
116     return m_info[0]->BooleanValue(m_context).FromMaybe(defaultValue);
117   }
118 
firstArgToString(const String16 & defaultValue)119   String16 firstArgToString(const String16& defaultValue) {
120     if (m_info.Length() < 1) return defaultValue;
121     v8::Local<v8::String> titleValue;
122     if (m_info[0]->IsObject()) {
123       if (!m_info[0].As<v8::Object>()->ObjectProtoToString(m_context).ToLocal(
124               &titleValue))
125         return defaultValue;
126     } else {
127       if (!m_info[0]->ToString(m_context).ToLocal(&titleValue))
128         return defaultValue;
129     }
130     return toProtocolString(titleValue);
131   }
132 
firstArgAsObject()133   v8::MaybeLocal<v8::Object> firstArgAsObject() {
134     if (m_info.Length() < 1 || !m_info[0]->IsObject())
135       return v8::MaybeLocal<v8::Object>();
136     return m_info[0].As<v8::Object>();
137   }
138 
firstArgAsFunction()139   v8::MaybeLocal<v8::Function> firstArgAsFunction() {
140     if (m_info.Length() < 1 || !m_info[0]->IsFunction())
141       return v8::MaybeLocal<v8::Function>();
142     v8::Local<v8::Function> func = m_info[0].As<v8::Function>();
143     while (func->GetBoundFunction()->IsFunction())
144       func = func->GetBoundFunction().As<v8::Function>();
145     return func;
146   }
147 
privateMap(const char * name)148   v8::MaybeLocal<v8::Map> privateMap(const char* name) {
149     v8::Local<v8::Object> console = ensureConsole();
150     v8::Local<v8::Private> privateKey =
151         v8::Private::ForApi(m_isolate, toV8StringInternalized(m_isolate, name));
152     v8::Local<v8::Value> mapValue;
153     if (!console->GetPrivate(m_context, privateKey).ToLocal(&mapValue))
154       return v8::MaybeLocal<v8::Map>();
155     if (mapValue->IsUndefined()) {
156       v8::Local<v8::Map> map = v8::Map::New(m_isolate);
157       if (!console->SetPrivate(m_context, privateKey, map).FromMaybe(false))
158         return v8::MaybeLocal<v8::Map>();
159       return map;
160     }
161     return mapValue->IsMap() ? mapValue.As<v8::Map>()
162                              : v8::MaybeLocal<v8::Map>();
163   }
164 
getIntFromMap(v8::Local<v8::Map> map,const String16 & key,int32_t defaultValue)165   int32_t getIntFromMap(v8::Local<v8::Map> map, const String16& key,
166                         int32_t defaultValue) {
167     v8::Local<v8::String> v8Key = toV8String(m_isolate, key);
168     if (!map->Has(m_context, v8Key).FromMaybe(false)) return defaultValue;
169     v8::Local<v8::Value> intValue;
170     if (!map->Get(m_context, v8Key).ToLocal(&intValue)) return defaultValue;
171     return static_cast<int32_t>(intValue.As<v8::Integer>()->Value());
172   }
173 
setIntOnMap(v8::Local<v8::Map> map,const String16 & key,int32_t value)174   void setIntOnMap(v8::Local<v8::Map> map, const String16& key, int32_t value) {
175     v8::Local<v8::String> v8Key = toV8String(m_isolate, key);
176     if (!map->Set(m_context, v8Key, v8::Integer::New(m_isolate, value))
177              .ToLocal(&map))
178       return;
179   }
180 
getDoubleFromMap(v8::Local<v8::Map> map,const String16 & key,double defaultValue)181   double getDoubleFromMap(v8::Local<v8::Map> map, const String16& key,
182                           double defaultValue) {
183     v8::Local<v8::String> v8Key = toV8String(m_isolate, key);
184     if (!map->Has(m_context, v8Key).FromMaybe(false)) return defaultValue;
185     v8::Local<v8::Value> intValue;
186     if (!map->Get(m_context, v8Key).ToLocal(&intValue)) return defaultValue;
187     return intValue.As<v8::Number>()->Value();
188   }
189 
setDoubleOnMap(v8::Local<v8::Map> map,const String16 & key,double value)190   void setDoubleOnMap(v8::Local<v8::Map> map, const String16& key,
191                       double value) {
192     v8::Local<v8::String> v8Key = toV8String(m_isolate, key);
193     if (!map->Set(m_context, v8Key, v8::Number::New(m_isolate, value))
194              .ToLocal(&map))
195       return;
196   }
197 
profilerAgent()198   V8ProfilerAgentImpl* profilerAgent() {
199     if (V8InspectorSessionImpl* session = currentSession()) {
200       if (session && session->profilerAgent()->enabled())
201         return session->profilerAgent();
202     }
203     return nullptr;
204   }
205 
debuggerAgent()206   V8DebuggerAgentImpl* debuggerAgent() {
207     if (V8InspectorSessionImpl* session = currentSession()) {
208       if (session && session->debuggerAgent()->enabled())
209         return session->debuggerAgent();
210     }
211     return nullptr;
212   }
213 
currentSession()214   V8InspectorSessionImpl* currentSession() {
215     InspectedContext* inspectedContext = ensureInspectedContext();
216     if (!inspectedContext) return nullptr;
217     return inspectedContext->inspector()->sessionForContextGroup(
218         inspectedContext->contextGroupId());
219   }
220 
221  private:
222   const v8::FunctionCallbackInfo<v8::Value>& m_info;
223   v8::Isolate* m_isolate;
224   v8::Local<v8::Context> m_context;
225   v8::Local<v8::Object> m_console;
226   InspectedContext* m_inspectedContext;
227   V8InspectorClient* m_inspectorClient;
228 
checkAndSetPrivateFlagOnConsole(const char * name,bool defaultValue)229   bool checkAndSetPrivateFlagOnConsole(const char* name, bool defaultValue) {
230     v8::Local<v8::Object> console = ensureConsole();
231     v8::Local<v8::Private> key =
232         v8::Private::ForApi(m_isolate, toV8StringInternalized(m_isolate, name));
233     v8::Local<v8::Value> flagValue;
234     if (!console->GetPrivate(m_context, key).ToLocal(&flagValue))
235       return defaultValue;
236     DCHECK(flagValue->IsUndefined() || flagValue->IsBoolean());
237     if (flagValue->IsBoolean()) {
238       DCHECK(flagValue.As<v8::Boolean>()->Value());
239       return true;
240     }
241     if (!console->SetPrivate(m_context, key, v8::True(m_isolate))
242              .FromMaybe(false))
243       return defaultValue;
244     return false;
245   }
246 
247   DISALLOW_COPY_AND_ASSIGN(ConsoleHelper);
248 };
249 
returnDataCallback(const v8::FunctionCallbackInfo<v8::Value> & info)250 void returnDataCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
251   info.GetReturnValue().Set(info.Data());
252 }
253 
createBoundFunctionProperty(v8::Local<v8::Context> context,v8::Local<v8::Object> console,const char * name,v8::FunctionCallback callback,const char * description=nullptr)254 void createBoundFunctionProperty(v8::Local<v8::Context> context,
255                                  v8::Local<v8::Object> console,
256                                  const char* name,
257                                  v8::FunctionCallback callback,
258                                  const char* description = nullptr) {
259   v8::Local<v8::String> funcName =
260       toV8StringInternalized(context->GetIsolate(), name);
261   v8::Local<v8::Function> func;
262   if (!v8::Function::New(context, callback, console, 0,
263                          v8::ConstructorBehavior::kThrow)
264            .ToLocal(&func))
265     return;
266   func->SetName(funcName);
267   if (description) {
268     v8::Local<v8::String> returnValue =
269         toV8String(context->GetIsolate(), description);
270     v8::Local<v8::Function> toStringFunction;
271     if (v8::Function::New(context, returnDataCallback, returnValue, 0,
272                           v8::ConstructorBehavior::kThrow)
273             .ToLocal(&toStringFunction))
274       createDataProperty(context, func, toV8StringInternalized(
275                                             context->GetIsolate(), "toString"),
276                          toStringFunction);
277   }
278   createDataProperty(context, console, funcName, func);
279 }
280 
281 }  // namespace
282 
debugCallback(const v8::FunctionCallbackInfo<v8::Value> & info)283 void V8Console::debugCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
284   ConsoleHelper(info).reportCall(ConsoleAPIType::kDebug);
285 }
286 
errorCallback(const v8::FunctionCallbackInfo<v8::Value> & info)287 void V8Console::errorCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
288   ConsoleHelper(info).reportCall(ConsoleAPIType::kError);
289 }
290 
infoCallback(const v8::FunctionCallbackInfo<v8::Value> & info)291 void V8Console::infoCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
292   ConsoleHelper(info).reportCall(ConsoleAPIType::kInfo);
293 }
294 
logCallback(const v8::FunctionCallbackInfo<v8::Value> & info)295 void V8Console::logCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
296   ConsoleHelper(info).reportCall(ConsoleAPIType::kLog);
297 }
298 
warnCallback(const v8::FunctionCallbackInfo<v8::Value> & info)299 void V8Console::warnCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
300   ConsoleHelper(info).reportCall(ConsoleAPIType::kWarning);
301 }
302 
dirCallback(const v8::FunctionCallbackInfo<v8::Value> & info)303 void V8Console::dirCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
304   ConsoleHelper(info).reportCall(ConsoleAPIType::kDir);
305 }
306 
dirxmlCallback(const v8::FunctionCallbackInfo<v8::Value> & info)307 void V8Console::dirxmlCallback(
308     const v8::FunctionCallbackInfo<v8::Value>& info) {
309   ConsoleHelper(info).reportCall(ConsoleAPIType::kDirXML);
310 }
311 
tableCallback(const v8::FunctionCallbackInfo<v8::Value> & info)312 void V8Console::tableCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
313   ConsoleHelper(info).reportCall(ConsoleAPIType::kTable);
314 }
315 
traceCallback(const v8::FunctionCallbackInfo<v8::Value> & info)316 void V8Console::traceCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
317   ConsoleHelper(info).reportCallWithDefaultArgument(ConsoleAPIType::kTrace,
318                                                     String16("console.trace"));
319 }
320 
groupCallback(const v8::FunctionCallbackInfo<v8::Value> & info)321 void V8Console::groupCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
322   ConsoleHelper(info).reportCallWithDefaultArgument(ConsoleAPIType::kStartGroup,
323                                                     String16("console.group"));
324 }
325 
groupCollapsedCallback(const v8::FunctionCallbackInfo<v8::Value> & info)326 void V8Console::groupCollapsedCallback(
327     const v8::FunctionCallbackInfo<v8::Value>& info) {
328   ConsoleHelper(info).reportCallWithDefaultArgument(
329       ConsoleAPIType::kStartGroupCollapsed, String16("console.groupCollapsed"));
330 }
331 
groupEndCallback(const v8::FunctionCallbackInfo<v8::Value> & info)332 void V8Console::groupEndCallback(
333     const v8::FunctionCallbackInfo<v8::Value>& info) {
334   ConsoleHelper(info).reportCallWithDefaultArgument(
335       ConsoleAPIType::kEndGroup, String16("console.groupEnd"));
336 }
337 
clearCallback(const v8::FunctionCallbackInfo<v8::Value> & info)338 void V8Console::clearCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
339   ConsoleHelper helper(info);
340   InspectedContext* context = helper.ensureInspectedContext();
341   if (!context) return;
342   int contextGroupId = context->contextGroupId();
343   if (V8InspectorClient* client = helper.ensureDebuggerClient())
344     client->consoleClear(contextGroupId);
345   helper.reportCallWithDefaultArgument(ConsoleAPIType::kClear,
346                                        String16("console.clear"));
347 }
348 
countCallback(const v8::FunctionCallbackInfo<v8::Value> & info)349 void V8Console::countCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
350   ConsoleHelper helper(info);
351 
352   String16 title = helper.firstArgToString(String16());
353   String16 identifier;
354   if (title.isEmpty()) {
355     std::unique_ptr<V8StackTraceImpl> stackTrace =
356         V8StackTraceImpl::capture(nullptr, 0, 1);
357     if (stackTrace && !stackTrace->isEmpty()) {
358       identifier = toString16(stackTrace->topSourceURL()) + ":" +
359                    String16::fromInteger(stackTrace->topLineNumber());
360     }
361   } else {
362     identifier = title + "@";
363   }
364 
365   v8::Local<v8::Map> countMap;
366   if (!helper.privateMap("V8Console#countMap").ToLocal(&countMap)) return;
367   int32_t count = helper.getIntFromMap(countMap, identifier, 0) + 1;
368   helper.setIntOnMap(countMap, identifier, count);
369   String16 countString = String16::fromInteger(count);
370   helper.reportCallWithArgument(
371       ConsoleAPIType::kCount,
372       title.isEmpty() ? countString : (title + ": " + countString));
373 }
374 
assertCallback(const v8::FunctionCallbackInfo<v8::Value> & info)375 void V8Console::assertCallback(
376     const v8::FunctionCallbackInfo<v8::Value>& info) {
377   ConsoleHelper helper(info);
378   if (helper.firstArgToBoolean(false)) return;
379 
380   std::vector<v8::Local<v8::Value>> arguments;
381   for (int i = 1; i < info.Length(); ++i) arguments.push_back(info[i]);
382   if (info.Length() < 2)
383     arguments.push_back(
384         toV8String(info.GetIsolate(), String16("console.assert")));
385   helper.reportCall(ConsoleAPIType::kAssert, arguments);
386 
387   if (V8DebuggerAgentImpl* debuggerAgent = helper.debuggerAgent())
388     debuggerAgent->breakProgramOnException(
389         protocol::Debugger::Paused::ReasonEnum::Assert, nullptr);
390 }
391 
markTimelineCallback(const v8::FunctionCallbackInfo<v8::Value> & info)392 void V8Console::markTimelineCallback(
393     const v8::FunctionCallbackInfo<v8::Value>& info) {
394   ConsoleHelper(info).reportDeprecatedCall("V8Console#markTimelineDeprecated",
395                                            "'console.markTimeline' is "
396                                            "deprecated. Please use "
397                                            "'console.timeStamp' instead.");
398   timeStampCallback(info);
399 }
400 
profileCallback(const v8::FunctionCallbackInfo<v8::Value> & info)401 void V8Console::profileCallback(
402     const v8::FunctionCallbackInfo<v8::Value>& info) {
403   ConsoleHelper helper(info);
404   if (V8ProfilerAgentImpl* profilerAgent = helper.profilerAgent())
405     profilerAgent->consoleProfile(helper.firstArgToString(String16()));
406 }
407 
profileEndCallback(const v8::FunctionCallbackInfo<v8::Value> & info)408 void V8Console::profileEndCallback(
409     const v8::FunctionCallbackInfo<v8::Value>& info) {
410   ConsoleHelper helper(info);
411   if (V8ProfilerAgentImpl* profilerAgent = helper.profilerAgent())
412     profilerAgent->consoleProfileEnd(helper.firstArgToString(String16()));
413 }
414 
timeFunction(const v8::FunctionCallbackInfo<v8::Value> & info,bool timelinePrefix)415 static void timeFunction(const v8::FunctionCallbackInfo<v8::Value>& info,
416                          bool timelinePrefix) {
417   ConsoleHelper helper(info);
418   if (V8InspectorClient* client = helper.ensureDebuggerClient()) {
419     String16 protocolTitle = helper.firstArgToString("default");
420     if (timelinePrefix) protocolTitle = "Timeline '" + protocolTitle + "'";
421     client->consoleTime(toStringView(protocolTitle));
422 
423     v8::Local<v8::Map> timeMap;
424     if (!helper.privateMap("V8Console#timeMap").ToLocal(&timeMap)) return;
425     helper.setDoubleOnMap(timeMap, protocolTitle, client->currentTimeMS());
426   }
427 }
428 
timeEndFunction(const v8::FunctionCallbackInfo<v8::Value> & info,bool timelinePrefix)429 static void timeEndFunction(const v8::FunctionCallbackInfo<v8::Value>& info,
430                             bool timelinePrefix) {
431   ConsoleHelper helper(info);
432   if (V8InspectorClient* client = helper.ensureDebuggerClient()) {
433     String16 protocolTitle = helper.firstArgToString("default");
434     if (timelinePrefix) protocolTitle = "Timeline '" + protocolTitle + "'";
435     client->consoleTimeEnd(toStringView(protocolTitle));
436 
437     v8::Local<v8::Map> timeMap;
438     if (!helper.privateMap("V8Console#timeMap").ToLocal(&timeMap)) return;
439     double elapsed = client->currentTimeMS() -
440                      helper.getDoubleFromMap(timeMap, protocolTitle, 0.0);
441     String16 message =
442         protocolTitle + ": " + String16::fromDouble(elapsed) + "ms";
443     helper.reportCallWithArgument(ConsoleAPIType::kTimeEnd, message);
444   }
445 }
446 
timelineCallback(const v8::FunctionCallbackInfo<v8::Value> & info)447 void V8Console::timelineCallback(
448     const v8::FunctionCallbackInfo<v8::Value>& info) {
449   ConsoleHelper(info).reportDeprecatedCall(
450       "V8Console#timeline",
451       "'console.timeline' is deprecated. Please use 'console.time' instead.");
452   timeFunction(info, true);
453 }
454 
timelineEndCallback(const v8::FunctionCallbackInfo<v8::Value> & info)455 void V8Console::timelineEndCallback(
456     const v8::FunctionCallbackInfo<v8::Value>& info) {
457   ConsoleHelper(info).reportDeprecatedCall("V8Console#timelineEnd",
458                                            "'console.timelineEnd' is "
459                                            "deprecated. Please use "
460                                            "'console.timeEnd' instead.");
461   timeEndFunction(info, true);
462 }
463 
timeCallback(const v8::FunctionCallbackInfo<v8::Value> & info)464 void V8Console::timeCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
465   timeFunction(info, false);
466 }
467 
timeEndCallback(const v8::FunctionCallbackInfo<v8::Value> & info)468 void V8Console::timeEndCallback(
469     const v8::FunctionCallbackInfo<v8::Value>& info) {
470   timeEndFunction(info, false);
471 }
472 
timeStampCallback(const v8::FunctionCallbackInfo<v8::Value> & info)473 void V8Console::timeStampCallback(
474     const v8::FunctionCallbackInfo<v8::Value>& info) {
475   ConsoleHelper helper(info);
476   if (V8InspectorClient* client = helper.ensureDebuggerClient()) {
477     String16 title = helper.firstArgToString(String16());
478     client->consoleTimeStamp(toStringView(title));
479   }
480 }
481 
memoryGetterCallback(const v8::FunctionCallbackInfo<v8::Value> & info)482 void V8Console::memoryGetterCallback(
483     const v8::FunctionCallbackInfo<v8::Value>& info) {
484   if (V8InspectorClient* client = ConsoleHelper(info).ensureDebuggerClient()) {
485     v8::Local<v8::Value> memoryValue;
486     if (!client
487              ->memoryInfo(info.GetIsolate(),
488                           info.GetIsolate()->GetCurrentContext())
489              .ToLocal(&memoryValue))
490       return;
491     info.GetReturnValue().Set(memoryValue);
492   }
493 }
494 
memorySetterCallback(const v8::FunctionCallbackInfo<v8::Value> & info)495 void V8Console::memorySetterCallback(
496     const v8::FunctionCallbackInfo<v8::Value>& info) {
497   // We can't make the attribute readonly as it breaks existing code that relies
498   // on being able to assign to console.memory in strict mode. Instead, the
499   // setter just ignores the passed value.  http://crbug.com/468611
500 }
501 
keysCallback(const v8::FunctionCallbackInfo<v8::Value> & info)502 void V8Console::keysCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
503   v8::Isolate* isolate = info.GetIsolate();
504   info.GetReturnValue().Set(v8::Array::New(isolate));
505 
506   ConsoleHelper helper(info);
507   v8::Local<v8::Object> obj;
508   if (!helper.firstArgAsObject().ToLocal(&obj)) return;
509   v8::Local<v8::Array> names;
510   if (!obj->GetOwnPropertyNames(isolate->GetCurrentContext()).ToLocal(&names))
511     return;
512   info.GetReturnValue().Set(names);
513 }
514 
valuesCallback(const v8::FunctionCallbackInfo<v8::Value> & info)515 void V8Console::valuesCallback(
516     const v8::FunctionCallbackInfo<v8::Value>& info) {
517   v8::Isolate* isolate = info.GetIsolate();
518   info.GetReturnValue().Set(v8::Array::New(isolate));
519 
520   ConsoleHelper helper(info);
521   v8::Local<v8::Object> obj;
522   if (!helper.firstArgAsObject().ToLocal(&obj)) return;
523   v8::Local<v8::Array> names;
524   v8::Local<v8::Context> context = isolate->GetCurrentContext();
525   if (!obj->GetOwnPropertyNames(context).ToLocal(&names)) return;
526   v8::Local<v8::Array> values = v8::Array::New(isolate, names->Length());
527   for (uint32_t i = 0; i < names->Length(); ++i) {
528     v8::Local<v8::Value> key;
529     if (!names->Get(context, i).ToLocal(&key)) continue;
530     v8::Local<v8::Value> value;
531     if (!obj->Get(context, key).ToLocal(&value)) continue;
532     createDataProperty(context, values, i, value);
533   }
534   info.GetReturnValue().Set(values);
535 }
536 
setFunctionBreakpoint(ConsoleHelper & helper,v8::Local<v8::Function> function,V8DebuggerAgentImpl::BreakpointSource source,const String16 & condition,bool enable)537 static void setFunctionBreakpoint(ConsoleHelper& helper,
538                                   v8::Local<v8::Function> function,
539                                   V8DebuggerAgentImpl::BreakpointSource source,
540                                   const String16& condition, bool enable) {
541   V8DebuggerAgentImpl* debuggerAgent = helper.debuggerAgent();
542   if (!debuggerAgent) return;
543   String16 scriptId = String16::fromInteger(function->ScriptId());
544   int lineNumber = function->GetScriptLineNumber();
545   int columnNumber = function->GetScriptColumnNumber();
546   if (lineNumber == v8::Function::kLineOffsetNotFound ||
547       columnNumber == v8::Function::kLineOffsetNotFound)
548     return;
549   if (enable)
550     debuggerAgent->setBreakpointAt(scriptId, lineNumber, columnNumber, source,
551                                    condition);
552   else
553     debuggerAgent->removeBreakpointAt(scriptId, lineNumber, columnNumber,
554                                       source);
555 }
556 
debugFunctionCallback(const v8::FunctionCallbackInfo<v8::Value> & info)557 void V8Console::debugFunctionCallback(
558     const v8::FunctionCallbackInfo<v8::Value>& info) {
559   ConsoleHelper helper(info);
560   v8::Local<v8::Function> function;
561   if (!helper.firstArgAsFunction().ToLocal(&function)) return;
562   setFunctionBreakpoint(helper, function,
563                         V8DebuggerAgentImpl::DebugCommandBreakpointSource,
564                         String16(), true);
565 }
566 
undebugFunctionCallback(const v8::FunctionCallbackInfo<v8::Value> & info)567 void V8Console::undebugFunctionCallback(
568     const v8::FunctionCallbackInfo<v8::Value>& info) {
569   ConsoleHelper helper(info);
570   v8::Local<v8::Function> function;
571   if (!helper.firstArgAsFunction().ToLocal(&function)) return;
572   setFunctionBreakpoint(helper, function,
573                         V8DebuggerAgentImpl::DebugCommandBreakpointSource,
574                         String16(), false);
575 }
576 
monitorFunctionCallback(const v8::FunctionCallbackInfo<v8::Value> & info)577 void V8Console::monitorFunctionCallback(
578     const v8::FunctionCallbackInfo<v8::Value>& info) {
579   ConsoleHelper helper(info);
580   v8::Local<v8::Function> function;
581   if (!helper.firstArgAsFunction().ToLocal(&function)) return;
582   v8::Local<v8::Value> name = function->GetName();
583   if (!name->IsString() || !v8::Local<v8::String>::Cast(name)->Length())
584     name = function->GetInferredName();
585   String16 functionName = toProtocolStringWithTypeCheck(name);
586   String16Builder builder;
587   builder.append("console.log(\"function ");
588   if (functionName.isEmpty())
589     builder.append("(anonymous function)");
590   else
591     builder.append(functionName);
592   builder.append(
593       " called\" + (arguments.length > 0 ? \" with arguments: \" + "
594       "Array.prototype.join.call(arguments, \", \") : \"\")) && false");
595   setFunctionBreakpoint(helper, function,
596                         V8DebuggerAgentImpl::MonitorCommandBreakpointSource,
597                         builder.toString(), true);
598 }
599 
unmonitorFunctionCallback(const v8::FunctionCallbackInfo<v8::Value> & info)600 void V8Console::unmonitorFunctionCallback(
601     const v8::FunctionCallbackInfo<v8::Value>& info) {
602   ConsoleHelper helper(info);
603   v8::Local<v8::Function> function;
604   if (!helper.firstArgAsFunction().ToLocal(&function)) return;
605   setFunctionBreakpoint(helper, function,
606                         V8DebuggerAgentImpl::MonitorCommandBreakpointSource,
607                         String16(), false);
608 }
609 
lastEvaluationResultCallback(const v8::FunctionCallbackInfo<v8::Value> & info)610 void V8Console::lastEvaluationResultCallback(
611     const v8::FunctionCallbackInfo<v8::Value>& info) {
612   ConsoleHelper helper(info);
613   InspectedContext* context = helper.ensureInspectedContext();
614   if (!context) return;
615   if (InjectedScript* injectedScript = context->getInjectedScript())
616     info.GetReturnValue().Set(injectedScript->lastEvaluationResult());
617 }
618 
inspectImpl(const v8::FunctionCallbackInfo<v8::Value> & info,bool copyToClipboard)619 static void inspectImpl(const v8::FunctionCallbackInfo<v8::Value>& info,
620                         bool copyToClipboard) {
621   if (info.Length() < 1) return;
622   if (!copyToClipboard) info.GetReturnValue().Set(info[0]);
623 
624   ConsoleHelper helper(info);
625   InspectedContext* context = helper.ensureInspectedContext();
626   if (!context) return;
627   InjectedScript* injectedScript = context->getInjectedScript();
628   if (!injectedScript) return;
629   std::unique_ptr<protocol::Runtime::RemoteObject> wrappedObject;
630   protocol::Response response =
631       injectedScript->wrapObject(info[0], "", false /** forceValueType */,
632                                  false /** generatePreview */, &wrappedObject);
633   if (!response.isSuccess()) return;
634 
635   std::unique_ptr<protocol::DictionaryValue> hints =
636       protocol::DictionaryValue::create();
637   if (copyToClipboard) hints->setBoolean("copyToClipboard", true);
638   if (V8InspectorSessionImpl* session = helper.currentSession())
639     session->runtimeAgent()->inspect(std::move(wrappedObject),
640                                      std::move(hints));
641 }
642 
inspectCallback(const v8::FunctionCallbackInfo<v8::Value> & info)643 void V8Console::inspectCallback(
644     const v8::FunctionCallbackInfo<v8::Value>& info) {
645   inspectImpl(info, false);
646 }
647 
copyCallback(const v8::FunctionCallbackInfo<v8::Value> & info)648 void V8Console::copyCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
649   inspectImpl(info, true);
650 }
651 
inspectedObject(const v8::FunctionCallbackInfo<v8::Value> & info,unsigned num)652 void V8Console::inspectedObject(const v8::FunctionCallbackInfo<v8::Value>& info,
653                                 unsigned num) {
654   DCHECK(num < V8InspectorSessionImpl::kInspectedObjectBufferSize);
655   ConsoleHelper helper(info);
656   if (V8InspectorSessionImpl* session = helper.currentSession()) {
657     V8InspectorSession::Inspectable* object = session->inspectedObject(num);
658     v8::Isolate* isolate = info.GetIsolate();
659     if (object)
660       info.GetReturnValue().Set(object->get(isolate->GetCurrentContext()));
661     else
662       info.GetReturnValue().Set(v8::Undefined(isolate));
663   }
664 }
665 
createConsole(InspectedContext * inspectedContext,bool hasMemoryAttribute)666 v8::Local<v8::Object> V8Console::createConsole(
667     InspectedContext* inspectedContext, bool hasMemoryAttribute) {
668   v8::Local<v8::Context> context = inspectedContext->context();
669   v8::Context::Scope contextScope(context);
670   v8::Isolate* isolate = context->GetIsolate();
671   v8::MicrotasksScope microtasksScope(isolate,
672                                       v8::MicrotasksScope::kDoNotRunMicrotasks);
673 
674   v8::Local<v8::Object> console = v8::Object::New(isolate);
675   bool success =
676       console->SetPrototype(context, v8::Object::New(isolate)).FromMaybe(false);
677   DCHECK(success);
678   USE(success);
679 
680   createBoundFunctionProperty(context, console, "debug",
681                               V8Console::debugCallback);
682   createBoundFunctionProperty(context, console, "error",
683                               V8Console::errorCallback);
684   createBoundFunctionProperty(context, console, "info",
685                               V8Console::infoCallback);
686   createBoundFunctionProperty(context, console, "log", V8Console::logCallback);
687   createBoundFunctionProperty(context, console, "warn",
688                               V8Console::warnCallback);
689   createBoundFunctionProperty(context, console, "dir", V8Console::dirCallback);
690   createBoundFunctionProperty(context, console, "dirxml",
691                               V8Console::dirxmlCallback);
692   createBoundFunctionProperty(context, console, "table",
693                               V8Console::tableCallback);
694   createBoundFunctionProperty(context, console, "trace",
695                               V8Console::traceCallback);
696   createBoundFunctionProperty(context, console, "group",
697                               V8Console::groupCallback);
698   createBoundFunctionProperty(context, console, "groupCollapsed",
699                               V8Console::groupCollapsedCallback);
700   createBoundFunctionProperty(context, console, "groupEnd",
701                               V8Console::groupEndCallback);
702   createBoundFunctionProperty(context, console, "clear",
703                               V8Console::clearCallback);
704   createBoundFunctionProperty(context, console, "count",
705                               V8Console::countCallback);
706   createBoundFunctionProperty(context, console, "assert",
707                               V8Console::assertCallback);
708   createBoundFunctionProperty(context, console, "markTimeline",
709                               V8Console::markTimelineCallback);
710   createBoundFunctionProperty(context, console, "profile",
711                               V8Console::profileCallback);
712   createBoundFunctionProperty(context, console, "profileEnd",
713                               V8Console::profileEndCallback);
714   createBoundFunctionProperty(context, console, "timeline",
715                               V8Console::timelineCallback);
716   createBoundFunctionProperty(context, console, "timelineEnd",
717                               V8Console::timelineEndCallback);
718   createBoundFunctionProperty(context, console, "time",
719                               V8Console::timeCallback);
720   createBoundFunctionProperty(context, console, "timeEnd",
721                               V8Console::timeEndCallback);
722   createBoundFunctionProperty(context, console, "timeStamp",
723                               V8Console::timeStampCallback);
724 
725   const char* jsConsoleAssert =
726       "(function(){\n"
727       "  var originAssert = this.assert;\n"
728       "  originAssert.apply = Function.prototype.apply;\n"
729       "  this.assert = assertWrapper;\n"
730       "  assertWrapper.toString = () => originAssert.toString();\n"
731       "  function assertWrapper(){\n"
732       "    if (!!arguments[0]) return;\n"
733       "    originAssert.apply(null, arguments);\n"
734       "  }\n"
735       "})";
736 
737   v8::Local<v8::String> assertSource = toV8String(isolate, jsConsoleAssert);
738   V8InspectorImpl* inspector = inspectedContext->inspector();
739   v8::Local<v8::Value> setupFunction;
740   if (inspector->compileAndRunInternalScript(context, assertSource)
741           .ToLocal(&setupFunction) &&
742       setupFunction->IsFunction()) {
743     inspector->callInternalFunction(
744         v8::Local<v8::Function>::Cast(setupFunction), context, console, 0,
745         nullptr);
746   }
747 
748   if (hasMemoryAttribute)
749     console->SetAccessorProperty(
750         toV8StringInternalized(isolate, "memory"),
751         v8::Function::New(context, V8Console::memoryGetterCallback, console, 0,
752                           v8::ConstructorBehavior::kThrow)
753             .ToLocalChecked(),
754         v8::Function::New(context, V8Console::memorySetterCallback,
755                           v8::Local<v8::Value>(), 0,
756                           v8::ConstructorBehavior::kThrow)
757             .ToLocalChecked(),
758         static_cast<v8::PropertyAttribute>(v8::None), v8::DEFAULT);
759 
760   console->SetPrivate(context, inspectedContextPrivateKey(isolate),
761                       v8::External::New(isolate, inspectedContext));
762   return console;
763 }
764 
clearInspectedContextIfNeeded(v8::Local<v8::Context> context,v8::Local<v8::Object> console)765 void V8Console::clearInspectedContextIfNeeded(v8::Local<v8::Context> context,
766                                               v8::Local<v8::Object> console) {
767   v8::Isolate* isolate = context->GetIsolate();
768   console->SetPrivate(context, inspectedContextPrivateKey(isolate),
769                       v8::External::New(isolate, nullptr));
770 }
771 
createCommandLineAPI(InspectedContext * inspectedContext)772 v8::Local<v8::Object> V8Console::createCommandLineAPI(
773     InspectedContext* inspectedContext) {
774   v8::Local<v8::Context> context = inspectedContext->context();
775   v8::Isolate* isolate = context->GetIsolate();
776   v8::MicrotasksScope microtasksScope(isolate,
777                                       v8::MicrotasksScope::kDoNotRunMicrotasks);
778 
779   v8::Local<v8::Object> commandLineAPI = v8::Object::New(isolate);
780   bool success =
781       commandLineAPI->SetPrototype(context, v8::Null(isolate)).FromMaybe(false);
782   DCHECK(success);
783   USE(success);
784 
785   createBoundFunctionProperty(context, commandLineAPI, "dir",
786                               V8Console::dirCallback,
787                               "function dir(value) { [Command Line API] }");
788   createBoundFunctionProperty(context, commandLineAPI, "dirxml",
789                               V8Console::dirxmlCallback,
790                               "function dirxml(value) { [Command Line API] }");
791   createBoundFunctionProperty(context, commandLineAPI, "profile",
792                               V8Console::profileCallback,
793                               "function profile(title) { [Command Line API] }");
794   createBoundFunctionProperty(
795       context, commandLineAPI, "profileEnd", V8Console::profileEndCallback,
796       "function profileEnd(title) { [Command Line API] }");
797   createBoundFunctionProperty(context, commandLineAPI, "clear",
798                               V8Console::clearCallback,
799                               "function clear() { [Command Line API] }");
800   createBoundFunctionProperty(
801       context, commandLineAPI, "table", V8Console::tableCallback,
802       "function table(data, [columns]) { [Command Line API] }");
803 
804   createBoundFunctionProperty(context, commandLineAPI, "keys",
805                               V8Console::keysCallback,
806                               "function keys(object) { [Command Line API] }");
807   createBoundFunctionProperty(context, commandLineAPI, "values",
808                               V8Console::valuesCallback,
809                               "function values(object) { [Command Line API] }");
810   createBoundFunctionProperty(
811       context, commandLineAPI, "debug", V8Console::debugFunctionCallback,
812       "function debug(function) { [Command Line API] }");
813   createBoundFunctionProperty(
814       context, commandLineAPI, "undebug", V8Console::undebugFunctionCallback,
815       "function undebug(function) { [Command Line API] }");
816   createBoundFunctionProperty(
817       context, commandLineAPI, "monitor", V8Console::monitorFunctionCallback,
818       "function monitor(function) { [Command Line API] }");
819   createBoundFunctionProperty(
820       context, commandLineAPI, "unmonitor",
821       V8Console::unmonitorFunctionCallback,
822       "function unmonitor(function) { [Command Line API] }");
823   createBoundFunctionProperty(
824       context, commandLineAPI, "inspect", V8Console::inspectCallback,
825       "function inspect(object) { [Command Line API] }");
826   createBoundFunctionProperty(context, commandLineAPI, "copy",
827                               V8Console::copyCallback,
828                               "function copy(value) { [Command Line API] }");
829   createBoundFunctionProperty(context, commandLineAPI, "$_",
830                               V8Console::lastEvaluationResultCallback);
831   createBoundFunctionProperty(context, commandLineAPI, "$0",
832                               V8Console::inspectedObject0);
833   createBoundFunctionProperty(context, commandLineAPI, "$1",
834                               V8Console::inspectedObject1);
835   createBoundFunctionProperty(context, commandLineAPI, "$2",
836                               V8Console::inspectedObject2);
837   createBoundFunctionProperty(context, commandLineAPI, "$3",
838                               V8Console::inspectedObject3);
839   createBoundFunctionProperty(context, commandLineAPI, "$4",
840                               V8Console::inspectedObject4);
841 
842   inspectedContext->inspector()->client()->installAdditionalCommandLineAPI(
843       context, commandLineAPI);
844 
845   commandLineAPI->SetPrivate(context, inspectedContextPrivateKey(isolate),
846                              v8::External::New(isolate, inspectedContext));
847   return commandLineAPI;
848 }
849 
isCommandLineAPIGetter(const String16 & name)850 static bool isCommandLineAPIGetter(const String16& name) {
851   if (name.length() != 2) return false;
852   // $0 ... $4, $_
853   return name[0] == '$' &&
854          ((name[1] >= '0' && name[1] <= '4') || name[1] == '_');
855 }
856 
accessorGetterCallback(v8::Local<v8::Name> name,const v8::PropertyCallbackInfo<v8::Value> & info)857 void V8Console::CommandLineAPIScope::accessorGetterCallback(
858     v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
859   CommandLineAPIScope* scope = static_cast<CommandLineAPIScope*>(
860       info.Data().As<v8::External>()->Value());
861   DCHECK(scope);
862 
863   v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
864   if (scope->m_cleanup) {
865     bool removed = info.Holder()->Delete(context, name).FromMaybe(false);
866     DCHECK(removed);
867     USE(removed);
868     return;
869   }
870   v8::Local<v8::Object> commandLineAPI = scope->m_commandLineAPI;
871 
872   v8::Local<v8::Value> value;
873   if (!commandLineAPI->Get(context, name).ToLocal(&value)) return;
874   if (isCommandLineAPIGetter(toProtocolStringWithTypeCheck(name))) {
875     DCHECK(value->IsFunction());
876     v8::MicrotasksScope microtasks(info.GetIsolate(),
877                                    v8::MicrotasksScope::kDoNotRunMicrotasks);
878     if (value.As<v8::Function>()
879             ->Call(context, commandLineAPI, 0, nullptr)
880             .ToLocal(&value))
881       info.GetReturnValue().Set(value);
882   } else {
883     info.GetReturnValue().Set(value);
884   }
885 }
886 
accessorSetterCallback(v8::Local<v8::Name> name,v8::Local<v8::Value> value,const v8::PropertyCallbackInfo<void> & info)887 void V8Console::CommandLineAPIScope::accessorSetterCallback(
888     v8::Local<v8::Name> name, v8::Local<v8::Value> value,
889     const v8::PropertyCallbackInfo<void>& info) {
890   CommandLineAPIScope* scope = static_cast<CommandLineAPIScope*>(
891       info.Data().As<v8::External>()->Value());
892   v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
893   if (!info.Holder()->Delete(context, name).FromMaybe(false)) return;
894   if (!info.Holder()->CreateDataProperty(context, name, value).FromMaybe(false))
895     return;
896   bool removed =
897       scope->m_installedMethods->Delete(context, name).FromMaybe(false);
898   DCHECK(removed);
899   USE(removed);
900 }
901 
CommandLineAPIScope(v8::Local<v8::Context> context,v8::Local<v8::Object> commandLineAPI,v8::Local<v8::Object> global)902 V8Console::CommandLineAPIScope::CommandLineAPIScope(
903     v8::Local<v8::Context> context, v8::Local<v8::Object> commandLineAPI,
904     v8::Local<v8::Object> global)
905     : m_context(context),
906       m_commandLineAPI(commandLineAPI),
907       m_global(global),
908       m_installedMethods(v8::Set::New(context->GetIsolate())),
909       m_cleanup(false) {
910   v8::Local<v8::Array> names;
911   if (!m_commandLineAPI->GetOwnPropertyNames(context).ToLocal(&names)) return;
912   v8::Local<v8::External> externalThis =
913       v8::External::New(context->GetIsolate(), this);
914   for (uint32_t i = 0; i < names->Length(); ++i) {
915     v8::Local<v8::Value> name;
916     if (!names->Get(context, i).ToLocal(&name) || !name->IsName()) continue;
917     if (m_global->Has(context, name).FromMaybe(true)) continue;
918     if (!m_installedMethods->Add(context, name).ToLocal(&m_installedMethods))
919       continue;
920     if (!m_global
921              ->SetAccessor(context, v8::Local<v8::Name>::Cast(name),
922                            CommandLineAPIScope::accessorGetterCallback,
923                            CommandLineAPIScope::accessorSetterCallback,
924                            externalThis, v8::DEFAULT, v8::DontEnum)
925              .FromMaybe(false)) {
926       bool removed = m_installedMethods->Delete(context, name).FromMaybe(false);
927       DCHECK(removed);
928       USE(removed);
929       continue;
930     }
931   }
932 }
933 
~CommandLineAPIScope()934 V8Console::CommandLineAPIScope::~CommandLineAPIScope() {
935   m_cleanup = true;
936   v8::Local<v8::Array> names = m_installedMethods->AsArray();
937   for (uint32_t i = 0; i < names->Length(); ++i) {
938     v8::Local<v8::Value> name;
939     if (!names->Get(m_context, i).ToLocal(&name) || !name->IsName()) continue;
940     if (name->IsString()) {
941       v8::Local<v8::Value> descriptor;
942       bool success = m_global
943                          ->GetOwnPropertyDescriptor(
944                              m_context, v8::Local<v8::String>::Cast(name))
945                          .ToLocal(&descriptor);
946       DCHECK(success);
947       USE(success);
948     }
949   }
950 }
951 
952 }  // namespace v8_inspector
953