1 | /* |
2 | * Copyright (C) 2010-2017 Apple Inc. All rights reserved. |
3 | * Copyright (C) 2010, 2011 Google Inc. All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions |
7 | * are met: |
8 | * |
9 | * 1. Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * 2. Redistributions in binary form must reproduce the above copyright |
12 | * notice, this list of conditions and the following disclaimer in the |
13 | * documentation and/or other materials provided with the distribution. |
14 | * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
15 | * its contributors may be used to endorse or promote products derived |
16 | * from this software without specific prior written permission. |
17 | * |
18 | * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
21 | * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
24 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
27 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
28 | */ |
29 | |
30 | #include "config.h" |
31 | #include "InspectorDebuggerAgent.h" |
32 | |
33 | #include "AsyncStackTrace.h" |
34 | #include "ContentSearchUtilities.h" |
35 | #include "InjectedScript.h" |
36 | #include "InjectedScriptManager.h" |
37 | #include "InspectorFrontendRouter.h" |
38 | #include "JSCInlines.h" |
39 | #include "RegularExpression.h" |
40 | #include "ScriptCallStack.h" |
41 | #include "ScriptCallStackFactory.h" |
42 | #include "ScriptDebugServer.h" |
43 | #include "ScriptObject.h" |
44 | #include <wtf/JSONValues.h> |
45 | #include <wtf/NeverDestroyed.h> |
46 | #include <wtf/Stopwatch.h> |
47 | #include <wtf/text/StringConcatenateNumbers.h> |
48 | #include <wtf/text/WTFString.h> |
49 | |
50 | namespace Inspector { |
51 | |
52 | const char* InspectorDebuggerAgent::backtraceObjectGroup = "backtrace" ; |
53 | |
54 | // Objects created and retained by evaluating breakpoint actions are put into object groups |
55 | // according to the breakpoint action identifier assigned by the frontend. A breakpoint may |
56 | // have several object groups, and objects from several backend breakpoint action instances may |
57 | // create objects in the same group. |
58 | static String objectGroupForBreakpointAction(const ScriptBreakpointAction& action) |
59 | { |
60 | return makeString("breakpoint-action-" , action.identifier); |
61 | } |
62 | |
63 | InspectorDebuggerAgent::InspectorDebuggerAgent(AgentContext& context) |
64 | : InspectorAgentBase("Debugger"_s ) |
65 | , m_frontendDispatcher(std::make_unique<DebuggerFrontendDispatcher>(context.frontendRouter)) |
66 | , m_backendDispatcher(DebuggerBackendDispatcher::create(context.backendDispatcher, this)) |
67 | , m_scriptDebugServer(context.environment.scriptDebugServer()) |
68 | , m_injectedScriptManager(context.injectedScriptManager) |
69 | { |
70 | // FIXME: make breakReason optional so that there was no need to init it with "other". |
71 | clearBreakDetails(); |
72 | } |
73 | |
74 | InspectorDebuggerAgent::~InspectorDebuggerAgent() |
75 | { |
76 | } |
77 | |
78 | void InspectorDebuggerAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*) |
79 | { |
80 | } |
81 | |
82 | void InspectorDebuggerAgent::willDestroyFrontendAndBackend(DisconnectReason reason) |
83 | { |
84 | bool skipRecompile = reason == DisconnectReason::InspectedTargetDestroyed; |
85 | disable(skipRecompile); |
86 | } |
87 | |
88 | void InspectorDebuggerAgent::enable() |
89 | { |
90 | if (m_enabled) |
91 | return; |
92 | |
93 | m_enabled = true; |
94 | |
95 | m_scriptDebugServer.addListener(this); |
96 | |
97 | for (auto* listener : copyToVector(m_listeners)) |
98 | listener->debuggerWasEnabled(); |
99 | } |
100 | |
101 | void InspectorDebuggerAgent::disable(bool isBeingDestroyed) |
102 | { |
103 | if (!m_enabled) |
104 | return; |
105 | |
106 | for (auto* listener : copyToVector(m_listeners)) |
107 | listener->debuggerWasDisabled(); |
108 | |
109 | m_scriptDebugServer.removeListener(this, isBeingDestroyed); |
110 | |
111 | clearInspectorBreakpointState(); |
112 | |
113 | if (!isBeingDestroyed) |
114 | m_scriptDebugServer.deactivateBreakpoints(); |
115 | |
116 | ASSERT(m_javaScriptBreakpoints.isEmpty()); |
117 | |
118 | clearAsyncStackTraceData(); |
119 | |
120 | m_pauseOnAssertionFailures = false; |
121 | |
122 | m_enabled = false; |
123 | } |
124 | |
125 | void InspectorDebuggerAgent::enable(ErrorString&) |
126 | { |
127 | enable(); |
128 | } |
129 | |
130 | void InspectorDebuggerAgent::disable(ErrorString&) |
131 | { |
132 | disable(false); |
133 | } |
134 | |
135 | bool InspectorDebuggerAgent::breakpointsActive() const |
136 | { |
137 | return m_scriptDebugServer.breakpointsActive(); |
138 | } |
139 | |
140 | void InspectorDebuggerAgent::setAsyncStackTraceDepth(ErrorString& errorString, int depth) |
141 | { |
142 | if (m_asyncStackTraceDepth == depth) |
143 | return; |
144 | |
145 | if (depth < 0) { |
146 | errorString = "depth must be a positive number."_s ; |
147 | return; |
148 | } |
149 | |
150 | m_asyncStackTraceDepth = depth; |
151 | |
152 | if (!m_asyncStackTraceDepth) |
153 | clearAsyncStackTraceData(); |
154 | } |
155 | |
156 | void InspectorDebuggerAgent::setBreakpointsActive(ErrorString&, bool active) |
157 | { |
158 | if (active) |
159 | m_scriptDebugServer.activateBreakpoints(); |
160 | else |
161 | m_scriptDebugServer.deactivateBreakpoints(); |
162 | } |
163 | |
164 | bool InspectorDebuggerAgent::isPaused() const |
165 | { |
166 | return m_scriptDebugServer.isPaused(); |
167 | } |
168 | |
169 | void InspectorDebuggerAgent::setSuppressAllPauses(bool suppress) |
170 | { |
171 | m_scriptDebugServer.setSuppressAllPauses(suppress); |
172 | } |
173 | |
174 | static RefPtr<JSON::Object> buildAssertPauseReason(const String& message) |
175 | { |
176 | auto reason = Protocol::Debugger::AssertPauseReason::create().release(); |
177 | if (!message.isNull()) |
178 | reason->setMessage(message); |
179 | return reason->openAccessors(); |
180 | } |
181 | |
182 | static RefPtr<JSON::Object> buildCSPViolationPauseReason(const String& directiveText) |
183 | { |
184 | auto reason = Protocol::Debugger::CSPViolationPauseReason::create() |
185 | .setDirective(directiveText) |
186 | .release(); |
187 | return reason->openAccessors(); |
188 | } |
189 | |
190 | RefPtr<JSON::Object> InspectorDebuggerAgent::buildBreakpointPauseReason(JSC::BreakpointID debuggerBreakpointIdentifier) |
191 | { |
192 | ASSERT(debuggerBreakpointIdentifier != JSC::noBreakpointID); |
193 | auto it = m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.find(debuggerBreakpointIdentifier); |
194 | if (it == m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.end()) |
195 | return nullptr; |
196 | |
197 | auto reason = Protocol::Debugger::BreakpointPauseReason::create() |
198 | .setBreakpointId(it->value) |
199 | .release(); |
200 | return reason->openAccessors(); |
201 | } |
202 | |
203 | RefPtr<JSON::Object> InspectorDebuggerAgent::buildExceptionPauseReason(JSC::JSValue exception, const InjectedScript& injectedScript) |
204 | { |
205 | ASSERT(exception); |
206 | if (!exception) |
207 | return nullptr; |
208 | |
209 | ASSERT(!injectedScript.hasNoValue()); |
210 | if (injectedScript.hasNoValue()) |
211 | return nullptr; |
212 | |
213 | return injectedScript.wrapObject(exception, InspectorDebuggerAgent::backtraceObjectGroup)->openAccessors(); |
214 | } |
215 | |
216 | void InspectorDebuggerAgent::handleConsoleAssert(const String& message) |
217 | { |
218 | if (!m_scriptDebugServer.breakpointsActive()) |
219 | return; |
220 | |
221 | if (m_pauseOnAssertionFailures) |
222 | breakProgram(DebuggerFrontendDispatcher::Reason::Assert, buildAssertPauseReason(message)); |
223 | } |
224 | |
225 | InspectorDebuggerAgent::AsyncCallIdentifier InspectorDebuggerAgent::asyncCallIdentifier(AsyncCallType asyncCallType, int callbackId) |
226 | { |
227 | return std::make_pair(static_cast<unsigned>(asyncCallType), callbackId); |
228 | } |
229 | |
230 | void InspectorDebuggerAgent::didScheduleAsyncCall(JSC::ExecState* exec, AsyncCallType asyncCallType, int callbackId, bool singleShot) |
231 | { |
232 | if (!m_asyncStackTraceDepth) |
233 | return; |
234 | |
235 | if (!m_scriptDebugServer.breakpointsActive()) |
236 | return; |
237 | |
238 | Ref<ScriptCallStack> callStack = createScriptCallStack(exec, m_asyncStackTraceDepth); |
239 | ASSERT(callStack->size()); |
240 | if (!callStack->size()) |
241 | return; |
242 | |
243 | RefPtr<AsyncStackTrace> parentStackTrace; |
244 | if (m_currentAsyncCallIdentifier) { |
245 | auto it = m_pendingAsyncCalls.find(m_currentAsyncCallIdentifier.value()); |
246 | ASSERT(it != m_pendingAsyncCalls.end()); |
247 | parentStackTrace = it->value; |
248 | } |
249 | |
250 | auto identifier = asyncCallIdentifier(asyncCallType, callbackId); |
251 | auto asyncStackTrace = AsyncStackTrace::create(WTFMove(callStack), singleShot, WTFMove(parentStackTrace)); |
252 | |
253 | m_pendingAsyncCalls.set(identifier, WTFMove(asyncStackTrace)); |
254 | } |
255 | |
256 | void InspectorDebuggerAgent::didCancelAsyncCall(AsyncCallType asyncCallType, int callbackId) |
257 | { |
258 | if (!m_asyncStackTraceDepth) |
259 | return; |
260 | |
261 | auto identifier = asyncCallIdentifier(asyncCallType, callbackId); |
262 | auto it = m_pendingAsyncCalls.find(identifier); |
263 | if (it == m_pendingAsyncCalls.end()) |
264 | return; |
265 | |
266 | auto& asyncStackTrace = it->value; |
267 | asyncStackTrace->didCancelAsyncCall(); |
268 | |
269 | if (m_currentAsyncCallIdentifier && m_currentAsyncCallIdentifier.value() == identifier) |
270 | return; |
271 | |
272 | m_pendingAsyncCalls.remove(identifier); |
273 | } |
274 | |
275 | void InspectorDebuggerAgent::willDispatchAsyncCall(AsyncCallType asyncCallType, int callbackId) |
276 | { |
277 | if (!m_asyncStackTraceDepth) |
278 | return; |
279 | |
280 | if (m_currentAsyncCallIdentifier) |
281 | return; |
282 | |
283 | // A call can be scheduled before the Inspector is opened, or while async stack |
284 | // traces are disabled. If no call data exists, do nothing. |
285 | auto identifier = asyncCallIdentifier(asyncCallType, callbackId); |
286 | auto it = m_pendingAsyncCalls.find(identifier); |
287 | if (it == m_pendingAsyncCalls.end()) |
288 | return; |
289 | |
290 | auto& asyncStackTrace = it->value; |
291 | asyncStackTrace->willDispatchAsyncCall(m_asyncStackTraceDepth); |
292 | |
293 | m_currentAsyncCallIdentifier = identifier; |
294 | } |
295 | |
296 | void InspectorDebuggerAgent::didDispatchAsyncCall() |
297 | { |
298 | if (!m_asyncStackTraceDepth) |
299 | return; |
300 | |
301 | if (!m_currentAsyncCallIdentifier) |
302 | return; |
303 | |
304 | auto identifier = m_currentAsyncCallIdentifier.value(); |
305 | auto it = m_pendingAsyncCalls.find(identifier); |
306 | ASSERT(it != m_pendingAsyncCalls.end()); |
307 | |
308 | auto& asyncStackTrace = it->value; |
309 | asyncStackTrace->didDispatchAsyncCall(); |
310 | |
311 | m_currentAsyncCallIdentifier = WTF::nullopt; |
312 | |
313 | if (!asyncStackTrace->isPending()) |
314 | m_pendingAsyncCalls.remove(identifier); |
315 | } |
316 | |
317 | static Ref<JSON::Object> buildObjectForBreakpointCookie(const String& url, int lineNumber, int columnNumber, const String& condition, RefPtr<JSON::Array>& actions, bool isRegex, bool autoContinue, unsigned ignoreCount) |
318 | { |
319 | Ref<JSON::Object> breakpointObject = JSON::Object::create(); |
320 | breakpointObject->setString("url"_s , url); |
321 | breakpointObject->setInteger("lineNumber"_s , lineNumber); |
322 | breakpointObject->setInteger("columnNumber"_s , columnNumber); |
323 | breakpointObject->setString("condition"_s , condition); |
324 | breakpointObject->setBoolean("isRegex"_s , isRegex); |
325 | breakpointObject->setBoolean("autoContinue"_s , autoContinue); |
326 | breakpointObject->setInteger("ignoreCount"_s , ignoreCount); |
327 | |
328 | if (actions) |
329 | breakpointObject->setArray("actions"_s , actions); |
330 | |
331 | return breakpointObject; |
332 | } |
333 | |
334 | static bool matches(const String& url, const String& pattern, bool isRegex) |
335 | { |
336 | if (isRegex) { |
337 | JSC::Yarr::RegularExpression regex(pattern); |
338 | return regex.match(url) != -1; |
339 | } |
340 | return url == pattern; |
341 | } |
342 | |
343 | static bool breakpointActionTypeForString(const String& typeString, ScriptBreakpointActionType* output) |
344 | { |
345 | if (typeString == Protocol::InspectorHelpers::getEnumConstantValue(Protocol::Debugger::BreakpointAction::Type::Log)) { |
346 | *output = ScriptBreakpointActionTypeLog; |
347 | return true; |
348 | } |
349 | if (typeString == Protocol::InspectorHelpers::getEnumConstantValue(Protocol::Debugger::BreakpointAction::Type::Evaluate)) { |
350 | *output = ScriptBreakpointActionTypeEvaluate; |
351 | return true; |
352 | } |
353 | if (typeString == Protocol::InspectorHelpers::getEnumConstantValue(Protocol::Debugger::BreakpointAction::Type::Sound)) { |
354 | *output = ScriptBreakpointActionTypeSound; |
355 | return true; |
356 | } |
357 | if (typeString == Protocol::InspectorHelpers::getEnumConstantValue(Protocol::Debugger::BreakpointAction::Type::Probe)) { |
358 | *output = ScriptBreakpointActionTypeProbe; |
359 | return true; |
360 | } |
361 | |
362 | return false; |
363 | } |
364 | |
365 | bool InspectorDebuggerAgent::breakpointActionsFromProtocol(ErrorString& errorString, RefPtr<JSON::Array>& actions, BreakpointActions* result) |
366 | { |
367 | if (!actions) |
368 | return true; |
369 | |
370 | unsigned actionsLength = actions->length(); |
371 | if (!actionsLength) |
372 | return true; |
373 | |
374 | result->reserveCapacity(actionsLength); |
375 | for (unsigned i = 0; i < actionsLength; ++i) { |
376 | RefPtr<JSON::Value> value = actions->get(i); |
377 | RefPtr<JSON::Object> object; |
378 | if (!value->asObject(object)) { |
379 | errorString = "BreakpointAction of incorrect type, expected object"_s ; |
380 | return false; |
381 | } |
382 | |
383 | String typeString; |
384 | if (!object->getString("type"_s , typeString)) { |
385 | errorString = "BreakpointAction had type missing"_s ; |
386 | return false; |
387 | } |
388 | |
389 | ScriptBreakpointActionType type; |
390 | if (!breakpointActionTypeForString(typeString, &type)) { |
391 | errorString = "BreakpointAction had unknown type"_s ; |
392 | return false; |
393 | } |
394 | |
395 | // Specifying an identifier is optional. They are used to correlate probe samples |
396 | // in the frontend across multiple backend probe actions and segregate object groups. |
397 | int identifier = 0; |
398 | object->getInteger("id"_s , identifier); |
399 | |
400 | String data; |
401 | object->getString("data"_s , data); |
402 | |
403 | result->append(ScriptBreakpointAction(type, identifier, data)); |
404 | } |
405 | |
406 | return true; |
407 | } |
408 | |
409 | static RefPtr<Protocol::Debugger::Location> buildDebuggerLocation(const JSC::Breakpoint& breakpoint) |
410 | { |
411 | ASSERT(breakpoint.resolved); |
412 | |
413 | auto location = Protocol::Debugger::Location::create() |
414 | .setScriptId(String::number(breakpoint.sourceID)) |
415 | .setLineNumber(breakpoint.line) |
416 | .release(); |
417 | location->setColumnNumber(breakpoint.column); |
418 | |
419 | return location; |
420 | } |
421 | |
422 | static bool parseLocation(ErrorString& errorString, const JSON::Object& location, JSC::SourceID& sourceID, unsigned& lineNumber, unsigned& columnNumber) |
423 | { |
424 | String scriptIDStr; |
425 | if (!location.getString("scriptId"_s , scriptIDStr) || !location.getInteger("lineNumber"_s , lineNumber)) { |
426 | sourceID = JSC::noSourceID; |
427 | errorString = "scriptId and lineNumber are required."_s ; |
428 | return false; |
429 | } |
430 | |
431 | sourceID = scriptIDStr.toIntPtr(); |
432 | columnNumber = 0; |
433 | location.getInteger("columnNumber"_s , columnNumber); |
434 | return true; |
435 | } |
436 | |
437 | void InspectorDebuggerAgent::setBreakpointByUrl(ErrorString& errorString, int lineNumber, const String* optionalURL, const String* optionalURLRegex, const int* optionalColumnNumber, const JSON::Object* options, Protocol::Debugger::BreakpointId* outBreakpointIdentifier, RefPtr<JSON::ArrayOf<Protocol::Debugger::Location>>& locations) |
438 | { |
439 | locations = JSON::ArrayOf<Protocol::Debugger::Location>::create(); |
440 | if (!optionalURL == !optionalURLRegex) { |
441 | errorString = "Either url or urlRegex must be specified."_s ; |
442 | return; |
443 | } |
444 | |
445 | String url = optionalURL ? *optionalURL : *optionalURLRegex; |
446 | int columnNumber = optionalColumnNumber ? *optionalColumnNumber : 0; |
447 | bool isRegex = optionalURLRegex; |
448 | |
449 | String breakpointIdentifier = makeString(isRegex ? "/" : "" , url, isRegex ? "/:" : ":" , lineNumber, ':', columnNumber); |
450 | if (m_javaScriptBreakpoints.contains(breakpointIdentifier)) { |
451 | errorString = "Breakpoint at specified location already exists."_s ; |
452 | return; |
453 | } |
454 | |
455 | String condition = emptyString(); |
456 | bool autoContinue = false; |
457 | unsigned ignoreCount = 0; |
458 | RefPtr<JSON::Array> actions; |
459 | if (options) { |
460 | options->getString("condition"_s , condition); |
461 | options->getBoolean("autoContinue"_s , autoContinue); |
462 | options->getArray("actions"_s , actions); |
463 | options->getInteger("ignoreCount"_s , ignoreCount); |
464 | } |
465 | |
466 | BreakpointActions breakpointActions; |
467 | if (!breakpointActionsFromProtocol(errorString, actions, &breakpointActions)) |
468 | return; |
469 | |
470 | m_javaScriptBreakpoints.set(breakpointIdentifier, buildObjectForBreakpointCookie(url, lineNumber, columnNumber, condition, actions, isRegex, autoContinue, ignoreCount)); |
471 | |
472 | for (auto& entry : m_scripts) { |
473 | Script& script = entry.value; |
474 | String scriptURLForBreakpoints = !script.sourceURL.isEmpty() ? script.sourceURL : script.url; |
475 | if (!matches(scriptURLForBreakpoints, url, isRegex)) |
476 | continue; |
477 | |
478 | JSC::SourceID sourceID = entry.key; |
479 | JSC::Breakpoint breakpoint(sourceID, lineNumber, columnNumber, condition, autoContinue, ignoreCount); |
480 | resolveBreakpoint(script, breakpoint); |
481 | if (!breakpoint.resolved) |
482 | continue; |
483 | |
484 | bool existing; |
485 | setBreakpoint(breakpoint, existing); |
486 | if (existing) |
487 | continue; |
488 | |
489 | ScriptBreakpoint scriptBreakpoint(breakpoint.line, breakpoint.column, condition, breakpointActions, autoContinue, ignoreCount); |
490 | didSetBreakpoint(breakpoint, breakpointIdentifier, scriptBreakpoint); |
491 | |
492 | locations->addItem(buildDebuggerLocation(breakpoint)); |
493 | } |
494 | |
495 | *outBreakpointIdentifier = breakpointIdentifier; |
496 | } |
497 | |
498 | void InspectorDebuggerAgent::setBreakpoint(ErrorString& errorString, const JSON::Object& location, const JSON::Object* options, Protocol::Debugger::BreakpointId* outBreakpointIdentifier, RefPtr<Protocol::Debugger::Location>& actualLocation) |
499 | { |
500 | JSC::SourceID sourceID; |
501 | unsigned lineNumber; |
502 | unsigned columnNumber; |
503 | if (!parseLocation(errorString, location, sourceID, lineNumber, columnNumber)) |
504 | return; |
505 | |
506 | String condition = emptyString(); |
507 | bool autoContinue = false; |
508 | unsigned ignoreCount = 0; |
509 | RefPtr<JSON::Array> actions; |
510 | if (options) { |
511 | options->getString("condition"_s , condition); |
512 | options->getBoolean("autoContinue"_s , autoContinue); |
513 | options->getArray("actions"_s , actions); |
514 | options->getInteger("ignoreCount"_s , ignoreCount); |
515 | } |
516 | |
517 | BreakpointActions breakpointActions; |
518 | if (!breakpointActionsFromProtocol(errorString, actions, &breakpointActions)) |
519 | return; |
520 | |
521 | auto scriptIterator = m_scripts.find(sourceID); |
522 | if (scriptIterator == m_scripts.end()) { |
523 | errorString = makeString("No script for id: "_s , sourceID); |
524 | return; |
525 | } |
526 | |
527 | Script& script = scriptIterator->value; |
528 | JSC::Breakpoint breakpoint(sourceID, lineNumber, columnNumber, condition, autoContinue, ignoreCount); |
529 | resolveBreakpoint(script, breakpoint); |
530 | if (!breakpoint.resolved) { |
531 | errorString = "Could not resolve breakpoint"_s ; |
532 | return; |
533 | } |
534 | |
535 | bool existing; |
536 | setBreakpoint(breakpoint, existing); |
537 | if (existing) { |
538 | errorString = "Breakpoint at specified location already exists"_s ; |
539 | return; |
540 | } |
541 | |
542 | String breakpointIdentifier = makeString(sourceID, ':', breakpoint.line, ':', breakpoint.column); |
543 | ScriptBreakpoint scriptBreakpoint(breakpoint.line, breakpoint.column, condition, breakpointActions, autoContinue, ignoreCount); |
544 | didSetBreakpoint(breakpoint, breakpointIdentifier, scriptBreakpoint); |
545 | |
546 | actualLocation = buildDebuggerLocation(breakpoint); |
547 | *outBreakpointIdentifier = breakpointIdentifier; |
548 | } |
549 | |
550 | void InspectorDebuggerAgent::didSetBreakpoint(const JSC::Breakpoint& breakpoint, const String& breakpointIdentifier, const ScriptBreakpoint& scriptBreakpoint) |
551 | { |
552 | JSC::BreakpointID id = breakpoint.id; |
553 | m_scriptDebugServer.setBreakpointActions(id, scriptBreakpoint); |
554 | |
555 | auto debugServerBreakpointIDsIterator = m_breakpointIdentifierToDebugServerBreakpointIDs.find(breakpointIdentifier); |
556 | if (debugServerBreakpointIDsIterator == m_breakpointIdentifierToDebugServerBreakpointIDs.end()) |
557 | debugServerBreakpointIDsIterator = m_breakpointIdentifierToDebugServerBreakpointIDs.set(breakpointIdentifier, Vector<JSC::BreakpointID>()).iterator; |
558 | debugServerBreakpointIDsIterator->value.append(id); |
559 | |
560 | m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.set(id, breakpointIdentifier); |
561 | } |
562 | |
563 | void InspectorDebuggerAgent::resolveBreakpoint(const Script& script, JSC::Breakpoint& breakpoint) |
564 | { |
565 | if (breakpoint.line < static_cast<unsigned>(script.startLine) || static_cast<unsigned>(script.endLine) < breakpoint.line) |
566 | return; |
567 | |
568 | m_scriptDebugServer.resolveBreakpoint(breakpoint, script.sourceProvider.get()); |
569 | } |
570 | |
571 | void InspectorDebuggerAgent::setBreakpoint(JSC::Breakpoint& breakpoint, bool& existing) |
572 | { |
573 | JSC::JSLockHolder locker(m_scriptDebugServer.vm()); |
574 | m_scriptDebugServer.setBreakpoint(breakpoint, existing); |
575 | } |
576 | |
577 | void InspectorDebuggerAgent::removeBreakpoint(ErrorString&, const String& breakpointIdentifier) |
578 | { |
579 | m_javaScriptBreakpoints.remove(breakpointIdentifier); |
580 | |
581 | for (JSC::BreakpointID breakpointID : m_breakpointIdentifierToDebugServerBreakpointIDs.take(breakpointIdentifier)) { |
582 | m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.remove(breakpointID); |
583 | |
584 | const BreakpointActions& breakpointActions = m_scriptDebugServer.getActionsForBreakpoint(breakpointID); |
585 | for (auto& action : breakpointActions) |
586 | m_injectedScriptManager.releaseObjectGroup(objectGroupForBreakpointAction(action)); |
587 | |
588 | JSC::JSLockHolder locker(m_scriptDebugServer.vm()); |
589 | m_scriptDebugServer.removeBreakpointActions(breakpointID); |
590 | m_scriptDebugServer.removeBreakpoint(breakpointID); |
591 | } |
592 | } |
593 | |
594 | void InspectorDebuggerAgent::continueUntilNextRunLoop(ErrorString& errorString) |
595 | { |
596 | if (!assertPaused(errorString)) |
597 | return; |
598 | |
599 | resume(errorString); |
600 | |
601 | m_enablePauseWhenIdle = true; |
602 | |
603 | registerIdleHandler(); |
604 | } |
605 | |
606 | void InspectorDebuggerAgent::continueToLocation(ErrorString& errorString, const JSON::Object& location) |
607 | { |
608 | if (!assertPaused(errorString)) |
609 | return; |
610 | |
611 | if (m_continueToLocationBreakpointID != JSC::noBreakpointID) { |
612 | m_scriptDebugServer.removeBreakpoint(m_continueToLocationBreakpointID); |
613 | m_continueToLocationBreakpointID = JSC::noBreakpointID; |
614 | } |
615 | |
616 | JSC::SourceID sourceID; |
617 | unsigned lineNumber; |
618 | unsigned columnNumber; |
619 | if (!parseLocation(errorString, location, sourceID, lineNumber, columnNumber)) |
620 | return; |
621 | |
622 | auto scriptIterator = m_scripts.find(sourceID); |
623 | if (scriptIterator == m_scripts.end()) { |
624 | m_scriptDebugServer.continueProgram(); |
625 | m_frontendDispatcher->resumed(); |
626 | errorString = makeString("No script for id: "_s , sourceID); |
627 | return; |
628 | } |
629 | |
630 | String condition; |
631 | bool autoContinue = false; |
632 | unsigned ignoreCount = 0; |
633 | JSC::Breakpoint breakpoint(sourceID, lineNumber, columnNumber, condition, autoContinue, ignoreCount); |
634 | Script& script = scriptIterator->value; |
635 | resolveBreakpoint(script, breakpoint); |
636 | if (!breakpoint.resolved) { |
637 | m_scriptDebugServer.continueProgram(); |
638 | m_frontendDispatcher->resumed(); |
639 | errorString = "Could not resolve breakpoint"_s ; |
640 | return; |
641 | } |
642 | |
643 | bool existing; |
644 | setBreakpoint(breakpoint, existing); |
645 | if (existing) { |
646 | // There is an existing breakpoint at this location. Instead of |
647 | // acting like a series of steps, just resume and we will either |
648 | // hit this new breakpoint or not. |
649 | m_scriptDebugServer.continueProgram(); |
650 | m_frontendDispatcher->resumed(); |
651 | return; |
652 | } |
653 | |
654 | m_continueToLocationBreakpointID = breakpoint.id; |
655 | |
656 | // Treat this as a series of steps until reaching the new breakpoint. |
657 | // So don't issue a resumed event unless we exit the VM without pausing. |
658 | willStepAndMayBecomeIdle(); |
659 | m_scriptDebugServer.continueProgram(); |
660 | } |
661 | |
662 | void InspectorDebuggerAgent::searchInContent(ErrorString& error, const String& scriptIDStr, const String& query, const bool* optionalCaseSensitive, const bool* optionalIsRegex, RefPtr<JSON::ArrayOf<Protocol::GenericTypes::SearchMatch>>& results) |
663 | { |
664 | JSC::SourceID sourceID = scriptIDStr.toIntPtr(); |
665 | auto it = m_scripts.find(sourceID); |
666 | if (it == m_scripts.end()) { |
667 | error = makeString("No script for id: "_s , scriptIDStr); |
668 | return; |
669 | } |
670 | |
671 | bool isRegex = optionalIsRegex ? *optionalIsRegex : false; |
672 | bool caseSensitive = optionalCaseSensitive ? *optionalCaseSensitive : false; |
673 | results = ContentSearchUtilities::searchInTextByLines(it->value.source, query, caseSensitive, isRegex); |
674 | } |
675 | |
676 | void InspectorDebuggerAgent::getScriptSource(ErrorString& error, const String& scriptIDStr, String* scriptSource) |
677 | { |
678 | JSC::SourceID sourceID = scriptIDStr.toIntPtr(); |
679 | auto it = m_scripts.find(sourceID); |
680 | if (it != m_scripts.end()) |
681 | *scriptSource = it->value.source; |
682 | else |
683 | error = makeString("No script for id: "_s , scriptIDStr); |
684 | } |
685 | |
686 | void InspectorDebuggerAgent::getFunctionDetails(ErrorString& errorString, const String& functionId, RefPtr<Protocol::Debugger::FunctionDetails>& details) |
687 | { |
688 | InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(functionId); |
689 | if (injectedScript.hasNoValue()) { |
690 | errorString = "Function object id is obsolete"_s ; |
691 | return; |
692 | } |
693 | |
694 | injectedScript.getFunctionDetails(errorString, functionId, details); |
695 | } |
696 | |
697 | void InspectorDebuggerAgent::schedulePauseOnNextStatement(DebuggerFrontendDispatcher::Reason breakReason, RefPtr<JSON::Object>&& data) |
698 | { |
699 | if (m_javaScriptPauseScheduled) |
700 | return; |
701 | |
702 | m_javaScriptPauseScheduled = true; |
703 | |
704 | m_breakReason = breakReason; |
705 | m_breakData = WTFMove(data); |
706 | |
707 | JSC::JSLockHolder locker(m_scriptDebugServer.vm()); |
708 | m_scriptDebugServer.setPauseOnNextStatement(true); |
709 | } |
710 | |
711 | void InspectorDebuggerAgent::cancelPauseOnNextStatement() |
712 | { |
713 | if (!m_javaScriptPauseScheduled) |
714 | return; |
715 | |
716 | m_javaScriptPauseScheduled = false; |
717 | |
718 | clearBreakDetails(); |
719 | m_scriptDebugServer.setPauseOnNextStatement(false); |
720 | m_enablePauseWhenIdle = false; |
721 | } |
722 | |
723 | void InspectorDebuggerAgent::pause(ErrorString&) |
724 | { |
725 | schedulePauseOnNextStatement(DebuggerFrontendDispatcher::Reason::PauseOnNextStatement, nullptr); |
726 | } |
727 | |
728 | void InspectorDebuggerAgent::resume(ErrorString& errorString) |
729 | { |
730 | if (!m_pausedScriptState && !m_javaScriptPauseScheduled) { |
731 | errorString = "Was not paused or waiting to pause"_s ; |
732 | return; |
733 | } |
734 | |
735 | cancelPauseOnNextStatement(); |
736 | m_scriptDebugServer.continueProgram(); |
737 | m_conditionToDispatchResumed = ShouldDispatchResumed::WhenContinued; |
738 | } |
739 | |
740 | void InspectorDebuggerAgent::stepOver(ErrorString& errorString) |
741 | { |
742 | if (!assertPaused(errorString)) |
743 | return; |
744 | |
745 | willStepAndMayBecomeIdle(); |
746 | m_scriptDebugServer.stepOverStatement(); |
747 | } |
748 | |
749 | void InspectorDebuggerAgent::stepInto(ErrorString& errorString) |
750 | { |
751 | if (!assertPaused(errorString)) |
752 | return; |
753 | |
754 | willStepAndMayBecomeIdle(); |
755 | m_scriptDebugServer.stepIntoStatement(); |
756 | } |
757 | |
758 | void InspectorDebuggerAgent::stepOut(ErrorString& errorString) |
759 | { |
760 | if (!assertPaused(errorString)) |
761 | return; |
762 | |
763 | willStepAndMayBecomeIdle(); |
764 | m_scriptDebugServer.stepOutOfFunction(); |
765 | } |
766 | |
767 | void InspectorDebuggerAgent::registerIdleHandler() |
768 | { |
769 | if (!m_registeredIdleCallback) { |
770 | m_registeredIdleCallback = true; |
771 | JSC::VM& vm = m_scriptDebugServer.vm(); |
772 | vm.whenIdle([this]() { |
773 | didBecomeIdle(); |
774 | }); |
775 | } |
776 | } |
777 | |
778 | void InspectorDebuggerAgent::willStepAndMayBecomeIdle() |
779 | { |
780 | // When stepping the backend must eventually trigger a "paused" or "resumed" event. |
781 | // If the step causes us to exit the VM, then we should issue "resumed". |
782 | m_conditionToDispatchResumed = ShouldDispatchResumed::WhenIdle; |
783 | |
784 | registerIdleHandler(); |
785 | } |
786 | |
787 | void InspectorDebuggerAgent::didBecomeIdle() |
788 | { |
789 | m_registeredIdleCallback = false; |
790 | |
791 | if (m_conditionToDispatchResumed == ShouldDispatchResumed::WhenIdle) { |
792 | cancelPauseOnNextStatement(); |
793 | m_scriptDebugServer.continueProgram(); |
794 | m_frontendDispatcher->resumed(); |
795 | } |
796 | |
797 | m_conditionToDispatchResumed = ShouldDispatchResumed::No; |
798 | |
799 | if (m_enablePauseWhenIdle) { |
800 | ErrorString ignored; |
801 | pause(ignored); |
802 | } |
803 | } |
804 | |
805 | void InspectorDebuggerAgent::setPauseOnExceptions(ErrorString& errorString, const String& stringPauseState) |
806 | { |
807 | JSC::Debugger::PauseOnExceptionsState pauseState; |
808 | if (stringPauseState == "none" ) |
809 | pauseState = JSC::Debugger::DontPauseOnExceptions; |
810 | else if (stringPauseState == "all" ) |
811 | pauseState = JSC::Debugger::PauseOnAllExceptions; |
812 | else if (stringPauseState == "uncaught" ) |
813 | pauseState = JSC::Debugger::PauseOnUncaughtExceptions; |
814 | else { |
815 | errorString = makeString("Unknown pause on exceptions mode: "_s , stringPauseState); |
816 | return; |
817 | } |
818 | |
819 | m_scriptDebugServer.setPauseOnExceptionsState(static_cast<JSC::Debugger::PauseOnExceptionsState>(pauseState)); |
820 | if (m_scriptDebugServer.pauseOnExceptionsState() != pauseState) |
821 | errorString = "Internal error. Could not change pause on exceptions state"_s ; |
822 | } |
823 | |
824 | void InspectorDebuggerAgent::setPauseOnAssertions(ErrorString&, bool enabled) |
825 | { |
826 | m_pauseOnAssertionFailures = enabled; |
827 | } |
828 | |
829 | void InspectorDebuggerAgent::evaluateOnCallFrame(ErrorString& errorString, const String& callFrameId, const String& expression, const String* objectGroup, const bool* includeCommandLineAPI, const bool* doNotPauseOnExceptionsAndMuteConsole, const bool* returnByValue, const bool* generatePreview, const bool* saveResult, RefPtr<Protocol::Runtime::RemoteObject>& result, Optional<bool>& wasThrown, Optional<int>& savedResultIndex) |
830 | { |
831 | if (!m_currentCallStack) { |
832 | errorString = "Not paused"_s ; |
833 | return; |
834 | } |
835 | |
836 | InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(callFrameId); |
837 | if (injectedScript.hasNoValue()) { |
838 | errorString = "Could not find InjectedScript for callFrameId"_s ; |
839 | return; |
840 | } |
841 | |
842 | auto pauseState = m_scriptDebugServer.pauseOnExceptionsState(); |
843 | bool pauseAndMute = doNotPauseOnExceptionsAndMuteConsole && *doNotPauseOnExceptionsAndMuteConsole; |
844 | if (pauseAndMute) { |
845 | if (pauseState != JSC::Debugger::DontPauseOnExceptions) |
846 | m_scriptDebugServer.setPauseOnExceptionsState(JSC::Debugger::DontPauseOnExceptions); |
847 | muteConsole(); |
848 | } |
849 | |
850 | injectedScript.evaluateOnCallFrame(errorString, m_currentCallStack.get(), callFrameId, expression, |
851 | objectGroup ? *objectGroup : emptyString(), includeCommandLineAPI && *includeCommandLineAPI, returnByValue && *returnByValue, generatePreview && *generatePreview, saveResult && *saveResult, |
852 | result, wasThrown, savedResultIndex); |
853 | |
854 | if (pauseAndMute) { |
855 | unmuteConsole(); |
856 | m_scriptDebugServer.setPauseOnExceptionsState(pauseState); |
857 | } |
858 | } |
859 | |
860 | void InspectorDebuggerAgent::scriptExecutionBlockedByCSP(const String& directiveText) |
861 | { |
862 | if (m_scriptDebugServer.pauseOnExceptionsState() != JSC::Debugger::DontPauseOnExceptions) |
863 | breakProgram(DebuggerFrontendDispatcher::Reason::CSPViolation, buildCSPViolationPauseReason(directiveText)); |
864 | } |
865 | |
866 | Ref<JSON::ArrayOf<Protocol::Debugger::CallFrame>> InspectorDebuggerAgent::currentCallFrames(const InjectedScript& injectedScript) |
867 | { |
868 | ASSERT(!injectedScript.hasNoValue()); |
869 | if (injectedScript.hasNoValue()) |
870 | return JSON::ArrayOf<Protocol::Debugger::CallFrame>::create(); |
871 | |
872 | return injectedScript.wrapCallFrames(m_currentCallStack.get()); |
873 | } |
874 | |
875 | String InspectorDebuggerAgent::sourceMapURLForScript(const Script& script) |
876 | { |
877 | return script.sourceMappingURL; |
878 | } |
879 | |
880 | void InspectorDebuggerAgent::setPauseForInternalScripts(ErrorString&, bool shouldPause) |
881 | { |
882 | if (shouldPause == m_pauseForInternalScripts) |
883 | return; |
884 | |
885 | m_pauseForInternalScripts = shouldPause; |
886 | |
887 | if (m_pauseForInternalScripts) |
888 | m_scriptDebugServer.clearBlacklist(); |
889 | } |
890 | |
891 | static bool isWebKitInjectedScript(const String& sourceURL) |
892 | { |
893 | return sourceURL.startsWith("__InjectedScript_" ) && sourceURL.endsWith(".js" ); |
894 | } |
895 | |
896 | void InspectorDebuggerAgent::didParseSource(JSC::SourceID sourceID, const Script& script) |
897 | { |
898 | String scriptIDStr = String::number(sourceID); |
899 | bool hasSourceURL = !script.sourceURL.isEmpty(); |
900 | String sourceURL = script.sourceURL; |
901 | String sourceMappingURL = sourceMapURLForScript(script); |
902 | |
903 | const bool isModule = script.sourceProvider->sourceType() == JSC::SourceProviderSourceType::Module; |
904 | const bool* isContentScript = script.isContentScript ? &script.isContentScript : nullptr; |
905 | String* sourceURLParam = hasSourceURL ? &sourceURL : nullptr; |
906 | String* sourceMapURLParam = sourceMappingURL.isEmpty() ? nullptr : &sourceMappingURL; |
907 | |
908 | m_frontendDispatcher->scriptParsed(scriptIDStr, script.url, script.startLine, script.startColumn, script.endLine, script.endColumn, isContentScript, sourceURLParam, sourceMapURLParam, isModule ? &isModule : nullptr); |
909 | |
910 | m_scripts.set(sourceID, script); |
911 | |
912 | if (hasSourceURL && isWebKitInjectedScript(sourceURL) && !m_pauseForInternalScripts) |
913 | m_scriptDebugServer.addToBlacklist(sourceID); |
914 | |
915 | String scriptURLForBreakpoints = hasSourceURL ? script.sourceURL : script.url; |
916 | if (scriptURLForBreakpoints.isEmpty()) |
917 | return; |
918 | |
919 | for (auto& entry : m_javaScriptBreakpoints) { |
920 | RefPtr<JSON::Object> breakpointObject = entry.value; |
921 | |
922 | bool isRegex; |
923 | String url; |
924 | breakpointObject->getBoolean("isRegex"_s , isRegex); |
925 | breakpointObject->getString("url"_s , url); |
926 | if (!matches(scriptURLForBreakpoints, url, isRegex)) |
927 | continue; |
928 | |
929 | ScriptBreakpoint scriptBreakpoint; |
930 | breakpointObject->getInteger("lineNumber"_s , scriptBreakpoint.lineNumber); |
931 | breakpointObject->getInteger("columnNumber"_s , scriptBreakpoint.columnNumber); |
932 | breakpointObject->getString("condition"_s , scriptBreakpoint.condition); |
933 | breakpointObject->getBoolean("autoContinue"_s , scriptBreakpoint.autoContinue); |
934 | breakpointObject->getInteger("ignoreCount"_s , scriptBreakpoint.ignoreCount); |
935 | ErrorString errorString; |
936 | RefPtr<JSON::Array> actions; |
937 | breakpointObject->getArray("actions"_s , actions); |
938 | if (!breakpointActionsFromProtocol(errorString, actions, &scriptBreakpoint.actions)) { |
939 | ASSERT_NOT_REACHED(); |
940 | continue; |
941 | } |
942 | |
943 | JSC::Breakpoint breakpoint(sourceID, scriptBreakpoint.lineNumber, scriptBreakpoint.columnNumber, scriptBreakpoint.condition, scriptBreakpoint.autoContinue, scriptBreakpoint.ignoreCount); |
944 | resolveBreakpoint(script, breakpoint); |
945 | if (!breakpoint.resolved) |
946 | continue; |
947 | |
948 | bool existing; |
949 | setBreakpoint(breakpoint, existing); |
950 | if (existing) |
951 | continue; |
952 | |
953 | String breakpointIdentifier = entry.key; |
954 | didSetBreakpoint(breakpoint, breakpointIdentifier, scriptBreakpoint); |
955 | |
956 | m_frontendDispatcher->breakpointResolved(breakpointIdentifier, buildDebuggerLocation(breakpoint)); |
957 | } |
958 | } |
959 | |
960 | void InspectorDebuggerAgent::failedToParseSource(const String& url, const String& data, int firstLine, int errorLine, const String& errorMessage) |
961 | { |
962 | m_frontendDispatcher->scriptFailedToParse(url, data, firstLine, errorLine, errorMessage); |
963 | } |
964 | |
965 | void InspectorDebuggerAgent::didPause(JSC::ExecState& scriptState, JSC::JSValue callFrames, JSC::JSValue exceptionOrCaughtValue) |
966 | { |
967 | ASSERT(!m_pausedScriptState); |
968 | m_pausedScriptState = &scriptState; |
969 | m_currentCallStack = { scriptState.vm(), callFrames }; |
970 | |
971 | InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(&scriptState); |
972 | |
973 | // If a high level pause pause reason is not already set, try to infer a reason from the debugger. |
974 | if (m_breakReason == DebuggerFrontendDispatcher::Reason::Other) { |
975 | switch (m_scriptDebugServer.reasonForPause()) { |
976 | case JSC::Debugger::PausedForBreakpoint: { |
977 | JSC::BreakpointID debuggerBreakpointId = m_scriptDebugServer.pausingBreakpointID(); |
978 | if (debuggerBreakpointId != m_continueToLocationBreakpointID) { |
979 | m_breakReason = DebuggerFrontendDispatcher::Reason::Breakpoint; |
980 | m_breakData = buildBreakpointPauseReason(debuggerBreakpointId); |
981 | } |
982 | break; |
983 | } |
984 | case JSC::Debugger::PausedForDebuggerStatement: |
985 | m_breakReason = DebuggerFrontendDispatcher::Reason::DebuggerStatement; |
986 | m_breakData = nullptr; |
987 | break; |
988 | case JSC::Debugger::PausedForException: |
989 | m_breakReason = DebuggerFrontendDispatcher::Reason::Exception; |
990 | m_breakData = buildExceptionPauseReason(exceptionOrCaughtValue, injectedScript); |
991 | break; |
992 | case JSC::Debugger::PausedAtStatement: |
993 | case JSC::Debugger::PausedAtExpression: |
994 | case JSC::Debugger::PausedBeforeReturn: |
995 | case JSC::Debugger::PausedAtEndOfProgram: |
996 | // Pause was just stepping. Nothing to report. |
997 | break; |
998 | case JSC::Debugger::NotPaused: |
999 | ASSERT_NOT_REACHED(); |
1000 | break; |
1001 | } |
1002 | } |
1003 | |
1004 | // Set $exception to the exception or caught value. |
1005 | if (exceptionOrCaughtValue && !injectedScript.hasNoValue()) { |
1006 | injectedScript.setExceptionValue(exceptionOrCaughtValue); |
1007 | m_hasExceptionValue = true; |
1008 | } |
1009 | |
1010 | m_conditionToDispatchResumed = ShouldDispatchResumed::No; |
1011 | m_enablePauseWhenIdle = false; |
1012 | |
1013 | RefPtr<Protocol::Console::StackTrace> asyncStackTrace; |
1014 | if (m_currentAsyncCallIdentifier) { |
1015 | auto it = m_pendingAsyncCalls.find(m_currentAsyncCallIdentifier.value()); |
1016 | if (it != m_pendingAsyncCalls.end()) |
1017 | asyncStackTrace = it->value->buildInspectorObject(); |
1018 | } |
1019 | |
1020 | m_frontendDispatcher->paused(currentCallFrames(injectedScript), m_breakReason, m_breakData, asyncStackTrace); |
1021 | |
1022 | m_javaScriptPauseScheduled = false; |
1023 | |
1024 | if (m_continueToLocationBreakpointID != JSC::noBreakpointID) { |
1025 | m_scriptDebugServer.removeBreakpoint(m_continueToLocationBreakpointID); |
1026 | m_continueToLocationBreakpointID = JSC::noBreakpointID; |
1027 | } |
1028 | |
1029 | RefPtr<Stopwatch> stopwatch = m_injectedScriptManager.inspectorEnvironment().executionStopwatch(); |
1030 | if (stopwatch && stopwatch->isActive()) { |
1031 | stopwatch->stop(); |
1032 | m_didPauseStopwatch = true; |
1033 | } |
1034 | } |
1035 | |
1036 | void InspectorDebuggerAgent::breakpointActionSound(int breakpointActionIdentifier) |
1037 | { |
1038 | m_frontendDispatcher->playBreakpointActionSound(breakpointActionIdentifier); |
1039 | } |
1040 | |
1041 | void InspectorDebuggerAgent::breakpointActionProbe(JSC::ExecState& scriptState, const ScriptBreakpointAction& action, unsigned batchId, unsigned sampleId, JSC::JSValue sample) |
1042 | { |
1043 | InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(&scriptState); |
1044 | auto payload = injectedScript.wrapObject(sample, objectGroupForBreakpointAction(action), true); |
1045 | auto result = Protocol::Debugger::ProbeSample::create() |
1046 | .setProbeId(action.identifier) |
1047 | .setBatchId(batchId) |
1048 | .setSampleId(sampleId) |
1049 | .setTimestamp(m_injectedScriptManager.inspectorEnvironment().executionStopwatch()->elapsedTime().seconds()) |
1050 | .setPayload(WTFMove(payload)) |
1051 | .release(); |
1052 | m_frontendDispatcher->didSampleProbe(WTFMove(result)); |
1053 | } |
1054 | |
1055 | void InspectorDebuggerAgent::didContinue() |
1056 | { |
1057 | if (m_didPauseStopwatch) { |
1058 | m_didPauseStopwatch = false; |
1059 | m_injectedScriptManager.inspectorEnvironment().executionStopwatch()->start(); |
1060 | } |
1061 | |
1062 | m_pausedScriptState = nullptr; |
1063 | m_currentCallStack = { }; |
1064 | m_injectedScriptManager.releaseObjectGroup(InspectorDebuggerAgent::backtraceObjectGroup); |
1065 | clearBreakDetails(); |
1066 | clearExceptionValue(); |
1067 | |
1068 | if (m_conditionToDispatchResumed == ShouldDispatchResumed::WhenContinued) |
1069 | m_frontendDispatcher->resumed(); |
1070 | } |
1071 | |
1072 | void InspectorDebuggerAgent::breakProgram(DebuggerFrontendDispatcher::Reason breakReason, RefPtr<JSON::Object>&& data) |
1073 | { |
1074 | m_breakReason = breakReason; |
1075 | m_breakData = WTFMove(data); |
1076 | m_scriptDebugServer.breakProgram(); |
1077 | } |
1078 | |
1079 | void InspectorDebuggerAgent::clearInspectorBreakpointState() |
1080 | { |
1081 | ErrorString dummyError; |
1082 | for (const String& identifier : copyToVector(m_breakpointIdentifierToDebugServerBreakpointIDs.keys())) |
1083 | removeBreakpoint(dummyError, identifier); |
1084 | |
1085 | m_javaScriptBreakpoints.clear(); |
1086 | |
1087 | clearDebuggerBreakpointState(); |
1088 | } |
1089 | |
1090 | void InspectorDebuggerAgent::clearDebuggerBreakpointState() |
1091 | { |
1092 | { |
1093 | JSC::JSLockHolder holder(m_scriptDebugServer.vm()); |
1094 | m_scriptDebugServer.clearBreakpointActions(); |
1095 | m_scriptDebugServer.clearBreakpoints(); |
1096 | m_scriptDebugServer.clearBlacklist(); |
1097 | } |
1098 | |
1099 | m_pausedScriptState = nullptr; |
1100 | m_currentCallStack = { }; |
1101 | m_scripts.clear(); |
1102 | m_breakpointIdentifierToDebugServerBreakpointIDs.clear(); |
1103 | m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.clear(); |
1104 | m_continueToLocationBreakpointID = JSC::noBreakpointID; |
1105 | clearBreakDetails(); |
1106 | m_javaScriptPauseScheduled = false; |
1107 | m_hasExceptionValue = false; |
1108 | |
1109 | if (isPaused()) { |
1110 | m_scriptDebugServer.continueProgram(); |
1111 | m_frontendDispatcher->resumed(); |
1112 | } |
1113 | } |
1114 | |
1115 | void InspectorDebuggerAgent::didClearGlobalObject() |
1116 | { |
1117 | // Clear breakpoints from the debugger, but keep the inspector's model of which |
1118 | // pages have what breakpoints, as the mapping is only sent to DebuggerAgent once. |
1119 | clearDebuggerBreakpointState(); |
1120 | |
1121 | clearAsyncStackTraceData(); |
1122 | |
1123 | m_frontendDispatcher->globalObjectCleared(); |
1124 | } |
1125 | |
1126 | bool InspectorDebuggerAgent::assertPaused(ErrorString& errorString) |
1127 | { |
1128 | if (!m_pausedScriptState) { |
1129 | errorString = "Can only perform operation while paused."_s ; |
1130 | return false; |
1131 | } |
1132 | |
1133 | return true; |
1134 | } |
1135 | |
1136 | void InspectorDebuggerAgent::clearBreakDetails() |
1137 | { |
1138 | m_breakReason = DebuggerFrontendDispatcher::Reason::Other; |
1139 | m_breakData = nullptr; |
1140 | } |
1141 | |
1142 | void InspectorDebuggerAgent::clearExceptionValue() |
1143 | { |
1144 | if (m_hasExceptionValue) { |
1145 | m_injectedScriptManager.clearExceptionValue(); |
1146 | m_hasExceptionValue = false; |
1147 | } |
1148 | } |
1149 | |
1150 | void InspectorDebuggerAgent::clearAsyncStackTraceData() |
1151 | { |
1152 | m_pendingAsyncCalls.clear(); |
1153 | m_currentAsyncCallIdentifier = WTF::nullopt; |
1154 | |
1155 | didClearAsyncStackTraceData(); |
1156 | } |
1157 | |
1158 | } // namespace Inspector |
1159 | |