• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/inspector/v8-console.h"
6 
7 #include "src/base/macros.h"
8 #include "src/inspector/injected-script.h"
9 #include "src/inspector/inspected-context.h"
10 #include "src/inspector/string-util.h"
11 #include "src/inspector/v8-console-message.h"
12 #include "src/inspector/v8-debugger-agent-impl.h"
13 #include "src/inspector/v8-inspector-impl.h"
14 #include "src/inspector/v8-inspector-session-impl.h"
15 #include "src/inspector/v8-profiler-agent-impl.h"
16 #include "src/inspector/v8-runtime-agent-impl.h"
17 #include "src/inspector/v8-stack-trace-impl.h"
18 #include "src/inspector/v8-value-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