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