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
50namespace Inspector {
51
52const 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.
58static String objectGroupForBreakpointAction(const ScriptBreakpointAction& action)
59{
60 return makeString("breakpoint-action-", action.identifier);
61}
62
63InspectorDebuggerAgent::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
74InspectorDebuggerAgent::~InspectorDebuggerAgent()
75{
76}
77
78void InspectorDebuggerAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*)
79{
80}
81
82void InspectorDebuggerAgent::willDestroyFrontendAndBackend(DisconnectReason reason)
83{
84 bool skipRecompile = reason == DisconnectReason::InspectedTargetDestroyed;
85 disable(skipRecompile);
86}
87
88void 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
101void 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
125void InspectorDebuggerAgent::enable(ErrorString&)
126{
127 enable();
128}
129
130void InspectorDebuggerAgent::disable(ErrorString&)
131{
132 disable(false);
133}
134
135bool InspectorDebuggerAgent::breakpointsActive() const
136{
137 return m_scriptDebugServer.breakpointsActive();
138}
139
140void 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
156void InspectorDebuggerAgent::setBreakpointsActive(ErrorString&, bool active)
157{
158 if (active)
159 m_scriptDebugServer.activateBreakpoints();
160 else
161 m_scriptDebugServer.deactivateBreakpoints();
162}
163
164bool InspectorDebuggerAgent::isPaused() const
165{
166 return m_scriptDebugServer.isPaused();
167}
168
169void InspectorDebuggerAgent::setSuppressAllPauses(bool suppress)
170{
171 m_scriptDebugServer.setSuppressAllPauses(suppress);
172}
173
174static 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
182static 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
190RefPtr<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
203RefPtr<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
216void 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
225InspectorDebuggerAgent::AsyncCallIdentifier InspectorDebuggerAgent::asyncCallIdentifier(AsyncCallType asyncCallType, int callbackId)
226{
227 return std::make_pair(static_cast<unsigned>(asyncCallType), callbackId);
228}
229
230void 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
256void 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
275void 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
296void 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
317static 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
334static 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
343static 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
365bool 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
409static 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
422static 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
437void 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
498void 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
550void 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
563void 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
571void InspectorDebuggerAgent::setBreakpoint(JSC::Breakpoint& breakpoint, bool& existing)
572{
573 JSC::JSLockHolder locker(m_scriptDebugServer.vm());
574 m_scriptDebugServer.setBreakpoint(breakpoint, existing);
575}
576
577void 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
594void 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
606void 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
662void 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
676void 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
686void 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
697void 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
711void 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
723void InspectorDebuggerAgent::pause(ErrorString&)
724{
725 schedulePauseOnNextStatement(DebuggerFrontendDispatcher::Reason::PauseOnNextStatement, nullptr);
726}
727
728void 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
740void InspectorDebuggerAgent::stepOver(ErrorString& errorString)
741{
742 if (!assertPaused(errorString))
743 return;
744
745 willStepAndMayBecomeIdle();
746 m_scriptDebugServer.stepOverStatement();
747}
748
749void InspectorDebuggerAgent::stepInto(ErrorString& errorString)
750{
751 if (!assertPaused(errorString))
752 return;
753
754 willStepAndMayBecomeIdle();
755 m_scriptDebugServer.stepIntoStatement();
756}
757
758void InspectorDebuggerAgent::stepOut(ErrorString& errorString)
759{
760 if (!assertPaused(errorString))
761 return;
762
763 willStepAndMayBecomeIdle();
764 m_scriptDebugServer.stepOutOfFunction();
765}
766
767void 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
778void 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
787void 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
805void 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
824void InspectorDebuggerAgent::setPauseOnAssertions(ErrorString&, bool enabled)
825{
826 m_pauseOnAssertionFailures = enabled;
827}
828
829void 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
860void InspectorDebuggerAgent::scriptExecutionBlockedByCSP(const String& directiveText)
861{
862 if (m_scriptDebugServer.pauseOnExceptionsState() != JSC::Debugger::DontPauseOnExceptions)
863 breakProgram(DebuggerFrontendDispatcher::Reason::CSPViolation, buildCSPViolationPauseReason(directiveText));
864}
865
866Ref<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
875String InspectorDebuggerAgent::sourceMapURLForScript(const Script& script)
876{
877 return script.sourceMappingURL;
878}
879
880void 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
891static bool isWebKitInjectedScript(const String& sourceURL)
892{
893 return sourceURL.startsWith("__InjectedScript_") && sourceURL.endsWith(".js");
894}
895
896void 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
960void 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
965void 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
1036void InspectorDebuggerAgent::breakpointActionSound(int breakpointActionIdentifier)
1037{
1038 m_frontendDispatcher->playBreakpointActionSound(breakpointActionIdentifier);
1039}
1040
1041void 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
1055void 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
1072void 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
1079void 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
1090void 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
1115void 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
1126bool 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
1136void InspectorDebuggerAgent::clearBreakDetails()
1137{
1138 m_breakReason = DebuggerFrontendDispatcher::Reason::Other;
1139 m_breakData = nullptr;
1140}
1141
1142void InspectorDebuggerAgent::clearExceptionValue()
1143{
1144 if (m_hasExceptionValue) {
1145 m_injectedScriptManager.clearExceptionValue();
1146 m_hasExceptionValue = false;
1147 }
1148}
1149
1150void InspectorDebuggerAgent::clearAsyncStackTraceData()
1151{
1152 m_pendingAsyncCalls.clear();
1153 m_currentAsyncCallIdentifier = WTF::nullopt;
1154
1155 didClearAsyncStackTraceData();
1156}
1157
1158} // namespace Inspector
1159