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