OLD | NEW |
| (Empty) |
1 // Copyright 2015 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/V8DebuggerAgentImpl.h" | |
6 | |
7 #include "src/inspector/InjectedScript.h" | |
8 #include "src/inspector/InspectedContext.h" | |
9 #include "src/inspector/JavaScriptCallFrame.h" | |
10 #include "src/inspector/RemoteObjectId.h" | |
11 #include "src/inspector/ScriptBreakpoint.h" | |
12 #include "src/inspector/SearchUtil.h" | |
13 #include "src/inspector/StringUtil.h" | |
14 #include "src/inspector/V8Debugger.h" | |
15 #include "src/inspector/V8DebuggerScript.h" | |
16 #include "src/inspector/V8InspectorImpl.h" | |
17 #include "src/inspector/V8InspectorSessionImpl.h" | |
18 #include "src/inspector/V8Regex.h" | |
19 #include "src/inspector/V8RuntimeAgentImpl.h" | |
20 #include "src/inspector/V8StackTraceImpl.h" | |
21 #include "src/inspector/protocol/Protocol.h" | |
22 | |
23 #include "include/v8-inspector.h" | |
24 | |
25 #include <algorithm> | |
26 | |
27 namespace v8_inspector { | |
28 | |
29 using protocol::Array; | |
30 using protocol::Maybe; | |
31 using protocol::Debugger::BreakpointId; | |
32 using protocol::Debugger::CallFrame; | |
33 using protocol::Runtime::ExceptionDetails; | |
34 using protocol::Runtime::ScriptId; | |
35 using protocol::Runtime::StackTrace; | |
36 using protocol::Runtime::RemoteObject; | |
37 | |
38 namespace DebuggerAgentState { | |
39 static const char javaScriptBreakpoints[] = "javaScriptBreakopints"; | |
40 static const char pauseOnExceptionsState[] = "pauseOnExceptionsState"; | |
41 static const char asyncCallStackDepth[] = "asyncCallStackDepth"; | |
42 static const char blackboxPattern[] = "blackboxPattern"; | |
43 static const char debuggerEnabled[] = "debuggerEnabled"; | |
44 | |
45 // Breakpoint properties. | |
46 static const char url[] = "url"; | |
47 static const char isRegex[] = "isRegex"; | |
48 static const char lineNumber[] = "lineNumber"; | |
49 static const char columnNumber[] = "columnNumber"; | |
50 static const char condition[] = "condition"; | |
51 static const char skipAllPauses[] = "skipAllPauses"; | |
52 | |
53 } // namespace DebuggerAgentState; | |
54 | |
55 static const int maxSkipStepFrameCount = 128; | |
56 static const char backtraceObjectGroup[] = "backtrace"; | |
57 | |
58 static String16 breakpointIdSuffix( | |
59 V8DebuggerAgentImpl::BreakpointSource source) { | |
60 switch (source) { | |
61 case V8DebuggerAgentImpl::UserBreakpointSource: | |
62 break; | |
63 case V8DebuggerAgentImpl::DebugCommandBreakpointSource: | |
64 return ":debug"; | |
65 case V8DebuggerAgentImpl::MonitorCommandBreakpointSource: | |
66 return ":monitor"; | |
67 } | |
68 return String16(); | |
69 } | |
70 | |
71 static String16 generateBreakpointId( | |
72 const String16& scriptId, int lineNumber, int columnNumber, | |
73 V8DebuggerAgentImpl::BreakpointSource source) { | |
74 return scriptId + ":" + String16::fromInteger(lineNumber) + ":" + | |
75 String16::fromInteger(columnNumber) + breakpointIdSuffix(source); | |
76 } | |
77 | |
78 static bool positionComparator(const std::pair<int, int>& a, | |
79 const std::pair<int, int>& b) { | |
80 if (a.first != b.first) return a.first < b.first; | |
81 return a.second < b.second; | |
82 } | |
83 | |
84 static bool hasInternalError(ErrorString* errorString, bool hasError) { | |
85 if (hasError) *errorString = "Internal error"; | |
86 return hasError; | |
87 } | |
88 | |
89 static std::unique_ptr<protocol::Debugger::Location> buildProtocolLocation( | |
90 const String16& scriptId, int lineNumber, int columnNumber) { | |
91 return protocol::Debugger::Location::create() | |
92 .setScriptId(scriptId) | |
93 .setLineNumber(lineNumber) | |
94 .setColumnNumber(columnNumber) | |
95 .build(); | |
96 } | |
97 | |
98 V8DebuggerAgentImpl::V8DebuggerAgentImpl( | |
99 V8InspectorSessionImpl* session, protocol::FrontendChannel* frontendChannel, | |
100 protocol::DictionaryValue* state) | |
101 : m_inspector(session->inspector()), | |
102 m_debugger(m_inspector->debugger()), | |
103 m_session(session), | |
104 m_enabled(false), | |
105 m_state(state), | |
106 m_frontend(frontendChannel), | |
107 m_isolate(m_inspector->isolate()), | |
108 m_breakReason(protocol::Debugger::Paused::ReasonEnum::Other), | |
109 m_scheduledDebuggerStep(NoStep), | |
110 m_skipNextDebuggerStepOut(false), | |
111 m_javaScriptPauseScheduled(false), | |
112 m_steppingFromFramework(false), | |
113 m_pausingOnNativeEvent(false), | |
114 m_skippedStepFrameCount(0), | |
115 m_recursionLevelForStepOut(0), | |
116 m_recursionLevelForStepFrame(0), | |
117 m_skipAllPauses(false) { | |
118 clearBreakDetails(); | |
119 } | |
120 | |
121 V8DebuggerAgentImpl::~V8DebuggerAgentImpl() {} | |
122 | |
123 bool V8DebuggerAgentImpl::checkEnabled(ErrorString* errorString) { | |
124 if (enabled()) return true; | |
125 *errorString = "Debugger agent is not enabled"; | |
126 return false; | |
127 } | |
128 | |
129 void V8DebuggerAgentImpl::enable() { | |
130 // m_inspector->addListener may result in reporting all parsed scripts to | |
131 // the agent so it should already be in enabled state by then. | |
132 m_enabled = true; | |
133 m_state->setBoolean(DebuggerAgentState::debuggerEnabled, true); | |
134 m_debugger->enable(); | |
135 | |
136 std::vector<std::unique_ptr<V8DebuggerScript>> compiledScripts; | |
137 m_debugger->getCompiledScripts(m_session->contextGroupId(), compiledScripts); | |
138 for (size_t i = 0; i < compiledScripts.size(); i++) | |
139 didParseSource(std::move(compiledScripts[i]), true); | |
140 | |
141 // FIXME(WK44513): breakpoints activated flag should be synchronized between | |
142 // all front-ends | |
143 m_debugger->setBreakpointsActivated(true); | |
144 } | |
145 | |
146 bool V8DebuggerAgentImpl::enabled() { return m_enabled; } | |
147 | |
148 void V8DebuggerAgentImpl::enable(ErrorString* errorString) { | |
149 if (enabled()) return; | |
150 | |
151 if (!m_inspector->client()->canExecuteScripts(m_session->contextGroupId())) { | |
152 *errorString = "Script execution is prohibited"; | |
153 return; | |
154 } | |
155 | |
156 enable(); | |
157 } | |
158 | |
159 void V8DebuggerAgentImpl::disable(ErrorString*) { | |
160 if (!enabled()) return; | |
161 | |
162 m_state->setObject(DebuggerAgentState::javaScriptBreakpoints, | |
163 protocol::DictionaryValue::create()); | |
164 m_state->setInteger(DebuggerAgentState::pauseOnExceptionsState, | |
165 V8Debugger::DontPauseOnExceptions); | |
166 m_state->setInteger(DebuggerAgentState::asyncCallStackDepth, 0); | |
167 | |
168 if (!m_pausedContext.IsEmpty()) m_debugger->continueProgram(); | |
169 m_debugger->disable(); | |
170 m_pausedContext.Reset(); | |
171 JavaScriptCallFrames emptyCallFrames; | |
172 m_pausedCallFrames.swap(emptyCallFrames); | |
173 m_scripts.clear(); | |
174 m_blackboxedPositions.clear(); | |
175 m_breakpointIdToDebuggerBreakpointIds.clear(); | |
176 m_debugger->setAsyncCallStackDepth(this, 0); | |
177 m_continueToLocationBreakpointId = String16(); | |
178 clearBreakDetails(); | |
179 m_scheduledDebuggerStep = NoStep; | |
180 m_skipNextDebuggerStepOut = false; | |
181 m_javaScriptPauseScheduled = false; | |
182 m_steppingFromFramework = false; | |
183 m_pausingOnNativeEvent = false; | |
184 m_skippedStepFrameCount = 0; | |
185 m_recursionLevelForStepFrame = 0; | |
186 m_skipAllPauses = false; | |
187 m_blackboxPattern = nullptr; | |
188 m_state->remove(DebuggerAgentState::blackboxPattern); | |
189 m_enabled = false; | |
190 m_state->setBoolean(DebuggerAgentState::debuggerEnabled, false); | |
191 } | |
192 | |
193 void V8DebuggerAgentImpl::restore() { | |
194 DCHECK(!m_enabled); | |
195 if (!m_state->booleanProperty(DebuggerAgentState::debuggerEnabled, false)) | |
196 return; | |
197 if (!m_inspector->client()->canExecuteScripts(m_session->contextGroupId())) | |
198 return; | |
199 | |
200 enable(); | |
201 ErrorString error; | |
202 | |
203 int pauseState = V8Debugger::DontPauseOnExceptions; | |
204 m_state->getInteger(DebuggerAgentState::pauseOnExceptionsState, &pauseState); | |
205 setPauseOnExceptionsImpl(&error, pauseState); | |
206 DCHECK(error.isEmpty()); | |
207 | |
208 m_skipAllPauses = | |
209 m_state->booleanProperty(DebuggerAgentState::skipAllPauses, false); | |
210 | |
211 int asyncCallStackDepth = 0; | |
212 m_state->getInteger(DebuggerAgentState::asyncCallStackDepth, | |
213 &asyncCallStackDepth); | |
214 m_debugger->setAsyncCallStackDepth(this, asyncCallStackDepth); | |
215 | |
216 String16 blackboxPattern; | |
217 if (m_state->getString(DebuggerAgentState::blackboxPattern, | |
218 &blackboxPattern)) { | |
219 if (!setBlackboxPattern(&error, blackboxPattern)) UNREACHABLE(); | |
220 } | |
221 } | |
222 | |
223 void V8DebuggerAgentImpl::setBreakpointsActive(ErrorString* errorString, | |
224 bool active) { | |
225 if (!checkEnabled(errorString)) return; | |
226 m_debugger->setBreakpointsActivated(active); | |
227 } | |
228 | |
229 void V8DebuggerAgentImpl::setSkipAllPauses(ErrorString*, bool skip) { | |
230 m_skipAllPauses = skip; | |
231 m_state->setBoolean(DebuggerAgentState::skipAllPauses, m_skipAllPauses); | |
232 } | |
233 | |
234 static std::unique_ptr<protocol::DictionaryValue> | |
235 buildObjectForBreakpointCookie(const String16& url, int lineNumber, | |
236 int columnNumber, const String16& condition, | |
237 bool isRegex) { | |
238 std::unique_ptr<protocol::DictionaryValue> breakpointObject = | |
239 protocol::DictionaryValue::create(); | |
240 breakpointObject->setString(DebuggerAgentState::url, url); | |
241 breakpointObject->setInteger(DebuggerAgentState::lineNumber, lineNumber); | |
242 breakpointObject->setInteger(DebuggerAgentState::columnNumber, columnNumber); | |
243 breakpointObject->setString(DebuggerAgentState::condition, condition); | |
244 breakpointObject->setBoolean(DebuggerAgentState::isRegex, isRegex); | |
245 return breakpointObject; | |
246 } | |
247 | |
248 static bool matches(V8InspectorImpl* inspector, const String16& url, | |
249 const String16& pattern, bool isRegex) { | |
250 if (isRegex) { | |
251 V8Regex regex(inspector, pattern, true); | |
252 return regex.match(url) != -1; | |
253 } | |
254 return url == pattern; | |
255 } | |
256 | |
257 void V8DebuggerAgentImpl::setBreakpointByUrl( | |
258 ErrorString* errorString, int lineNumber, | |
259 const Maybe<String16>& optionalURL, const Maybe<String16>& optionalURLRegex, | |
260 const Maybe<int>& optionalColumnNumber, | |
261 const Maybe<String16>& optionalCondition, String16* outBreakpointId, | |
262 std::unique_ptr<protocol::Array<protocol::Debugger::Location>>* locations) { | |
263 *locations = Array<protocol::Debugger::Location>::create(); | |
264 if (optionalURL.isJust() == optionalURLRegex.isJust()) { | |
265 *errorString = "Either url or urlRegex must be specified."; | |
266 return; | |
267 } | |
268 | |
269 String16 url = optionalURL.isJust() ? optionalURL.fromJust() | |
270 : optionalURLRegex.fromJust(); | |
271 int columnNumber = 0; | |
272 if (optionalColumnNumber.isJust()) { | |
273 columnNumber = optionalColumnNumber.fromJust(); | |
274 if (columnNumber < 0) { | |
275 *errorString = "Incorrect column number"; | |
276 return; | |
277 } | |
278 } | |
279 String16 condition = optionalCondition.fromMaybe(""); | |
280 bool isRegex = optionalURLRegex.isJust(); | |
281 | |
282 String16 breakpointId = (isRegex ? "/" + url + "/" : url) + ":" + | |
283 String16::fromInteger(lineNumber) + ":" + | |
284 String16::fromInteger(columnNumber); | |
285 protocol::DictionaryValue* breakpointsCookie = | |
286 m_state->getObject(DebuggerAgentState::javaScriptBreakpoints); | |
287 if (!breakpointsCookie) { | |
288 std::unique_ptr<protocol::DictionaryValue> newValue = | |
289 protocol::DictionaryValue::create(); | |
290 breakpointsCookie = newValue.get(); | |
291 m_state->setObject(DebuggerAgentState::javaScriptBreakpoints, | |
292 std::move(newValue)); | |
293 } | |
294 if (breakpointsCookie->get(breakpointId)) { | |
295 *errorString = "Breakpoint at specified location already exists."; | |
296 return; | |
297 } | |
298 | |
299 breakpointsCookie->setObject( | |
300 breakpointId, buildObjectForBreakpointCookie( | |
301 url, lineNumber, columnNumber, condition, isRegex)); | |
302 | |
303 ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition); | |
304 for (const auto& script : m_scripts) { | |
305 if (!matches(m_inspector, script.second->sourceURL(), url, isRegex)) | |
306 continue; | |
307 std::unique_ptr<protocol::Debugger::Location> location = resolveBreakpoint( | |
308 breakpointId, script.first, breakpoint, UserBreakpointSource); | |
309 if (location) (*locations)->addItem(std::move(location)); | |
310 } | |
311 | |
312 *outBreakpointId = breakpointId; | |
313 } | |
314 | |
315 static bool parseLocation( | |
316 ErrorString* errorString, | |
317 std::unique_ptr<protocol::Debugger::Location> location, String16* scriptId, | |
318 int* lineNumber, int* columnNumber) { | |
319 *scriptId = location->getScriptId(); | |
320 *lineNumber = location->getLineNumber(); | |
321 *columnNumber = location->getColumnNumber(0); | |
322 return true; | |
323 } | |
324 | |
325 void V8DebuggerAgentImpl::setBreakpoint( | |
326 ErrorString* errorString, | |
327 std::unique_ptr<protocol::Debugger::Location> location, | |
328 const Maybe<String16>& optionalCondition, String16* outBreakpointId, | |
329 std::unique_ptr<protocol::Debugger::Location>* actualLocation) { | |
330 String16 scriptId; | |
331 int lineNumber; | |
332 int columnNumber; | |
333 | |
334 if (!parseLocation(errorString, std::move(location), &scriptId, &lineNumber, | |
335 &columnNumber)) | |
336 return; | |
337 | |
338 String16 condition = optionalCondition.fromMaybe(""); | |
339 | |
340 String16 breakpointId = generateBreakpointId( | |
341 scriptId, lineNumber, columnNumber, UserBreakpointSource); | |
342 if (m_breakpointIdToDebuggerBreakpointIds.find(breakpointId) != | |
343 m_breakpointIdToDebuggerBreakpointIds.end()) { | |
344 *errorString = "Breakpoint at specified location already exists."; | |
345 return; | |
346 } | |
347 ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition); | |
348 *actualLocation = resolveBreakpoint(breakpointId, scriptId, breakpoint, | |
349 UserBreakpointSource); | |
350 if (*actualLocation) | |
351 *outBreakpointId = breakpointId; | |
352 else | |
353 *errorString = "Could not resolve breakpoint"; | |
354 } | |
355 | |
356 void V8DebuggerAgentImpl::removeBreakpoint(ErrorString* errorString, | |
357 const String16& breakpointId) { | |
358 if (!checkEnabled(errorString)) return; | |
359 protocol::DictionaryValue* breakpointsCookie = | |
360 m_state->getObject(DebuggerAgentState::javaScriptBreakpoints); | |
361 if (breakpointsCookie) breakpointsCookie->remove(breakpointId); | |
362 removeBreakpoint(breakpointId); | |
363 } | |
364 | |
365 void V8DebuggerAgentImpl::removeBreakpoint(const String16& breakpointId) { | |
366 DCHECK(enabled()); | |
367 BreakpointIdToDebuggerBreakpointIdsMap::iterator | |
368 debuggerBreakpointIdsIterator = | |
369 m_breakpointIdToDebuggerBreakpointIds.find(breakpointId); | |
370 if (debuggerBreakpointIdsIterator == | |
371 m_breakpointIdToDebuggerBreakpointIds.end()) | |
372 return; | |
373 const std::vector<String16>& ids = debuggerBreakpointIdsIterator->second; | |
374 for (size_t i = 0; i < ids.size(); ++i) { | |
375 const String16& debuggerBreakpointId = ids[i]; | |
376 | |
377 m_debugger->removeBreakpoint(debuggerBreakpointId); | |
378 m_serverBreakpoints.erase(debuggerBreakpointId); | |
379 } | |
380 m_breakpointIdToDebuggerBreakpointIds.erase(breakpointId); | |
381 } | |
382 | |
383 void V8DebuggerAgentImpl::continueToLocation( | |
384 ErrorString* errorString, | |
385 std::unique_ptr<protocol::Debugger::Location> location) { | |
386 if (!checkEnabled(errorString)) return; | |
387 if (!m_continueToLocationBreakpointId.isEmpty()) { | |
388 m_debugger->removeBreakpoint(m_continueToLocationBreakpointId); | |
389 m_continueToLocationBreakpointId = ""; | |
390 } | |
391 | |
392 String16 scriptId; | |
393 int lineNumber; | |
394 int columnNumber; | |
395 | |
396 if (!parseLocation(errorString, std::move(location), &scriptId, &lineNumber, | |
397 &columnNumber)) | |
398 return; | |
399 | |
400 ScriptBreakpoint breakpoint(lineNumber, columnNumber, ""); | |
401 m_continueToLocationBreakpointId = m_debugger->setBreakpoint( | |
402 scriptId, breakpoint, &lineNumber, &columnNumber); | |
403 resume(errorString); | |
404 } | |
405 | |
406 bool V8DebuggerAgentImpl::isCurrentCallStackEmptyOrBlackboxed() { | |
407 DCHECK(enabled()); | |
408 JavaScriptCallFrames callFrames = m_debugger->currentCallFrames(); | |
409 for (size_t index = 0; index < callFrames.size(); ++index) { | |
410 if (!isCallFrameWithUnknownScriptOrBlackboxed(callFrames[index].get())) | |
411 return false; | |
412 } | |
413 return true; | |
414 } | |
415 | |
416 bool V8DebuggerAgentImpl::isTopPausedCallFrameBlackboxed() { | |
417 DCHECK(enabled()); | |
418 JavaScriptCallFrame* frame = | |
419 m_pausedCallFrames.size() ? m_pausedCallFrames[0].get() : nullptr; | |
420 return isCallFrameWithUnknownScriptOrBlackboxed(frame); | |
421 } | |
422 | |
423 bool V8DebuggerAgentImpl::isCallFrameWithUnknownScriptOrBlackboxed( | |
424 JavaScriptCallFrame* frame) { | |
425 if (!frame) return true; | |
426 ScriptsMap::iterator it = | |
427 m_scripts.find(String16::fromInteger(frame->sourceID())); | |
428 if (it == m_scripts.end()) { | |
429 // Unknown scripts are blackboxed. | |
430 return true; | |
431 } | |
432 if (m_blackboxPattern) { | |
433 const String16& scriptSourceURL = it->second->sourceURL(); | |
434 if (!scriptSourceURL.isEmpty() && | |
435 m_blackboxPattern->match(scriptSourceURL) != -1) | |
436 return true; | |
437 } | |
438 auto itBlackboxedPositions = | |
439 m_blackboxedPositions.find(String16::fromInteger(frame->sourceID())); | |
440 if (itBlackboxedPositions == m_blackboxedPositions.end()) return false; | |
441 | |
442 const std::vector<std::pair<int, int>>& ranges = | |
443 itBlackboxedPositions->second; | |
444 auto itRange = std::lower_bound( | |
445 ranges.begin(), ranges.end(), | |
446 std::make_pair(frame->line(), frame->column()), positionComparator); | |
447 // Ranges array contains positions in script where blackbox state is changed. | |
448 // [(0,0) ... ranges[0]) isn't blackboxed, [ranges[0] ... ranges[1]) is | |
449 // blackboxed... | |
450 return std::distance(ranges.begin(), itRange) % 2; | |
451 } | |
452 | |
453 V8DebuggerAgentImpl::SkipPauseRequest | |
454 V8DebuggerAgentImpl::shouldSkipExceptionPause( | |
455 JavaScriptCallFrame* topCallFrame) { | |
456 if (m_steppingFromFramework) return RequestNoSkip; | |
457 if (isCallFrameWithUnknownScriptOrBlackboxed(topCallFrame)) | |
458 return RequestContinue; | |
459 return RequestNoSkip; | |
460 } | |
461 | |
462 V8DebuggerAgentImpl::SkipPauseRequest V8DebuggerAgentImpl::shouldSkipStepPause( | |
463 JavaScriptCallFrame* topCallFrame) { | |
464 if (m_steppingFromFramework) return RequestNoSkip; | |
465 | |
466 if (m_skipNextDebuggerStepOut) { | |
467 m_skipNextDebuggerStepOut = false; | |
468 if (m_scheduledDebuggerStep == StepOut) return RequestStepOut; | |
469 } | |
470 | |
471 if (!isCallFrameWithUnknownScriptOrBlackboxed(topCallFrame)) | |
472 return RequestNoSkip; | |
473 | |
474 if (m_skippedStepFrameCount >= maxSkipStepFrameCount) return RequestStepOut; | |
475 | |
476 if (!m_skippedStepFrameCount) m_recursionLevelForStepFrame = 1; | |
477 | |
478 ++m_skippedStepFrameCount; | |
479 return RequestStepFrame; | |
480 } | |
481 | |
482 std::unique_ptr<protocol::Debugger::Location> | |
483 V8DebuggerAgentImpl::resolveBreakpoint(const String16& breakpointId, | |
484 const String16& scriptId, | |
485 const ScriptBreakpoint& breakpoint, | |
486 BreakpointSource source) { | |
487 DCHECK(enabled()); | |
488 // FIXME: remove these checks once crbug.com/520702 is resolved. | |
489 CHECK(!breakpointId.isEmpty()); | |
490 CHECK(!scriptId.isEmpty()); | |
491 ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId); | |
492 if (scriptIterator == m_scripts.end()) return nullptr; | |
493 if (breakpoint.lineNumber < scriptIterator->second->startLine() || | |
494 scriptIterator->second->endLine() < breakpoint.lineNumber) | |
495 return nullptr; | |
496 | |
497 int actualLineNumber; | |
498 int actualColumnNumber; | |
499 String16 debuggerBreakpointId = m_debugger->setBreakpoint( | |
500 scriptId, breakpoint, &actualLineNumber, &actualColumnNumber); | |
501 if (debuggerBreakpointId.isEmpty()) return nullptr; | |
502 | |
503 m_serverBreakpoints[debuggerBreakpointId] = | |
504 std::make_pair(breakpointId, source); | |
505 CHECK(!breakpointId.isEmpty()); | |
506 | |
507 m_breakpointIdToDebuggerBreakpointIds[breakpointId].push_back( | |
508 debuggerBreakpointId); | |
509 return buildProtocolLocation(scriptId, actualLineNumber, actualColumnNumber); | |
510 } | |
511 | |
512 void V8DebuggerAgentImpl::searchInContent( | |
513 ErrorString* error, const String16& scriptId, const String16& query, | |
514 const Maybe<bool>& optionalCaseSensitive, | |
515 const Maybe<bool>& optionalIsRegex, | |
516 std::unique_ptr<Array<protocol::Debugger::SearchMatch>>* results) { | |
517 v8::HandleScope handles(m_isolate); | |
518 ScriptsMap::iterator it = m_scripts.find(scriptId); | |
519 if (it == m_scripts.end()) { | |
520 *error = String16("No script for id: " + scriptId); | |
521 return; | |
522 } | |
523 | |
524 std::vector<std::unique_ptr<protocol::Debugger::SearchMatch>> matches = | |
525 searchInTextByLinesImpl(m_session, | |
526 toProtocolString(it->second->source(m_isolate)), | |
527 query, optionalCaseSensitive.fromMaybe(false), | |
528 optionalIsRegex.fromMaybe(false)); | |
529 *results = protocol::Array<protocol::Debugger::SearchMatch>::create(); | |
530 for (size_t i = 0; i < matches.size(); ++i) | |
531 (*results)->addItem(std::move(matches[i])); | |
532 } | |
533 | |
534 void V8DebuggerAgentImpl::setScriptSource( | |
535 ErrorString* errorString, const String16& scriptId, | |
536 const String16& newContent, const Maybe<bool>& dryRun, | |
537 Maybe<protocol::Array<protocol::Debugger::CallFrame>>* newCallFrames, | |
538 Maybe<bool>* stackChanged, Maybe<StackTrace>* asyncStackTrace, | |
539 Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) { | |
540 if (!checkEnabled(errorString)) return; | |
541 | |
542 v8::HandleScope handles(m_isolate); | |
543 v8::Local<v8::String> newSource = toV8String(m_isolate, newContent); | |
544 if (!m_debugger->setScriptSource(scriptId, newSource, dryRun.fromMaybe(false), | |
545 errorString, optOutCompileError, | |
546 &m_pausedCallFrames, stackChanged)) | |
547 return; | |
548 | |
549 ScriptsMap::iterator it = m_scripts.find(scriptId); | |
550 if (it != m_scripts.end()) it->second->setSource(m_isolate, newSource); | |
551 | |
552 std::unique_ptr<Array<CallFrame>> callFrames = currentCallFrames(errorString); | |
553 if (!callFrames) return; | |
554 *newCallFrames = std::move(callFrames); | |
555 *asyncStackTrace = currentAsyncStackTrace(); | |
556 } | |
557 | |
558 void V8DebuggerAgentImpl::restartFrame( | |
559 ErrorString* errorString, const String16& callFrameId, | |
560 std::unique_ptr<Array<CallFrame>>* newCallFrames, | |
561 Maybe<StackTrace>* asyncStackTrace) { | |
562 if (!assertPaused(errorString)) return; | |
563 InjectedScript::CallFrameScope scope( | |
564 errorString, m_inspector, m_session->contextGroupId(), callFrameId); | |
565 if (!scope.initialize()) return; | |
566 if (scope.frameOrdinal() >= m_pausedCallFrames.size()) { | |
567 *errorString = "Could not find call frame with given id"; | |
568 return; | |
569 } | |
570 | |
571 v8::Local<v8::Value> resultValue; | |
572 v8::Local<v8::Boolean> result; | |
573 if (!m_pausedCallFrames[scope.frameOrdinal()]->restart().ToLocal( | |
574 &resultValue) || | |
575 scope.tryCatch().HasCaught() || | |
576 !resultValue->ToBoolean(scope.context()).ToLocal(&result) || | |
577 !result->Value()) { | |
578 *errorString = "Internal error"; | |
579 return; | |
580 } | |
581 JavaScriptCallFrames frames = m_debugger->currentCallFrames(); | |
582 m_pausedCallFrames.swap(frames); | |
583 | |
584 *newCallFrames = currentCallFrames(errorString); | |
585 if (!*newCallFrames) return; | |
586 *asyncStackTrace = currentAsyncStackTrace(); | |
587 } | |
588 | |
589 void V8DebuggerAgentImpl::getScriptSource(ErrorString* error, | |
590 const String16& scriptId, | |
591 String16* scriptSource) { | |
592 if (!checkEnabled(error)) return; | |
593 ScriptsMap::iterator it = m_scripts.find(scriptId); | |
594 if (it == m_scripts.end()) { | |
595 *error = "No script for id: " + scriptId; | |
596 return; | |
597 } | |
598 v8::HandleScope handles(m_isolate); | |
599 *scriptSource = toProtocolString(it->second->source(m_isolate)); | |
600 } | |
601 | |
602 void V8DebuggerAgentImpl::schedulePauseOnNextStatement( | |
603 const String16& breakReason, | |
604 std::unique_ptr<protocol::DictionaryValue> data) { | |
605 if (!enabled() || m_scheduledDebuggerStep == StepInto || | |
606 m_javaScriptPauseScheduled || m_debugger->isPaused() || | |
607 !m_debugger->breakpointsActivated()) | |
608 return; | |
609 m_breakReason = breakReason; | |
610 m_breakAuxData = std::move(data); | |
611 m_pausingOnNativeEvent = true; | |
612 m_skipNextDebuggerStepOut = false; | |
613 m_debugger->setPauseOnNextStatement(true); | |
614 } | |
615 | |
616 void V8DebuggerAgentImpl::schedulePauseOnNextStatementIfSteppingInto() { | |
617 DCHECK(enabled()); | |
618 if (m_scheduledDebuggerStep != StepInto || m_javaScriptPauseScheduled || | |
619 m_debugger->isPaused()) | |
620 return; | |
621 clearBreakDetails(); | |
622 m_pausingOnNativeEvent = false; | |
623 m_skippedStepFrameCount = 0; | |
624 m_recursionLevelForStepFrame = 0; | |
625 m_debugger->setPauseOnNextStatement(true); | |
626 } | |
627 | |
628 void V8DebuggerAgentImpl::cancelPauseOnNextStatement() { | |
629 if (m_javaScriptPauseScheduled || m_debugger->isPaused()) return; | |
630 clearBreakDetails(); | |
631 m_pausingOnNativeEvent = false; | |
632 m_debugger->setPauseOnNextStatement(false); | |
633 } | |
634 | |
635 void V8DebuggerAgentImpl::pause(ErrorString* errorString) { | |
636 if (!checkEnabled(errorString)) return; | |
637 if (m_javaScriptPauseScheduled || m_debugger->isPaused()) return; | |
638 clearBreakDetails(); | |
639 m_javaScriptPauseScheduled = true; | |
640 m_scheduledDebuggerStep = NoStep; | |
641 m_skippedStepFrameCount = 0; | |
642 m_steppingFromFramework = false; | |
643 m_debugger->setPauseOnNextStatement(true); | |
644 } | |
645 | |
646 void V8DebuggerAgentImpl::resume(ErrorString* errorString) { | |
647 if (!assertPaused(errorString)) return; | |
648 m_scheduledDebuggerStep = NoStep; | |
649 m_steppingFromFramework = false; | |
650 m_session->releaseObjectGroup(backtraceObjectGroup); | |
651 m_debugger->continueProgram(); | |
652 } | |
653 | |
654 void V8DebuggerAgentImpl::stepOver(ErrorString* errorString) { | |
655 if (!assertPaused(errorString)) return; | |
656 // StepOver at function return point should fallback to StepInto. | |
657 JavaScriptCallFrame* frame = | |
658 !m_pausedCallFrames.empty() ? m_pausedCallFrames[0].get() : nullptr; | |
659 if (frame && frame->isAtReturn()) { | |
660 stepInto(errorString); | |
661 return; | |
662 } | |
663 m_scheduledDebuggerStep = StepOver; | |
664 m_steppingFromFramework = isTopPausedCallFrameBlackboxed(); | |
665 m_session->releaseObjectGroup(backtraceObjectGroup); | |
666 m_debugger->stepOverStatement(); | |
667 } | |
668 | |
669 void V8DebuggerAgentImpl::stepInto(ErrorString* errorString) { | |
670 if (!assertPaused(errorString)) return; | |
671 m_scheduledDebuggerStep = StepInto; | |
672 m_steppingFromFramework = isTopPausedCallFrameBlackboxed(); | |
673 m_session->releaseObjectGroup(backtraceObjectGroup); | |
674 m_debugger->stepIntoStatement(); | |
675 } | |
676 | |
677 void V8DebuggerAgentImpl::stepOut(ErrorString* errorString) { | |
678 if (!assertPaused(errorString)) return; | |
679 m_scheduledDebuggerStep = StepOut; | |
680 m_skipNextDebuggerStepOut = false; | |
681 m_recursionLevelForStepOut = 1; | |
682 m_steppingFromFramework = isTopPausedCallFrameBlackboxed(); | |
683 m_session->releaseObjectGroup(backtraceObjectGroup); | |
684 m_debugger->stepOutOfFunction(); | |
685 } | |
686 | |
687 void V8DebuggerAgentImpl::setPauseOnExceptions( | |
688 ErrorString* errorString, const String16& stringPauseState) { | |
689 if (!checkEnabled(errorString)) return; | |
690 V8Debugger::PauseOnExceptionsState pauseState; | |
691 if (stringPauseState == "none") { | |
692 pauseState = V8Debugger::DontPauseOnExceptions; | |
693 } else if (stringPauseState == "all") { | |
694 pauseState = V8Debugger::PauseOnAllExceptions; | |
695 } else if (stringPauseState == "uncaught") { | |
696 pauseState = V8Debugger::PauseOnUncaughtExceptions; | |
697 } else { | |
698 *errorString = "Unknown pause on exceptions mode: " + stringPauseState; | |
699 return; | |
700 } | |
701 setPauseOnExceptionsImpl(errorString, pauseState); | |
702 } | |
703 | |
704 void V8DebuggerAgentImpl::setPauseOnExceptionsImpl(ErrorString* errorString, | |
705 int pauseState) { | |
706 m_debugger->setPauseOnExceptionsState( | |
707 static_cast<V8Debugger::PauseOnExceptionsState>(pauseState)); | |
708 if (m_debugger->getPauseOnExceptionsState() != pauseState) | |
709 *errorString = "Internal error. Could not change pause on exceptions state"; | |
710 else | |
711 m_state->setInteger(DebuggerAgentState::pauseOnExceptionsState, pauseState); | |
712 } | |
713 | |
714 void V8DebuggerAgentImpl::evaluateOnCallFrame( | |
715 ErrorString* errorString, const String16& callFrameId, | |
716 const String16& expression, const Maybe<String16>& objectGroup, | |
717 const Maybe<bool>& includeCommandLineAPI, const Maybe<bool>& silent, | |
718 const Maybe<bool>& returnByValue, const Maybe<bool>& generatePreview, | |
719 std::unique_ptr<RemoteObject>* result, | |
720 Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) { | |
721 if (!assertPaused(errorString)) return; | |
722 InjectedScript::CallFrameScope scope( | |
723 errorString, m_inspector, m_session->contextGroupId(), callFrameId); | |
724 if (!scope.initialize()) return; | |
725 if (scope.frameOrdinal() >= m_pausedCallFrames.size()) { | |
726 *errorString = "Could not find call frame with given id"; | |
727 return; | |
728 } | |
729 | |
730 if (includeCommandLineAPI.fromMaybe(false) && !scope.installCommandLineAPI()) | |
731 return; | |
732 if (silent.fromMaybe(false)) scope.ignoreExceptionsAndMuteConsole(); | |
733 | |
734 v8::MaybeLocal<v8::Value> maybeResultValue = | |
735 m_pausedCallFrames[scope.frameOrdinal()]->evaluate( | |
736 toV8String(m_isolate, expression)); | |
737 | |
738 // Re-initialize after running client's code, as it could have destroyed | |
739 // context or session. | |
740 if (!scope.initialize()) return; | |
741 scope.injectedScript()->wrapEvaluateResult( | |
742 errorString, maybeResultValue, scope.tryCatch(), | |
743 objectGroup.fromMaybe(""), returnByValue.fromMaybe(false), | |
744 generatePreview.fromMaybe(false), result, exceptionDetails); | |
745 } | |
746 | |
747 void V8DebuggerAgentImpl::setVariableValue( | |
748 ErrorString* errorString, int scopeNumber, const String16& variableName, | |
749 std::unique_ptr<protocol::Runtime::CallArgument> newValueArgument, | |
750 const String16& callFrameId) { | |
751 if (!checkEnabled(errorString)) return; | |
752 if (!assertPaused(errorString)) return; | |
753 InjectedScript::CallFrameScope scope( | |
754 errorString, m_inspector, m_session->contextGroupId(), callFrameId); | |
755 if (!scope.initialize()) return; | |
756 | |
757 v8::Local<v8::Value> newValue; | |
758 if (!scope.injectedScript() | |
759 ->resolveCallArgument(errorString, newValueArgument.get()) | |
760 .ToLocal(&newValue)) | |
761 return; | |
762 | |
763 if (scope.frameOrdinal() >= m_pausedCallFrames.size()) { | |
764 *errorString = "Could not find call frame with given id"; | |
765 return; | |
766 } | |
767 v8::MaybeLocal<v8::Value> result = | |
768 m_pausedCallFrames[scope.frameOrdinal()]->setVariableValue( | |
769 scopeNumber, toV8String(m_isolate, variableName), newValue); | |
770 if (scope.tryCatch().HasCaught() || result.IsEmpty()) { | |
771 *errorString = "Internal error"; | |
772 return; | |
773 } | |
774 } | |
775 | |
776 void V8DebuggerAgentImpl::setAsyncCallStackDepth(ErrorString* errorString, | |
777 int depth) { | |
778 if (!checkEnabled(errorString)) return; | |
779 m_state->setInteger(DebuggerAgentState::asyncCallStackDepth, depth); | |
780 m_debugger->setAsyncCallStackDepth(this, depth); | |
781 } | |
782 | |
783 void V8DebuggerAgentImpl::setBlackboxPatterns( | |
784 ErrorString* errorString, | |
785 std::unique_ptr<protocol::Array<String16>> patterns) { | |
786 if (!patterns->length()) { | |
787 m_blackboxPattern = nullptr; | |
788 m_state->remove(DebuggerAgentState::blackboxPattern); | |
789 return; | |
790 } | |
791 | |
792 String16Builder patternBuilder; | |
793 patternBuilder.append('('); | |
794 for (size_t i = 0; i < patterns->length() - 1; ++i) { | |
795 patternBuilder.append(patterns->get(i)); | |
796 patternBuilder.append("|"); | |
797 } | |
798 patternBuilder.append(patterns->get(patterns->length() - 1)); | |
799 patternBuilder.append(')'); | |
800 String16 pattern = patternBuilder.toString(); | |
801 if (!setBlackboxPattern(errorString, pattern)) return; | |
802 m_state->setString(DebuggerAgentState::blackboxPattern, pattern); | |
803 } | |
804 | |
805 bool V8DebuggerAgentImpl::setBlackboxPattern(ErrorString* errorString, | |
806 const String16& pattern) { | |
807 std::unique_ptr<V8Regex> regex(new V8Regex( | |
808 m_inspector, pattern, true /** caseSensitive */, false /** multiline */)); | |
809 if (!regex->isValid()) { | |
810 *errorString = "Pattern parser error: " + regex->errorMessage(); | |
811 return false; | |
812 } | |
813 m_blackboxPattern = std::move(regex); | |
814 return true; | |
815 } | |
816 | |
817 void V8DebuggerAgentImpl::setBlackboxedRanges( | |
818 ErrorString* error, const String16& scriptId, | |
819 std::unique_ptr<protocol::Array<protocol::Debugger::ScriptPosition>> | |
820 inPositions) { | |
821 if (m_scripts.find(scriptId) == m_scripts.end()) { | |
822 *error = "No script with passed id."; | |
823 return; | |
824 } | |
825 | |
826 if (!inPositions->length()) { | |
827 m_blackboxedPositions.erase(scriptId); | |
828 return; | |
829 } | |
830 | |
831 std::vector<std::pair<int, int>> positions; | |
832 positions.reserve(inPositions->length()); | |
833 for (size_t i = 0; i < inPositions->length(); ++i) { | |
834 protocol::Debugger::ScriptPosition* position = inPositions->get(i); | |
835 if (position->getLineNumber() < 0) { | |
836 *error = "Position missing 'line' or 'line' < 0."; | |
837 return; | |
838 } | |
839 if (position->getColumnNumber() < 0) { | |
840 *error = "Position missing 'column' or 'column' < 0."; | |
841 return; | |
842 } | |
843 positions.push_back( | |
844 std::make_pair(position->getLineNumber(), position->getColumnNumber())); | |
845 } | |
846 | |
847 for (size_t i = 1; i < positions.size(); ++i) { | |
848 if (positions[i - 1].first < positions[i].first) continue; | |
849 if (positions[i - 1].first == positions[i].first && | |
850 positions[i - 1].second < positions[i].second) | |
851 continue; | |
852 *error = | |
853 "Input positions array is not sorted or contains duplicate values."; | |
854 return; | |
855 } | |
856 | |
857 m_blackboxedPositions[scriptId] = positions; | |
858 } | |
859 | |
860 void V8DebuggerAgentImpl::willExecuteScript(int scriptId) { | |
861 changeJavaScriptRecursionLevel(+1); | |
862 // Fast return. | |
863 if (m_scheduledDebuggerStep != StepInto) return; | |
864 schedulePauseOnNextStatementIfSteppingInto(); | |
865 } | |
866 | |
867 void V8DebuggerAgentImpl::didExecuteScript() { | |
868 changeJavaScriptRecursionLevel(-1); | |
869 } | |
870 | |
871 void V8DebuggerAgentImpl::changeJavaScriptRecursionLevel(int step) { | |
872 if (m_javaScriptPauseScheduled && !m_skipAllPauses && | |
873 !m_debugger->isPaused()) { | |
874 // Do not ever loose user's pause request until we have actually paused. | |
875 m_debugger->setPauseOnNextStatement(true); | |
876 } | |
877 if (m_scheduledDebuggerStep == StepOut) { | |
878 m_recursionLevelForStepOut += step; | |
879 if (!m_recursionLevelForStepOut) { | |
880 // When StepOut crosses a task boundary (i.e. js -> c++) from where it was | |
881 // requested, | |
882 // switch stepping to step into a next JS task, as if we exited to a | |
883 // blackboxed framework. | |
884 m_scheduledDebuggerStep = StepInto; | |
885 m_skipNextDebuggerStepOut = false; | |
886 } | |
887 } | |
888 if (m_recursionLevelForStepFrame) { | |
889 m_recursionLevelForStepFrame += step; | |
890 if (!m_recursionLevelForStepFrame) { | |
891 // We have walked through a blackboxed framework and got back to where we | |
892 // started. | |
893 // If there was no stepping scheduled, we should cancel the stepping | |
894 // explicitly, | |
895 // since there may be a scheduled StepFrame left. | |
896 // Otherwise, if we were stepping in/over, the StepFrame will stop at the | |
897 // right location, | |
898 // whereas if we were stepping out, we should continue doing so after | |
899 // debugger pauses | |
900 // from the old StepFrame. | |
901 m_skippedStepFrameCount = 0; | |
902 if (m_scheduledDebuggerStep == NoStep) | |
903 m_debugger->clearStepping(); | |
904 else if (m_scheduledDebuggerStep == StepOut) | |
905 m_skipNextDebuggerStepOut = true; | |
906 } | |
907 } | |
908 } | |
909 | |
910 std::unique_ptr<Array<CallFrame>> V8DebuggerAgentImpl::currentCallFrames( | |
911 ErrorString* errorString) { | |
912 if (m_pausedContext.IsEmpty() || !m_pausedCallFrames.size()) | |
913 return Array<CallFrame>::create(); | |
914 ErrorString ignored; | |
915 v8::HandleScope handles(m_isolate); | |
916 v8::Local<v8::Context> debuggerContext = | |
917 v8::Debug::GetDebugContext(m_isolate); | |
918 v8::Context::Scope contextScope(debuggerContext); | |
919 | |
920 v8::Local<v8::Array> objects = v8::Array::New(m_isolate); | |
921 | |
922 for (size_t frameOrdinal = 0; frameOrdinal < m_pausedCallFrames.size(); | |
923 ++frameOrdinal) { | |
924 const std::unique_ptr<JavaScriptCallFrame>& currentCallFrame = | |
925 m_pausedCallFrames[frameOrdinal]; | |
926 | |
927 v8::Local<v8::Object> details = currentCallFrame->details(); | |
928 if (hasInternalError(errorString, details.IsEmpty())) | |
929 return Array<CallFrame>::create(); | |
930 | |
931 int contextId = currentCallFrame->contextId(); | |
932 InjectedScript* injectedScript = | |
933 contextId ? m_session->findInjectedScript(&ignored, contextId) | |
934 : nullptr; | |
935 | |
936 String16 callFrameId = | |
937 RemoteCallFrameId::serialize(contextId, frameOrdinal); | |
938 if (hasInternalError( | |
939 errorString, | |
940 !details | |
941 ->Set(debuggerContext, | |
942 toV8StringInternalized(m_isolate, "callFrameId"), | |
943 toV8String(m_isolate, callFrameId)) | |
944 .FromMaybe(false))) | |
945 return Array<CallFrame>::create(); | |
946 | |
947 if (injectedScript) { | |
948 v8::Local<v8::Value> scopeChain; | |
949 if (hasInternalError( | |
950 errorString, | |
951 !details->Get(debuggerContext, | |
952 toV8StringInternalized(m_isolate, "scopeChain")) | |
953 .ToLocal(&scopeChain) || | |
954 !scopeChain->IsArray())) | |
955 return Array<CallFrame>::create(); | |
956 v8::Local<v8::Array> scopeChainArray = scopeChain.As<v8::Array>(); | |
957 if (!injectedScript->wrapPropertyInArray( | |
958 errorString, scopeChainArray, | |
959 toV8StringInternalized(m_isolate, "object"), | |
960 backtraceObjectGroup)) | |
961 return Array<CallFrame>::create(); | |
962 if (!injectedScript->wrapObjectProperty( | |
963 errorString, details, toV8StringInternalized(m_isolate, "this"), | |
964 backtraceObjectGroup)) | |
965 return Array<CallFrame>::create(); | |
966 if (details | |
967 ->Has(debuggerContext, | |
968 toV8StringInternalized(m_isolate, "returnValue")) | |
969 .FromMaybe(false)) { | |
970 if (!injectedScript->wrapObjectProperty( | |
971 errorString, details, | |
972 toV8StringInternalized(m_isolate, "returnValue"), | |
973 backtraceObjectGroup)) | |
974 return Array<CallFrame>::create(); | |
975 } | |
976 } else { | |
977 if (hasInternalError(errorString, !details | |
978 ->Set(debuggerContext, | |
979 toV8StringInternalized( | |
980 m_isolate, "scopeChain"), | |
981 v8::Array::New(m_isolate, 0)) | |
982 .FromMaybe(false))) | |
983 return Array<CallFrame>::create(); | |
984 v8::Local<v8::Object> remoteObject = v8::Object::New(m_isolate); | |
985 if (hasInternalError( | |
986 errorString, | |
987 !remoteObject | |
988 ->Set(debuggerContext, | |
989 toV8StringInternalized(m_isolate, "type"), | |
990 toV8StringInternalized(m_isolate, "undefined")) | |
991 .FromMaybe(false))) | |
992 return Array<CallFrame>::create(); | |
993 if (hasInternalError(errorString, | |
994 !details | |
995 ->Set(debuggerContext, | |
996 toV8StringInternalized(m_isolate, "this"), | |
997 remoteObject) | |
998 .FromMaybe(false))) | |
999 return Array<CallFrame>::create(); | |
1000 if (hasInternalError( | |
1001 errorString, | |
1002 !details | |
1003 ->Delete(debuggerContext, | |
1004 toV8StringInternalized(m_isolate, "returnValue")) | |
1005 .FromMaybe(false))) | |
1006 return Array<CallFrame>::create(); | |
1007 } | |
1008 | |
1009 if (hasInternalError(errorString, | |
1010 !objects->Set(debuggerContext, frameOrdinal, details) | |
1011 .FromMaybe(false))) | |
1012 return Array<CallFrame>::create(); | |
1013 } | |
1014 | |
1015 protocol::ErrorSupport errorSupport; | |
1016 std::unique_ptr<Array<CallFrame>> callFrames = Array<CallFrame>::parse( | |
1017 toProtocolValue(debuggerContext, objects).get(), &errorSupport); | |
1018 if (hasInternalError(errorString, !callFrames)) | |
1019 return Array<CallFrame>::create(); | |
1020 return callFrames; | |
1021 } | |
1022 | |
1023 std::unique_ptr<StackTrace> V8DebuggerAgentImpl::currentAsyncStackTrace() { | |
1024 if (m_pausedContext.IsEmpty()) return nullptr; | |
1025 V8StackTraceImpl* stackTrace = m_debugger->currentAsyncCallChain(); | |
1026 return stackTrace ? stackTrace->buildInspectorObjectForTail(m_debugger) | |
1027 : nullptr; | |
1028 } | |
1029 | |
1030 void V8DebuggerAgentImpl::didParseSource( | |
1031 std::unique_ptr<V8DebuggerScript> script, bool success) { | |
1032 v8::HandleScope handles(m_isolate); | |
1033 String16 scriptSource = toProtocolString(script->source(m_isolate)); | |
1034 if (!success) script->setSourceURL(findSourceURL(scriptSource, false)); | |
1035 if (!success) | |
1036 script->setSourceMappingURL(findSourceMapURL(scriptSource, false)); | |
1037 | |
1038 std::unique_ptr<protocol::DictionaryValue> executionContextAuxData; | |
1039 if (!script->executionContextAuxData().isEmpty()) | |
1040 executionContextAuxData = protocol::DictionaryValue::cast( | |
1041 protocol::parseJSON(script->executionContextAuxData())); | |
1042 bool isLiveEdit = script->isLiveEdit(); | |
1043 bool hasSourceURL = script->hasSourceURL(); | |
1044 String16 scriptId = script->scriptId(); | |
1045 String16 scriptURL = script->sourceURL(); | |
1046 | |
1047 const Maybe<String16>& sourceMapURLParam = script->sourceMappingURL(); | |
1048 const Maybe<protocol::DictionaryValue>& executionContextAuxDataParam( | |
1049 std::move(executionContextAuxData)); | |
1050 const bool* isLiveEditParam = isLiveEdit ? &isLiveEdit : nullptr; | |
1051 const bool* hasSourceURLParam = hasSourceURL ? &hasSourceURL : nullptr; | |
1052 if (success) | |
1053 m_frontend.scriptParsed( | |
1054 scriptId, scriptURL, script->startLine(), script->startColumn(), | |
1055 script->endLine(), script->endColumn(), script->executionContextId(), | |
1056 script->hash(), executionContextAuxDataParam, isLiveEditParam, | |
1057 sourceMapURLParam, hasSourceURLParam); | |
1058 else | |
1059 m_frontend.scriptFailedToParse( | |
1060 scriptId, scriptURL, script->startLine(), script->startColumn(), | |
1061 script->endLine(), script->endColumn(), script->executionContextId(), | |
1062 script->hash(), executionContextAuxDataParam, sourceMapURLParam, | |
1063 hasSourceURLParam); | |
1064 | |
1065 m_scripts[scriptId] = std::move(script); | |
1066 | |
1067 if (scriptURL.isEmpty() || !success) return; | |
1068 | |
1069 protocol::DictionaryValue* breakpointsCookie = | |
1070 m_state->getObject(DebuggerAgentState::javaScriptBreakpoints); | |
1071 if (!breakpointsCookie) return; | |
1072 | |
1073 for (size_t i = 0; i < breakpointsCookie->size(); ++i) { | |
1074 auto cookie = breakpointsCookie->at(i); | |
1075 protocol::DictionaryValue* breakpointObject = | |
1076 protocol::DictionaryValue::cast(cookie.second); | |
1077 bool isRegex; | |
1078 breakpointObject->getBoolean(DebuggerAgentState::isRegex, &isRegex); | |
1079 String16 url; | |
1080 breakpointObject->getString(DebuggerAgentState::url, &url); | |
1081 if (!matches(m_inspector, scriptURL, url, isRegex)) continue; | |
1082 ScriptBreakpoint breakpoint; | |
1083 breakpointObject->getInteger(DebuggerAgentState::lineNumber, | |
1084 &breakpoint.lineNumber); | |
1085 breakpointObject->getInteger(DebuggerAgentState::columnNumber, | |
1086 &breakpoint.columnNumber); | |
1087 breakpointObject->getString(DebuggerAgentState::condition, | |
1088 &breakpoint.condition); | |
1089 std::unique_ptr<protocol::Debugger::Location> location = resolveBreakpoint( | |
1090 cookie.first, scriptId, breakpoint, UserBreakpointSource); | |
1091 if (location) | |
1092 m_frontend.breakpointResolved(cookie.first, std::move(location)); | |
1093 } | |
1094 } | |
1095 | |
1096 V8DebuggerAgentImpl::SkipPauseRequest V8DebuggerAgentImpl::didPause( | |
1097 v8::Local<v8::Context> context, v8::Local<v8::Value> exception, | |
1098 const std::vector<String16>& hitBreakpoints, bool isPromiseRejection) { | |
1099 JavaScriptCallFrames callFrames = m_debugger->currentCallFrames(1); | |
1100 JavaScriptCallFrame* topCallFrame = | |
1101 !callFrames.empty() ? callFrames.begin()->get() : nullptr; | |
1102 | |
1103 V8DebuggerAgentImpl::SkipPauseRequest result; | |
1104 if (m_skipAllPauses) | |
1105 result = RequestContinue; | |
1106 else if (!hitBreakpoints.empty()) | |
1107 result = RequestNoSkip; // Don't skip explicit breakpoints even if set in | |
1108 // frameworks. | |
1109 else if (!exception.IsEmpty()) | |
1110 result = shouldSkipExceptionPause(topCallFrame); | |
1111 else if (m_scheduledDebuggerStep != NoStep || m_javaScriptPauseScheduled || | |
1112 m_pausingOnNativeEvent) | |
1113 result = shouldSkipStepPause(topCallFrame); | |
1114 else | |
1115 result = RequestNoSkip; | |
1116 | |
1117 m_skipNextDebuggerStepOut = false; | |
1118 if (result != RequestNoSkip) return result; | |
1119 // Skip pauses inside V8 internal scripts and on syntax errors. | |
1120 if (!topCallFrame) return RequestContinue; | |
1121 | |
1122 DCHECK(m_pausedContext.IsEmpty()); | |
1123 JavaScriptCallFrames frames = m_debugger->currentCallFrames(); | |
1124 m_pausedCallFrames.swap(frames); | |
1125 m_pausedContext.Reset(m_isolate, context); | |
1126 v8::HandleScope handles(m_isolate); | |
1127 | |
1128 if (!exception.IsEmpty()) { | |
1129 ErrorString ignored; | |
1130 InjectedScript* injectedScript = | |
1131 m_session->findInjectedScript(&ignored, V8Debugger::contextId(context)); | |
1132 if (injectedScript) { | |
1133 m_breakReason = | |
1134 isPromiseRejection | |
1135 ? protocol::Debugger::Paused::ReasonEnum::PromiseRejection | |
1136 : protocol::Debugger::Paused::ReasonEnum::Exception; | |
1137 ErrorString errorString; | |
1138 auto obj = injectedScript->wrapObject(&errorString, exception, | |
1139 backtraceObjectGroup); | |
1140 m_breakAuxData = obj ? obj->serialize() : nullptr; | |
1141 // m_breakAuxData might be null after this. | |
1142 } | |
1143 } | |
1144 | |
1145 std::unique_ptr<Array<String16>> hitBreakpointIds = Array<String16>::create(); | |
1146 | |
1147 for (const auto& point : hitBreakpoints) { | |
1148 DebugServerBreakpointToBreakpointIdAndSourceMap::iterator | |
1149 breakpointIterator = m_serverBreakpoints.find(point); | |
1150 if (breakpointIterator != m_serverBreakpoints.end()) { | |
1151 const String16& localId = breakpointIterator->second.first; | |
1152 hitBreakpointIds->addItem(localId); | |
1153 | |
1154 BreakpointSource source = breakpointIterator->second.second; | |
1155 if (m_breakReason == protocol::Debugger::Paused::ReasonEnum::Other && | |
1156 source == DebugCommandBreakpointSource) | |
1157 m_breakReason = protocol::Debugger::Paused::ReasonEnum::DebugCommand; | |
1158 } | |
1159 } | |
1160 | |
1161 ErrorString errorString; | |
1162 m_frontend.paused(currentCallFrames(&errorString), m_breakReason, | |
1163 std::move(m_breakAuxData), std::move(hitBreakpointIds), | |
1164 currentAsyncStackTrace()); | |
1165 m_scheduledDebuggerStep = NoStep; | |
1166 m_javaScriptPauseScheduled = false; | |
1167 m_steppingFromFramework = false; | |
1168 m_pausingOnNativeEvent = false; | |
1169 m_skippedStepFrameCount = 0; | |
1170 m_recursionLevelForStepFrame = 0; | |
1171 | |
1172 if (!m_continueToLocationBreakpointId.isEmpty()) { | |
1173 m_debugger->removeBreakpoint(m_continueToLocationBreakpointId); | |
1174 m_continueToLocationBreakpointId = ""; | |
1175 } | |
1176 return result; | |
1177 } | |
1178 | |
1179 void V8DebuggerAgentImpl::didContinue() { | |
1180 m_pausedContext.Reset(); | |
1181 JavaScriptCallFrames emptyCallFrames; | |
1182 m_pausedCallFrames.swap(emptyCallFrames); | |
1183 clearBreakDetails(); | |
1184 m_frontend.resumed(); | |
1185 } | |
1186 | |
1187 void V8DebuggerAgentImpl::breakProgram( | |
1188 const String16& breakReason, | |
1189 std::unique_ptr<protocol::DictionaryValue> data) { | |
1190 if (!enabled() || m_skipAllPauses || !m_pausedContext.IsEmpty() || | |
1191 isCurrentCallStackEmptyOrBlackboxed() || | |
1192 !m_debugger->breakpointsActivated()) | |
1193 return; | |
1194 m_breakReason = breakReason; | |
1195 m_breakAuxData = std::move(data); | |
1196 m_scheduledDebuggerStep = NoStep; | |
1197 m_steppingFromFramework = false; | |
1198 m_pausingOnNativeEvent = false; | |
1199 m_debugger->breakProgram(); | |
1200 } | |
1201 | |
1202 void V8DebuggerAgentImpl::breakProgramOnException( | |
1203 const String16& breakReason, | |
1204 std::unique_ptr<protocol::DictionaryValue> data) { | |
1205 if (!enabled() || | |
1206 m_debugger->getPauseOnExceptionsState() == | |
1207 V8Debugger::DontPauseOnExceptions) | |
1208 return; | |
1209 breakProgram(breakReason, std::move(data)); | |
1210 } | |
1211 | |
1212 bool V8DebuggerAgentImpl::assertPaused(ErrorString* errorString) { | |
1213 if (m_pausedContext.IsEmpty()) { | |
1214 *errorString = "Can only perform operation while paused."; | |
1215 return false; | |
1216 } | |
1217 return true; | |
1218 } | |
1219 | |
1220 void V8DebuggerAgentImpl::clearBreakDetails() { | |
1221 m_breakReason = protocol::Debugger::Paused::ReasonEnum::Other; | |
1222 m_breakAuxData = nullptr; | |
1223 } | |
1224 | |
1225 void V8DebuggerAgentImpl::setBreakpointAt(const String16& scriptId, | |
1226 int lineNumber, int columnNumber, | |
1227 BreakpointSource source, | |
1228 const String16& condition) { | |
1229 String16 breakpointId = | |
1230 generateBreakpointId(scriptId, lineNumber, columnNumber, source); | |
1231 ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition); | |
1232 resolveBreakpoint(breakpointId, scriptId, breakpoint, source); | |
1233 } | |
1234 | |
1235 void V8DebuggerAgentImpl::removeBreakpointAt(const String16& scriptId, | |
1236 int lineNumber, int columnNumber, | |
1237 BreakpointSource source) { | |
1238 removeBreakpoint( | |
1239 generateBreakpointId(scriptId, lineNumber, columnNumber, source)); | |
1240 } | |
1241 | |
1242 void V8DebuggerAgentImpl::reset() { | |
1243 if (!enabled()) return; | |
1244 m_scheduledDebuggerStep = NoStep; | |
1245 m_scripts.clear(); | |
1246 m_blackboxedPositions.clear(); | |
1247 m_breakpointIdToDebuggerBreakpointIds.clear(); | |
1248 } | |
1249 | |
1250 } // namespace v8_inspector | |
OLD | NEW |