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-utils.h"
19
20 #include "include/v8-inspector.h"
21
22 namespace v8_inspector {
23
24 namespace {
25
consoleContextToString(v8::Isolate * isolate,const v8::debug::ConsoleContext & consoleContext)26 String16 consoleContextToString(
27 v8::Isolate* isolate, const v8::debug::ConsoleContext& consoleContext) {
28 if (consoleContext.id() == 0) return String16();
29 return toProtocolString(isolate, consoleContext.name()) + "#" +
30 String16::fromInteger(consoleContext.id());
31 }
32
33 class ConsoleHelper {
34 public:
ConsoleHelper(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext,V8InspectorImpl * inspector)35 ConsoleHelper(const v8::debug::ConsoleCallArguments& info,
36 const v8::debug::ConsoleContext& consoleContext,
37 V8InspectorImpl* inspector)
38 : m_info(info),
39 m_consoleContext(consoleContext),
40 m_isolate(inspector->isolate()),
41 m_context(m_isolate->GetCurrentContext()),
42 m_inspector(inspector),
43 m_contextId(InspectedContext::contextId(m_context)),
44 m_groupId(m_inspector->contextGroupId(m_contextId)) {}
45
contextId() const46 int contextId() const { return m_contextId; }
groupId() const47 int groupId() const { return m_groupId; }
48
injectedScript(int sessionId)49 InjectedScript* injectedScript(int sessionId) {
50 InspectedContext* context = m_inspector->getContext(m_groupId, m_contextId);
51 if (!context) return nullptr;
52 return context->getInjectedScript(sessionId);
53 }
54
session(int sessionId)55 V8InspectorSessionImpl* session(int sessionId) {
56 return m_inspector->sessionById(m_groupId, sessionId);
57 }
58
consoleMessageStorage()59 V8ConsoleMessageStorage* consoleMessageStorage() {
60 return m_inspector->ensureConsoleMessageStorage(m_groupId);
61 }
62
reportCall(ConsoleAPIType type)63 void reportCall(ConsoleAPIType type) {
64 if (!m_info.Length()) return;
65 std::vector<v8::Local<v8::Value>> arguments;
66 for (int i = 0; i < m_info.Length(); ++i) arguments.push_back(m_info[i]);
67 reportCall(type, arguments);
68 }
69
reportCallWithDefaultArgument(ConsoleAPIType type,const String16 & message)70 void reportCallWithDefaultArgument(ConsoleAPIType type,
71 const String16& message) {
72 std::vector<v8::Local<v8::Value>> arguments;
73 for (int i = 0; i < m_info.Length(); ++i) arguments.push_back(m_info[i]);
74 if (!m_info.Length()) arguments.push_back(toV8String(m_isolate, message));
75 reportCall(type, arguments);
76 }
77
reportCallWithArgument(ConsoleAPIType type,const String16 & message)78 void reportCallWithArgument(ConsoleAPIType type, const String16& message) {
79 std::vector<v8::Local<v8::Value>> arguments(1,
80 toV8String(m_isolate, message));
81 reportCall(type, arguments);
82 }
83
reportCall(ConsoleAPIType type,const std::vector<v8::Local<v8::Value>> & arguments)84 void reportCall(ConsoleAPIType type,
85 const std::vector<v8::Local<v8::Value>>& arguments) {
86 if (!m_groupId) return;
87 std::unique_ptr<V8ConsoleMessage> message =
88 V8ConsoleMessage::createForConsoleAPI(
89 m_context, m_contextId, m_groupId, m_inspector,
90 m_inspector->client()->currentTimeMS(), type, arguments,
91 consoleContextToString(m_isolate, m_consoleContext),
92 m_inspector->debugger()->captureStackTrace(false));
93 consoleMessageStorage()->addMessage(std::move(message));
94 }
95
reportDeprecatedCall(const char * id,const String16 & message)96 void reportDeprecatedCall(const char* id, const String16& message) {
97 if (!consoleMessageStorage()->shouldReportDeprecationMessage(m_contextId,
98 id)) {
99 return;
100 }
101 std::vector<v8::Local<v8::Value>> arguments(1,
102 toV8String(m_isolate, message));
103 reportCall(ConsoleAPIType::kWarning, arguments);
104 }
105
firstArgToBoolean(bool defaultValue)106 bool firstArgToBoolean(bool defaultValue) {
107 if (m_info.Length() < 1) return defaultValue;
108 if (m_info[0]->IsBoolean()) return m_info[0].As<v8::Boolean>()->Value();
109 return m_info[0]->BooleanValue(m_context).FromMaybe(defaultValue);
110 }
111
firstArgToString(const String16 & defaultValue,bool allowUndefined=true)112 String16 firstArgToString(const String16& defaultValue,
113 bool allowUndefined = true) {
114 if (m_info.Length() < 1 || (!allowUndefined && m_info[0]->IsUndefined())) {
115 return defaultValue;
116 }
117 v8::Local<v8::String> titleValue;
118 v8::TryCatch tryCatch(m_context->GetIsolate());
119 if (m_info[0]->IsObject()) {
120 if (!m_info[0].As<v8::Object>()->ObjectProtoToString(m_context).ToLocal(
121 &titleValue))
122 return defaultValue;
123 } else {
124 if (!m_info[0]->ToString(m_context).ToLocal(&titleValue))
125 return defaultValue;
126 }
127 return toProtocolString(m_context->GetIsolate(), titleValue);
128 }
129
firstArgAsObject()130 v8::MaybeLocal<v8::Object> firstArgAsObject() {
131 if (m_info.Length() < 1 || !m_info[0]->IsObject())
132 return v8::MaybeLocal<v8::Object>();
133 return m_info[0].As<v8::Object>();
134 }
135
firstArgAsFunction()136 v8::MaybeLocal<v8::Function> firstArgAsFunction() {
137 if (m_info.Length() < 1 || !m_info[0]->IsFunction())
138 return v8::MaybeLocal<v8::Function>();
139 v8::Local<v8::Function> func = m_info[0].As<v8::Function>();
140 while (func->GetBoundFunction()->IsFunction())
141 func = func->GetBoundFunction().As<v8::Function>();
142 return func;
143 }
144
forEachSession(std::function<void (V8InspectorSessionImpl *)> callback)145 void forEachSession(std::function<void(V8InspectorSessionImpl*)> callback) {
146 m_inspector->forEachSession(m_groupId, callback);
147 }
148
149 private:
150 const v8::debug::ConsoleCallArguments& m_info;
151 const v8::debug::ConsoleContext& m_consoleContext;
152 v8::Isolate* m_isolate;
153 v8::Local<v8::Context> m_context;
154 V8InspectorImpl* m_inspector = nullptr;
155 int m_contextId;
156 int m_groupId;
157
158 DISALLOW_COPY_AND_ASSIGN(ConsoleHelper);
159 };
160
returnDataCallback(const v8::FunctionCallbackInfo<v8::Value> & info)161 void returnDataCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
162 info.GetReturnValue().Set(info.Data());
163 }
164
createBoundFunctionProperty(v8::Local<v8::Context> context,v8::Local<v8::Object> console,v8::Local<v8::Value> data,const char * name,v8::FunctionCallback callback,const char * description=nullptr,v8::SideEffectType side_effect_type=v8::SideEffectType::kHasSideEffect)165 void createBoundFunctionProperty(
166 v8::Local<v8::Context> context, v8::Local<v8::Object> console,
167 v8::Local<v8::Value> data, const char* name, v8::FunctionCallback callback,
168 const char* description = nullptr,
169 v8::SideEffectType side_effect_type = v8::SideEffectType::kHasSideEffect) {
170 v8::Local<v8::String> funcName =
171 toV8StringInternalized(context->GetIsolate(), name);
172 v8::Local<v8::Function> func;
173 if (!v8::Function::New(context, callback, data, 0,
174 v8::ConstructorBehavior::kThrow, side_effect_type)
175 .ToLocal(&func))
176 return;
177 func->SetName(funcName);
178 if (description) {
179 v8::Local<v8::String> returnValue =
180 toV8String(context->GetIsolate(), description);
181 v8::Local<v8::Function> toStringFunction;
182 if (v8::Function::New(context, returnDataCallback, returnValue, 0,
183 v8::ConstructorBehavior::kThrow,
184 v8::SideEffectType::kHasNoSideEffect)
185 .ToLocal(&toStringFunction))
186 createDataProperty(context, func, toV8StringInternalized(
187 context->GetIsolate(), "toString"),
188 toStringFunction);
189 }
190 createDataProperty(context, console, funcName, func);
191 }
192
193 enum InspectRequest { kRegular, kCopyToClipboard, kQueryObjects };
194
195 } // namespace
196
V8Console(V8InspectorImpl * inspector)197 V8Console::V8Console(V8InspectorImpl* inspector) : m_inspector(inspector) {}
198
Debug(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)199 void V8Console::Debug(const v8::debug::ConsoleCallArguments& info,
200 const v8::debug::ConsoleContext& consoleContext) {
201 ConsoleHelper(info, consoleContext, m_inspector)
202 .reportCall(ConsoleAPIType::kDebug);
203 }
204
Error(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)205 void V8Console::Error(const v8::debug::ConsoleCallArguments& info,
206 const v8::debug::ConsoleContext& consoleContext) {
207 ConsoleHelper(info, consoleContext, m_inspector)
208 .reportCall(ConsoleAPIType::kError);
209 }
210
Info(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)211 void V8Console::Info(const v8::debug::ConsoleCallArguments& info,
212 const v8::debug::ConsoleContext& consoleContext) {
213 ConsoleHelper(info, consoleContext, m_inspector)
214 .reportCall(ConsoleAPIType::kInfo);
215 }
216
Log(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)217 void V8Console::Log(const v8::debug::ConsoleCallArguments& info,
218 const v8::debug::ConsoleContext& consoleContext) {
219 ConsoleHelper(info, consoleContext, m_inspector)
220 .reportCall(ConsoleAPIType::kLog);
221 }
222
Warn(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)223 void V8Console::Warn(const v8::debug::ConsoleCallArguments& info,
224 const v8::debug::ConsoleContext& consoleContext) {
225 ConsoleHelper(info, consoleContext, m_inspector)
226 .reportCall(ConsoleAPIType::kWarning);
227 }
228
Dir(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)229 void V8Console::Dir(const v8::debug::ConsoleCallArguments& info,
230 const v8::debug::ConsoleContext& consoleContext) {
231 ConsoleHelper(info, consoleContext, m_inspector)
232 .reportCall(ConsoleAPIType::kDir);
233 }
234
DirXml(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)235 void V8Console::DirXml(const v8::debug::ConsoleCallArguments& info,
236 const v8::debug::ConsoleContext& consoleContext) {
237 ConsoleHelper(info, consoleContext, m_inspector)
238 .reportCall(ConsoleAPIType::kDirXML);
239 }
240
Table(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)241 void V8Console::Table(const v8::debug::ConsoleCallArguments& info,
242 const v8::debug::ConsoleContext& consoleContext) {
243 ConsoleHelper(info, consoleContext, m_inspector)
244 .reportCall(ConsoleAPIType::kTable);
245 }
246
Trace(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)247 void V8Console::Trace(const v8::debug::ConsoleCallArguments& info,
248 const v8::debug::ConsoleContext& consoleContext) {
249 ConsoleHelper(info, consoleContext, m_inspector)
250 .reportCallWithDefaultArgument(ConsoleAPIType::kTrace,
251 String16("console.trace"));
252 }
253
Group(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)254 void V8Console::Group(const v8::debug::ConsoleCallArguments& info,
255 const v8::debug::ConsoleContext& consoleContext) {
256 ConsoleHelper(info, consoleContext, m_inspector)
257 .reportCallWithDefaultArgument(ConsoleAPIType::kStartGroup,
258 String16("console.group"));
259 }
260
GroupCollapsed(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)261 void V8Console::GroupCollapsed(
262 const v8::debug::ConsoleCallArguments& info,
263 const v8::debug::ConsoleContext& consoleContext) {
264 ConsoleHelper(info, consoleContext, m_inspector)
265 .reportCallWithDefaultArgument(ConsoleAPIType::kStartGroupCollapsed,
266 String16("console.groupCollapsed"));
267 }
268
GroupEnd(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)269 void V8Console::GroupEnd(const v8::debug::ConsoleCallArguments& info,
270 const v8::debug::ConsoleContext& consoleContext) {
271 ConsoleHelper(info, consoleContext, m_inspector)
272 .reportCallWithDefaultArgument(ConsoleAPIType::kEndGroup,
273 String16("console.groupEnd"));
274 }
275
Clear(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)276 void V8Console::Clear(const v8::debug::ConsoleCallArguments& info,
277 const v8::debug::ConsoleContext& consoleContext) {
278 ConsoleHelper helper(info, consoleContext, m_inspector);
279 if (!helper.groupId()) return;
280 m_inspector->client()->consoleClear(helper.groupId());
281 helper.reportCallWithDefaultArgument(ConsoleAPIType::kClear,
282 String16("console.clear"));
283 }
284
identifierFromTitleOrStackTrace(const String16 & title,const ConsoleHelper & helper,const v8::debug::ConsoleContext & consoleContext,V8InspectorImpl * inspector)285 static String16 identifierFromTitleOrStackTrace(
286 const String16& title, const ConsoleHelper& helper,
287 const v8::debug::ConsoleContext& consoleContext,
288 V8InspectorImpl* inspector) {
289 String16 identifier;
290 if (title.isEmpty()) {
291 std::unique_ptr<V8StackTraceImpl> stackTrace =
292 V8StackTraceImpl::capture(inspector->debugger(), helper.groupId(), 1);
293 if (stackTrace && !stackTrace->isEmpty()) {
294 identifier = toString16(stackTrace->topSourceURL()) + ":" +
295 String16::fromInteger(stackTrace->topLineNumber());
296 }
297 } else {
298 identifier = title + "@";
299 }
300 identifier = consoleContextToString(inspector->isolate(), consoleContext) +
301 "@" + identifier;
302
303 return identifier;
304 }
305
Count(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)306 void V8Console::Count(const v8::debug::ConsoleCallArguments& info,
307 const v8::debug::ConsoleContext& consoleContext) {
308 ConsoleHelper helper(info, consoleContext, m_inspector);
309 String16 title = helper.firstArgToString(String16("default"), false);
310 String16 identifier = identifierFromTitleOrStackTrace(
311 title, helper, consoleContext, m_inspector);
312
313 int count =
314 helper.consoleMessageStorage()->count(helper.contextId(), identifier);
315 String16 countString = String16::fromInteger(count);
316 helper.reportCallWithArgument(
317 ConsoleAPIType::kCount,
318 title.isEmpty() ? countString : (title + ": " + countString));
319 }
320
CountReset(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)321 void V8Console::CountReset(const v8::debug::ConsoleCallArguments& info,
322 const v8::debug::ConsoleContext& consoleContext) {
323 ConsoleHelper helper(info, consoleContext, m_inspector);
324 String16 title = helper.firstArgToString(String16("default"), false);
325 String16 identifier = identifierFromTitleOrStackTrace(
326 title, helper, consoleContext, m_inspector);
327
328 if (!helper.consoleMessageStorage()->countReset(helper.contextId(),
329 identifier)) {
330 helper.reportCallWithArgument(ConsoleAPIType::kWarning,
331 "Count for '" + title + "' does not exist");
332 }
333 }
334
Assert(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)335 void V8Console::Assert(const v8::debug::ConsoleCallArguments& info,
336 const v8::debug::ConsoleContext& consoleContext) {
337 ConsoleHelper helper(info, consoleContext, m_inspector);
338 DCHECK(!helper.firstArgToBoolean(false));
339
340 std::vector<v8::Local<v8::Value>> arguments;
341 for (int i = 1; i < info.Length(); ++i) arguments.push_back(info[i]);
342 if (info.Length() < 2)
343 arguments.push_back(
344 toV8String(m_inspector->isolate(), String16("console.assert")));
345 helper.reportCall(ConsoleAPIType::kAssert, arguments);
346 m_inspector->debugger()->breakProgramOnAssert(helper.groupId());
347 }
348
Profile(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)349 void V8Console::Profile(const v8::debug::ConsoleCallArguments& info,
350 const v8::debug::ConsoleContext& consoleContext) {
351 ConsoleHelper helper(info, consoleContext, m_inspector);
352 helper.forEachSession([&helper](V8InspectorSessionImpl* session) {
353 session->profilerAgent()->consoleProfile(
354 helper.firstArgToString(String16()));
355 });
356 }
357
ProfileEnd(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)358 void V8Console::ProfileEnd(const v8::debug::ConsoleCallArguments& info,
359 const v8::debug::ConsoleContext& consoleContext) {
360 ConsoleHelper helper(info, consoleContext, m_inspector);
361 helper.forEachSession([&helper](V8InspectorSessionImpl* session) {
362 session->profilerAgent()->consoleProfileEnd(
363 helper.firstArgToString(String16()));
364 });
365 }
366
timeFunction(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext,bool timelinePrefix,V8InspectorImpl * inspector)367 static void timeFunction(const v8::debug::ConsoleCallArguments& info,
368 const v8::debug::ConsoleContext& consoleContext,
369 bool timelinePrefix, V8InspectorImpl* inspector) {
370 ConsoleHelper helper(info, consoleContext, inspector);
371 String16 protocolTitle = helper.firstArgToString("default", false);
372 if (timelinePrefix) protocolTitle = "Timeline '" + protocolTitle + "'";
373 const String16& timerId =
374 protocolTitle + "@" +
375 consoleContextToString(inspector->isolate(), consoleContext);
376 if (helper.consoleMessageStorage()->hasTimer(helper.contextId(), timerId)) {
377 helper.reportCallWithArgument(
378 ConsoleAPIType::kWarning,
379 "Timer '" + protocolTitle + "' already exists");
380 return;
381 }
382 inspector->client()->consoleTime(toStringView(protocolTitle));
383 helper.consoleMessageStorage()->time(helper.contextId(), timerId);
384 }
385
timeEndFunction(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext,bool timelinePrefix,V8InspectorImpl * inspector)386 static void timeEndFunction(const v8::debug::ConsoleCallArguments& info,
387 const v8::debug::ConsoleContext& consoleContext,
388 bool timelinePrefix, V8InspectorImpl* inspector) {
389 ConsoleHelper helper(info, consoleContext, inspector);
390 String16 protocolTitle = helper.firstArgToString("default", false);
391 if (timelinePrefix) protocolTitle = "Timeline '" + protocolTitle + "'";
392 const String16& timerId =
393 protocolTitle + "@" +
394 consoleContextToString(inspector->isolate(), consoleContext);
395 if (!helper.consoleMessageStorage()->hasTimer(helper.contextId(), timerId)) {
396 helper.reportCallWithArgument(
397 ConsoleAPIType::kWarning,
398 "Timer '" + protocolTitle + "' does not exist");
399 return;
400 }
401 inspector->client()->consoleTimeEnd(toStringView(protocolTitle));
402 double elapsed = helper.consoleMessageStorage()->timeEnd(
403 helper.contextId(),
404 protocolTitle + "@" +
405 consoleContextToString(inspector->isolate(), consoleContext));
406 String16 message =
407 protocolTitle + ": " + String16::fromDouble(elapsed) + "ms";
408 helper.reportCallWithArgument(ConsoleAPIType::kTimeEnd, message);
409 }
410
Time(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)411 void V8Console::Time(const v8::debug::ConsoleCallArguments& info,
412 const v8::debug::ConsoleContext& consoleContext) {
413 timeFunction(info, consoleContext, false, m_inspector);
414 }
415
TimeEnd(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)416 void V8Console::TimeEnd(const v8::debug::ConsoleCallArguments& info,
417 const v8::debug::ConsoleContext& consoleContext) {
418 timeEndFunction(info, consoleContext, false, m_inspector);
419 }
420
TimeStamp(const v8::debug::ConsoleCallArguments & info,const v8::debug::ConsoleContext & consoleContext)421 void V8Console::TimeStamp(const v8::debug::ConsoleCallArguments& info,
422 const v8::debug::ConsoleContext& consoleContext) {
423 ConsoleHelper helper(info, consoleContext, m_inspector);
424 String16 title = helper.firstArgToString(String16());
425 m_inspector->client()->consoleTimeStamp(toStringView(title));
426 }
427
memoryGetterCallback(const v8::FunctionCallbackInfo<v8::Value> & info)428 void V8Console::memoryGetterCallback(
429 const v8::FunctionCallbackInfo<v8::Value>& info) {
430 v8::Local<v8::Value> memoryValue;
431 if (!m_inspector->client()
432 ->memoryInfo(info.GetIsolate(),
433 info.GetIsolate()->GetCurrentContext())
434 .ToLocal(&memoryValue))
435 return;
436 info.GetReturnValue().Set(memoryValue);
437 }
438
memorySetterCallback(const v8::FunctionCallbackInfo<v8::Value> & info)439 void V8Console::memorySetterCallback(
440 const v8::FunctionCallbackInfo<v8::Value>& info) {
441 // We can't make the attribute readonly as it breaks existing code that relies
442 // on being able to assign to console.memory in strict mode. Instead, the
443 // setter just ignores the passed value. http://crbug.com/468611
444 }
445
keysCallback(const v8::FunctionCallbackInfo<v8::Value> & info,int sessionId)446 void V8Console::keysCallback(const v8::FunctionCallbackInfo<v8::Value>& info,
447 int sessionId) {
448 v8::Isolate* isolate = info.GetIsolate();
449 info.GetReturnValue().Set(v8::Array::New(isolate));
450
451 v8::debug::ConsoleCallArguments args(info);
452 ConsoleHelper helper(args, v8::debug::ConsoleContext(), m_inspector);
453 v8::Local<v8::Object> obj;
454 if (!helper.firstArgAsObject().ToLocal(&obj)) return;
455 v8::Local<v8::Array> names;
456 if (!obj->GetOwnPropertyNames(isolate->GetCurrentContext()).ToLocal(&names))
457 return;
458 info.GetReturnValue().Set(names);
459 }
460
valuesCallback(const v8::FunctionCallbackInfo<v8::Value> & info,int sessionId)461 void V8Console::valuesCallback(const v8::FunctionCallbackInfo<v8::Value>& info,
462 int sessionId) {
463 v8::Isolate* isolate = info.GetIsolate();
464 info.GetReturnValue().Set(v8::Array::New(isolate));
465
466 v8::debug::ConsoleCallArguments args(info);
467 ConsoleHelper helper(args, v8::debug::ConsoleContext(), m_inspector);
468 v8::Local<v8::Object> obj;
469 if (!helper.firstArgAsObject().ToLocal(&obj)) return;
470 v8::Local<v8::Array> names;
471 v8::Local<v8::Context> context = isolate->GetCurrentContext();
472 if (!obj->GetOwnPropertyNames(context).ToLocal(&names)) return;
473 v8::Local<v8::Array> values = v8::Array::New(isolate, names->Length());
474 for (uint32_t i = 0; i < names->Length(); ++i) {
475 v8::Local<v8::Value> key;
476 if (!names->Get(context, i).ToLocal(&key)) continue;
477 v8::Local<v8::Value> value;
478 if (!obj->Get(context, key).ToLocal(&value)) continue;
479 createDataProperty(context, values, i, value);
480 }
481 info.GetReturnValue().Set(values);
482 }
483
setFunctionBreakpoint(ConsoleHelper & helper,int sessionId,v8::Local<v8::Function> function,V8DebuggerAgentImpl::BreakpointSource source,v8::Local<v8::String> condition,bool enable)484 static void setFunctionBreakpoint(ConsoleHelper& helper, int sessionId,
485 v8::Local<v8::Function> function,
486 V8DebuggerAgentImpl::BreakpointSource source,
487 v8::Local<v8::String> condition,
488 bool enable) {
489 V8InspectorSessionImpl* session = helper.session(sessionId);
490 if (session == nullptr) return;
491 if (!session->debuggerAgent()->enabled()) return;
492 if (enable) {
493 session->debuggerAgent()->setBreakpointFor(function, condition, source);
494 } else {
495 session->debuggerAgent()->removeBreakpointFor(function, source);
496 }
497 }
498
debugFunctionCallback(const v8::FunctionCallbackInfo<v8::Value> & info,int sessionId)499 void V8Console::debugFunctionCallback(
500 const v8::FunctionCallbackInfo<v8::Value>& info, int sessionId) {
501 v8::debug::ConsoleCallArguments args(info);
502 ConsoleHelper helper(args, v8::debug::ConsoleContext(), m_inspector);
503 v8::Local<v8::Function> function;
504 v8::Local<v8::String> condition;
505 if (!helper.firstArgAsFunction().ToLocal(&function)) return;
506 if (args.Length() > 1 && args[1]->IsString()) {
507 condition = args[1].As<v8::String>();
508 }
509 setFunctionBreakpoint(helper, sessionId, function,
510 V8DebuggerAgentImpl::DebugCommandBreakpointSource,
511 condition, true);
512 }
513
undebugFunctionCallback(const v8::FunctionCallbackInfo<v8::Value> & info,int sessionId)514 void V8Console::undebugFunctionCallback(
515 const v8::FunctionCallbackInfo<v8::Value>& info, int sessionId) {
516 v8::debug::ConsoleCallArguments args(info);
517 ConsoleHelper helper(args, v8::debug::ConsoleContext(), m_inspector);
518 v8::Local<v8::Function> function;
519 if (!helper.firstArgAsFunction().ToLocal(&function)) return;
520 setFunctionBreakpoint(helper, sessionId, function,
521 V8DebuggerAgentImpl::DebugCommandBreakpointSource,
522 v8::Local<v8::String>(), false);
523 }
524
monitorFunctionCallback(const v8::FunctionCallbackInfo<v8::Value> & info,int sessionId)525 void V8Console::monitorFunctionCallback(
526 const v8::FunctionCallbackInfo<v8::Value>& info, int sessionId) {
527 v8::debug::ConsoleCallArguments args(info);
528 ConsoleHelper helper(args, v8::debug::ConsoleContext(), m_inspector);
529 v8::Local<v8::Function> function;
530 if (!helper.firstArgAsFunction().ToLocal(&function)) return;
531 v8::Local<v8::Value> name = function->GetName();
532 if (!name->IsString() || !v8::Local<v8::String>::Cast(name)->Length())
533 name = function->GetInferredName();
534 String16 functionName =
535 toProtocolStringWithTypeCheck(info.GetIsolate(), name);
536 String16Builder builder;
537 builder.append("console.log(\"function ");
538 if (functionName.isEmpty())
539 builder.append("(anonymous function)");
540 else
541 builder.append(functionName);
542 builder.append(
543 " called\" + (arguments.length > 0 ? \" with arguments: \" + "
544 "Array.prototype.join.call(arguments, \", \") : \"\")) && false");
545 setFunctionBreakpoint(helper, sessionId, function,
546 V8DebuggerAgentImpl::MonitorCommandBreakpointSource,
547 toV8String(info.GetIsolate(), builder.toString()),
548 true);
549 }
550
unmonitorFunctionCallback(const v8::FunctionCallbackInfo<v8::Value> & info,int sessionId)551 void V8Console::unmonitorFunctionCallback(
552 const v8::FunctionCallbackInfo<v8::Value>& info, int sessionId) {
553 v8::debug::ConsoleCallArguments args(info);
554 ConsoleHelper helper(args, v8::debug::ConsoleContext(), m_inspector);
555 v8::Local<v8::Function> function;
556 if (!helper.firstArgAsFunction().ToLocal(&function)) return;
557 setFunctionBreakpoint(helper, sessionId, function,
558 V8DebuggerAgentImpl::MonitorCommandBreakpointSource,
559 v8::Local<v8::String>(), false);
560 }
561
lastEvaluationResultCallback(const v8::FunctionCallbackInfo<v8::Value> & info,int sessionId)562 void V8Console::lastEvaluationResultCallback(
563 const v8::FunctionCallbackInfo<v8::Value>& info, int sessionId) {
564 v8::debug::ConsoleCallArguments args(info);
565 ConsoleHelper helper(args, v8::debug::ConsoleContext(), m_inspector);
566 InjectedScript* injectedScript = helper.injectedScript(sessionId);
567 if (!injectedScript) return;
568 info.GetReturnValue().Set(injectedScript->lastEvaluationResult());
569 }
570
inspectImpl(const v8::FunctionCallbackInfo<v8::Value> & info,v8::Local<v8::Value> value,int sessionId,InspectRequest request,V8InspectorImpl * inspector)571 static void inspectImpl(const v8::FunctionCallbackInfo<v8::Value>& info,
572 v8::Local<v8::Value> value, int sessionId,
573 InspectRequest request, V8InspectorImpl* inspector) {
574 if (request == kRegular) info.GetReturnValue().Set(value);
575
576 v8::debug::ConsoleCallArguments args(info);
577 ConsoleHelper helper(args, v8::debug::ConsoleContext(), inspector);
578 InjectedScript* injectedScript = helper.injectedScript(sessionId);
579 if (!injectedScript) return;
580 std::unique_ptr<protocol::Runtime::RemoteObject> wrappedObject;
581 protocol::Response response =
582 injectedScript->wrapObject(value, "", false /** forceValueType */,
583 false /** generatePreview */, &wrappedObject);
584 if (!response.isSuccess()) return;
585
586 std::unique_ptr<protocol::DictionaryValue> hints =
587 protocol::DictionaryValue::create();
588 if (request == kCopyToClipboard) {
589 hints->setBoolean("copyToClipboard", true);
590 } else if (request == kQueryObjects) {
591 hints->setBoolean("queryObjects", true);
592 }
593 if (V8InspectorSessionImpl* session = helper.session(sessionId)) {
594 session->runtimeAgent()->inspect(std::move(wrappedObject),
595 std::move(hints));
596 }
597 }
598
inspectCallback(const v8::FunctionCallbackInfo<v8::Value> & info,int sessionId)599 void V8Console::inspectCallback(const v8::FunctionCallbackInfo<v8::Value>& info,
600 int sessionId) {
601 if (info.Length() < 1) return;
602 inspectImpl(info, info[0], sessionId, kRegular, m_inspector);
603 }
604
copyCallback(const v8::FunctionCallbackInfo<v8::Value> & info,int sessionId)605 void V8Console::copyCallback(const v8::FunctionCallbackInfo<v8::Value>& info,
606 int sessionId) {
607 if (info.Length() < 1) return;
608 inspectImpl(info, info[0], sessionId, kCopyToClipboard, m_inspector);
609 }
610
queryObjectsCallback(const v8::FunctionCallbackInfo<v8::Value> & info,int sessionId)611 void V8Console::queryObjectsCallback(
612 const v8::FunctionCallbackInfo<v8::Value>& info, int sessionId) {
613 if (info.Length() < 1) return;
614 v8::Local<v8::Value> arg = info[0];
615 if (arg->IsFunction()) {
616 v8::Isolate* isolate = info.GetIsolate();
617 v8::TryCatch tryCatch(isolate);
618 v8::Local<v8::Value> prototype;
619 if (arg.As<v8::Function>()
620 ->Get(isolate->GetCurrentContext(),
621 toV8StringInternalized(isolate, "prototype"))
622 .ToLocal(&prototype) &&
623 prototype->IsObject()) {
624 arg = prototype;
625 }
626 if (tryCatch.HasCaught()) {
627 tryCatch.ReThrow();
628 return;
629 }
630 }
631 inspectImpl(info, arg, sessionId, kQueryObjects, m_inspector);
632 }
633
inspectedObject(const v8::FunctionCallbackInfo<v8::Value> & info,int sessionId,unsigned num)634 void V8Console::inspectedObject(const v8::FunctionCallbackInfo<v8::Value>& info,
635 int sessionId, unsigned num) {
636 DCHECK_GT(V8InspectorSessionImpl::kInspectedObjectBufferSize, num);
637 v8::debug::ConsoleCallArguments args(info);
638 ConsoleHelper helper(args, v8::debug::ConsoleContext(), m_inspector);
639 if (V8InspectorSessionImpl* session = helper.session(sessionId)) {
640 V8InspectorSession::Inspectable* object = session->inspectedObject(num);
641 v8::Isolate* isolate = info.GetIsolate();
642 if (object)
643 info.GetReturnValue().Set(object->get(isolate->GetCurrentContext()));
644 else
645 info.GetReturnValue().Set(v8::Undefined(isolate));
646 }
647 }
648
installMemoryGetter(v8::Local<v8::Context> context,v8::Local<v8::Object> console)649 void V8Console::installMemoryGetter(v8::Local<v8::Context> context,
650 v8::Local<v8::Object> console) {
651 v8::Isolate* isolate = context->GetIsolate();
652 v8::Local<v8::External> data = v8::External::New(isolate, this);
653 console->SetAccessorProperty(
654 toV8StringInternalized(isolate, "memory"),
655 v8::Function::New(
656 context, &V8Console::call<&V8Console::memoryGetterCallback>, data, 0,
657 v8::ConstructorBehavior::kThrow, v8::SideEffectType::kHasNoSideEffect)
658 .ToLocalChecked(),
659 v8::Function::New(context,
660 &V8Console::call<&V8Console::memorySetterCallback>,
661 data, 0, v8::ConstructorBehavior::kThrow)
662 .ToLocalChecked(),
663 static_cast<v8::PropertyAttribute>(v8::None), v8::DEFAULT);
664 }
665
createCommandLineAPI(v8::Local<v8::Context> context,int sessionId)666 v8::Local<v8::Object> V8Console::createCommandLineAPI(
667 v8::Local<v8::Context> context, int sessionId) {
668 v8::Isolate* isolate = context->GetIsolate();
669 v8::MicrotasksScope microtasksScope(isolate,
670 v8::MicrotasksScope::kDoNotRunMicrotasks);
671
672 v8::Local<v8::Object> commandLineAPI = v8::Object::New(isolate);
673 bool success =
674 commandLineAPI->SetPrototype(context, v8::Null(isolate)).FromMaybe(false);
675 DCHECK(success);
676 USE(success);
677
678 v8::Local<v8::ArrayBuffer> data =
679 v8::ArrayBuffer::New(isolate, sizeof(CommandLineAPIData));
680 *static_cast<CommandLineAPIData*>(data->GetContents().Data()) =
681 CommandLineAPIData(this, sessionId);
682 createBoundFunctionProperty(context, commandLineAPI, data, "dir",
683 &V8Console::call<&V8Console::Dir>,
684 "function dir(value) { [Command Line API] }");
685 createBoundFunctionProperty(context, commandLineAPI, data, "dirxml",
686 &V8Console::call<&V8Console::DirXml>,
687 "function dirxml(value) { [Command Line API] }");
688 createBoundFunctionProperty(context, commandLineAPI, data, "profile",
689 &V8Console::call<&V8Console::Profile>,
690 "function profile(title) { [Command Line API] }");
691 createBoundFunctionProperty(
692 context, commandLineAPI, data, "profileEnd",
693 &V8Console::call<&V8Console::ProfileEnd>,
694 "function profileEnd(title) { [Command Line API] }");
695 createBoundFunctionProperty(context, commandLineAPI, data, "clear",
696 &V8Console::call<&V8Console::Clear>,
697 "function clear() { [Command Line API] }");
698 createBoundFunctionProperty(
699 context, commandLineAPI, data, "table",
700 &V8Console::call<&V8Console::Table>,
701 "function table(data, [columns]) { [Command Line API] }");
702
703 createBoundFunctionProperty(context, commandLineAPI, data, "keys",
704 &V8Console::call<&V8Console::keysCallback>,
705 "function keys(object) { [Command Line API] }",
706 v8::SideEffectType::kHasNoSideEffect);
707 createBoundFunctionProperty(context, commandLineAPI, data, "values",
708 &V8Console::call<&V8Console::valuesCallback>,
709 "function values(object) { [Command Line API] }",
710 v8::SideEffectType::kHasNoSideEffect);
711 createBoundFunctionProperty(
712 context, commandLineAPI, data, "debug",
713 &V8Console::call<&V8Console::debugFunctionCallback>,
714 "function debug(function, condition) { [Command Line API] }");
715 createBoundFunctionProperty(
716 context, commandLineAPI, data, "undebug",
717 &V8Console::call<&V8Console::undebugFunctionCallback>,
718 "function undebug(function) { [Command Line API] }");
719 createBoundFunctionProperty(
720 context, commandLineAPI, data, "monitor",
721 &V8Console::call<&V8Console::monitorFunctionCallback>,
722 "function monitor(function) { [Command Line API] }");
723 createBoundFunctionProperty(
724 context, commandLineAPI, data, "unmonitor",
725 &V8Console::call<&V8Console::unmonitorFunctionCallback>,
726 "function unmonitor(function) { [Command Line API] }");
727 createBoundFunctionProperty(
728 context, commandLineAPI, data, "inspect",
729 &V8Console::call<&V8Console::inspectCallback>,
730 "function inspect(object) { [Command Line API] }");
731 createBoundFunctionProperty(context, commandLineAPI, data, "copy",
732 &V8Console::call<&V8Console::copyCallback>,
733 "function copy(value) { [Command Line API] }");
734 createBoundFunctionProperty(
735 context, commandLineAPI, data, "queryObjects",
736 &V8Console::call<&V8Console::queryObjectsCallback>,
737 "function queryObjects(constructor) { [Command Line API] }");
738 createBoundFunctionProperty(
739 context, commandLineAPI, data, "$_",
740 &V8Console::call<&V8Console::lastEvaluationResultCallback>, nullptr,
741 v8::SideEffectType::kHasNoSideEffect);
742 createBoundFunctionProperty(context, commandLineAPI, data, "$0",
743 &V8Console::call<&V8Console::inspectedObject0>,
744 nullptr, v8::SideEffectType::kHasNoSideEffect);
745 createBoundFunctionProperty(context, commandLineAPI, data, "$1",
746 &V8Console::call<&V8Console::inspectedObject1>,
747 nullptr, v8::SideEffectType::kHasNoSideEffect);
748 createBoundFunctionProperty(context, commandLineAPI, data, "$2",
749 &V8Console::call<&V8Console::inspectedObject2>,
750 nullptr, v8::SideEffectType::kHasNoSideEffect);
751 createBoundFunctionProperty(context, commandLineAPI, data, "$3",
752 &V8Console::call<&V8Console::inspectedObject3>,
753 nullptr, v8::SideEffectType::kHasNoSideEffect);
754 createBoundFunctionProperty(context, commandLineAPI, data, "$4",
755 &V8Console::call<&V8Console::inspectedObject4>,
756 nullptr, v8::SideEffectType::kHasNoSideEffect);
757
758 m_inspector->client()->installAdditionalCommandLineAPI(context,
759 commandLineAPI);
760 return commandLineAPI;
761 }
762
isCommandLineAPIGetter(const String16 & name)763 static bool isCommandLineAPIGetter(const String16& name) {
764 if (name.length() != 2) return false;
765 // $0 ... $4, $_
766 return name[0] == '$' &&
767 ((name[1] >= '0' && name[1] <= '4') || name[1] == '_');
768 }
769
accessorGetterCallback(v8::Local<v8::Name> name,const v8::PropertyCallbackInfo<v8::Value> & info)770 void V8Console::CommandLineAPIScope::accessorGetterCallback(
771 v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
772 CommandLineAPIScope* scope = static_cast<CommandLineAPIScope*>(
773 info.Data().As<v8::External>()->Value());
774 DCHECK(scope);
775
776 v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
777 if (scope->m_cleanup) {
778 bool removed = info.Holder()->Delete(context, name).FromMaybe(false);
779 DCHECK(removed);
780 USE(removed);
781 return;
782 }
783 v8::Local<v8::Object> commandLineAPI = scope->m_commandLineAPI;
784
785 v8::Local<v8::Value> value;
786 if (!commandLineAPI->Get(context, name).ToLocal(&value)) return;
787 if (isCommandLineAPIGetter(
788 toProtocolStringWithTypeCheck(info.GetIsolate(), name))) {
789 DCHECK(value->IsFunction());
790 v8::MicrotasksScope microtasks(info.GetIsolate(),
791 v8::MicrotasksScope::kDoNotRunMicrotasks);
792 if (value.As<v8::Function>()
793 ->Call(context, commandLineAPI, 0, nullptr)
794 .ToLocal(&value))
795 info.GetReturnValue().Set(value);
796 } else {
797 info.GetReturnValue().Set(value);
798 }
799 }
800
accessorSetterCallback(v8::Local<v8::Name> name,v8::Local<v8::Value> value,const v8::PropertyCallbackInfo<void> & info)801 void V8Console::CommandLineAPIScope::accessorSetterCallback(
802 v8::Local<v8::Name> name, v8::Local<v8::Value> value,
803 const v8::PropertyCallbackInfo<void>& info) {
804 CommandLineAPIScope* scope = static_cast<CommandLineAPIScope*>(
805 info.Data().As<v8::External>()->Value());
806 v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
807 if (!info.Holder()->Delete(context, name).FromMaybe(false)) return;
808 if (!info.Holder()->CreateDataProperty(context, name, value).FromMaybe(false))
809 return;
810 bool removed =
811 scope->m_installedMethods->Delete(context, name).FromMaybe(false);
812 DCHECK(removed);
813 USE(removed);
814 }
815
CommandLineAPIScope(v8::Local<v8::Context> context,v8::Local<v8::Object> commandLineAPI,v8::Local<v8::Object> global)816 V8Console::CommandLineAPIScope::CommandLineAPIScope(
817 v8::Local<v8::Context> context, v8::Local<v8::Object> commandLineAPI,
818 v8::Local<v8::Object> global)
819 : m_context(context),
820 m_commandLineAPI(commandLineAPI),
821 m_global(global),
822 m_installedMethods(v8::Set::New(context->GetIsolate())),
823 m_cleanup(false) {
824 v8::MicrotasksScope microtasksScope(context->GetIsolate(),
825 v8::MicrotasksScope::kDoNotRunMicrotasks);
826 v8::Local<v8::Array> names;
827 if (!m_commandLineAPI->GetOwnPropertyNames(context).ToLocal(&names)) return;
828 v8::Local<v8::External> externalThis =
829 v8::External::New(context->GetIsolate(), this);
830 for (uint32_t i = 0; i < names->Length(); ++i) {
831 v8::Local<v8::Value> name;
832 if (!names->Get(context, i).ToLocal(&name) || !name->IsName()) continue;
833 if (m_global->Has(context, name).FromMaybe(true)) continue;
834 if (!m_installedMethods->Add(context, name).ToLocal(&m_installedMethods))
835 continue;
836 if (!m_global
837 ->SetAccessor(context, v8::Local<v8::Name>::Cast(name),
838 CommandLineAPIScope::accessorGetterCallback,
839 CommandLineAPIScope::accessorSetterCallback,
840 externalThis, v8::DEFAULT, v8::DontEnum,
841 v8::SideEffectType::kHasNoSideEffect)
842 .FromMaybe(false)) {
843 bool removed = m_installedMethods->Delete(context, name).FromMaybe(false);
844 DCHECK(removed);
845 USE(removed);
846 continue;
847 }
848 }
849 }
850
~CommandLineAPIScope()851 V8Console::CommandLineAPIScope::~CommandLineAPIScope() {
852 v8::MicrotasksScope microtasksScope(m_context->GetIsolate(),
853 v8::MicrotasksScope::kDoNotRunMicrotasks);
854 m_cleanup = true;
855 v8::Local<v8::Array> names = m_installedMethods->AsArray();
856 for (uint32_t i = 0; i < names->Length(); ++i) {
857 v8::Local<v8::Value> name;
858 if (!names->Get(m_context, i).ToLocal(&name) || !name->IsName()) continue;
859 if (name->IsString()) {
860 v8::Local<v8::Value> descriptor;
861 bool success = m_global
862 ->GetOwnPropertyDescriptor(
863 m_context, v8::Local<v8::String>::Cast(name))
864 .ToLocal(&descriptor);
865 DCHECK(success);
866 USE(success);
867 }
868 }
869 }
870
871 } // namespace v8_inspector
872