1c09152c9fc56f9202bc4a6c82cb9ab811874355
[WebKit-https.git] / Source / JavaScriptCore / inspector / agents / InspectorDebuggerAgent.cpp
1 /*
2  * Copyright (C) 2010, 2013, 2015 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 "ContentSearchUtilities.h"
34 #include "InjectedScript.h"
35 #include "InjectedScriptManager.h"
36 #include "InspectorFrontendRouter.h"
37 #include "InspectorValues.h"
38 #include "RegularExpression.h"
39 #include "ScriptDebugServer.h"
40 #include "ScriptObject.h"
41 #include "ScriptValue.h"
42 #include <wtf/Stopwatch.h>
43 #include <wtf/text/WTFString.h>
44
45 namespace Inspector {
46
47 const char* InspectorDebuggerAgent::backtraceObjectGroup = "backtrace";
48
49 // Objects created and retained by evaluating breakpoint actions are put into object groups
50 // according to the breakpoint action identifier assigned by the frontend. A breakpoint may
51 // have several object groups, and objects from several backend breakpoint action instances may
52 // create objects in the same group.
53 static String objectGroupForBreakpointAction(const ScriptBreakpointAction& action)
54 {
55     DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, objectGroup, ("breakpoint-action-", AtomicString::ConstructFromLiteral));
56     return makeString(objectGroup, String::number(action.identifier));
57 }
58
59 InspectorDebuggerAgent::InspectorDebuggerAgent(AgentContext& context)
60     : InspectorAgentBase(ASCIILiteral("Debugger"))
61     , m_injectedScriptManager(context.injectedScriptManager)
62     , m_frontendDispatcher(std::make_unique<DebuggerFrontendDispatcher>(context.frontendRouter))
63     , m_backendDispatcher(DebuggerBackendDispatcher::create(context.backendDispatcher, this))
64     , m_continueToLocationBreakpointID(JSC::noBreakpointID)
65 {
66     // FIXME: make breakReason optional so that there was no need to init it with "other".
67     clearBreakDetails();
68 }
69
70 InspectorDebuggerAgent::~InspectorDebuggerAgent()
71 {
72 }
73
74 void InspectorDebuggerAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*)
75 {
76 }
77
78 void InspectorDebuggerAgent::willDestroyFrontendAndBackend(DisconnectReason reason)
79 {
80     bool skipRecompile = reason == DisconnectReason::InspectedTargetDestroyed;
81     disable(skipRecompile);
82 }
83
84 void InspectorDebuggerAgent::enable()
85 {
86     if (m_enabled)
87         return;
88
89     scriptDebugServer().setBreakpointsActivated(true);
90     scriptDebugServer().addListener(this);
91
92     if (m_listener)
93         m_listener->debuggerWasEnabled();
94
95     m_enabled = true;
96 }
97
98 void InspectorDebuggerAgent::disable(bool isBeingDestroyed)
99 {
100     if (!m_enabled)
101         return;
102
103     scriptDebugServer().removeListener(this, isBeingDestroyed);
104     clearInspectorBreakpointState();
105
106     ASSERT(m_javaScriptBreakpoints.isEmpty());
107
108     if (m_listener)
109         m_listener->debuggerWasDisabled();
110
111     m_enabled = false;
112 }
113
114 void InspectorDebuggerAgent::enable(ErrorString&)
115 {
116     enable();
117 }
118
119 void InspectorDebuggerAgent::disable(ErrorString&)
120 {
121     disable(false);
122 }
123
124 void InspectorDebuggerAgent::setBreakpointsActive(ErrorString&, bool active)
125 {
126     if (active)
127         scriptDebugServer().activateBreakpoints();
128     else
129         scriptDebugServer().deactivateBreakpoints();
130 }
131
132 bool InspectorDebuggerAgent::isPaused()
133 {
134     return scriptDebugServer().isPaused();
135 }
136
137 void InspectorDebuggerAgent::setSuppressAllPauses(bool suppress)
138 {
139     scriptDebugServer().setSuppressAllPauses(suppress);
140 }
141
142 static RefPtr<InspectorObject> buildAssertPauseReason(const String& message)
143 {
144     auto reason = Inspector::Protocol::Debugger::AssertPauseReason::create().release();
145     if (!message.isNull())
146         reason->setMessage(message);
147     return reason->openAccessors();
148 }
149
150 static RefPtr<InspectorObject> buildCSPViolationPauseReason(const String& directiveText)
151 {
152     auto reason = Inspector::Protocol::Debugger::CSPViolationPauseReason::create()
153         .setDirective(directiveText)
154         .release();
155     return reason->openAccessors();
156 }
157
158 RefPtr<InspectorObject> InspectorDebuggerAgent::buildBreakpointPauseReason(JSC::BreakpointID debuggerBreakpointIdentifier)
159 {
160     ASSERT(debuggerBreakpointIdentifier != JSC::noBreakpointID);
161     auto it = m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.find(debuggerBreakpointIdentifier);
162     if (it == m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.end())
163         return nullptr;
164
165     auto reason = Inspector::Protocol::Debugger::BreakpointPauseReason::create()
166         .setBreakpointId(it->value)
167         .release();
168     return reason->openAccessors();
169 }
170
171 RefPtr<InspectorObject> InspectorDebuggerAgent::buildExceptionPauseReason(const Deprecated::ScriptValue& exception, const InjectedScript& injectedScript)
172 {
173     ASSERT(!exception.hasNoValue());
174     if (exception.hasNoValue())
175         return nullptr;
176
177     ASSERT(!injectedScript.hasNoValue());
178     if (injectedScript.hasNoValue())
179         return nullptr;
180
181     return injectedScript.wrapObject(exception, InspectorDebuggerAgent::backtraceObjectGroup)->openAccessors();
182 }
183
184 void InspectorDebuggerAgent::handleConsoleAssert(const String& message)
185 {
186     if (scriptDebugServer().pauseOnExceptionsState() != JSC::Debugger::DontPauseOnExceptions)
187         breakProgram(DebuggerFrontendDispatcher::Reason::Assert, buildAssertPauseReason(message));
188 }
189
190 static Ref<InspectorObject> buildObjectForBreakpointCookie(const String& url, int lineNumber, int columnNumber, const String& condition, RefPtr<InspectorArray>& actions, bool isRegex, bool autoContinue, unsigned ignoreCount)
191 {
192     Ref<InspectorObject> breakpointObject = InspectorObject::create();
193     breakpointObject->setString(ASCIILiteral("url"), url);
194     breakpointObject->setInteger(ASCIILiteral("lineNumber"), lineNumber);
195     breakpointObject->setInteger(ASCIILiteral("columnNumber"), columnNumber);
196     breakpointObject->setString(ASCIILiteral("condition"), condition);
197     breakpointObject->setBoolean(ASCIILiteral("isRegex"), isRegex);
198     breakpointObject->setBoolean(ASCIILiteral("autoContinue"), autoContinue);
199     breakpointObject->setInteger(ASCIILiteral("ignoreCount"), ignoreCount);
200
201     if (actions)
202         breakpointObject->setArray(ASCIILiteral("actions"), actions);
203
204     return breakpointObject;
205 }
206
207 static bool matches(const String& url, const String& pattern, bool isRegex)
208 {
209     if (isRegex) {
210         JSC::Yarr::RegularExpression regex(pattern, TextCaseSensitive);
211         return regex.match(url) != -1;
212     }
213     return url == pattern;
214 }
215
216 static bool breakpointActionTypeForString(const String& typeString, ScriptBreakpointActionType* output)
217 {
218     if (typeString == Inspector::Protocol::getEnumConstantValue(Inspector::Protocol::Debugger::BreakpointAction::Type::Log)) {
219         *output = ScriptBreakpointActionTypeLog;
220         return true;
221     }
222     if (typeString == Inspector::Protocol::getEnumConstantValue(Inspector::Protocol::Debugger::BreakpointAction::Type::Evaluate)) {
223         *output = ScriptBreakpointActionTypeEvaluate;
224         return true;
225     }
226     if (typeString == Inspector::Protocol::getEnumConstantValue(Inspector::Protocol::Debugger::BreakpointAction::Type::Sound)) {
227         *output = ScriptBreakpointActionTypeSound;
228         return true;
229     }
230     if (typeString == Inspector::Protocol::getEnumConstantValue(Inspector::Protocol::Debugger::BreakpointAction::Type::Probe)) {
231         *output = ScriptBreakpointActionTypeProbe;
232         return true;
233     }
234
235     return false;
236 }
237
238 bool InspectorDebuggerAgent::breakpointActionsFromProtocol(ErrorString& errorString, RefPtr<InspectorArray>& actions, BreakpointActions* result)
239 {
240     if (!actions)
241         return true;
242
243     unsigned actionsLength = actions->length();
244     if (!actionsLength)
245         return true;
246
247     result->reserveCapacity(actionsLength);
248     for (unsigned i = 0; i < actionsLength; ++i) {
249         RefPtr<InspectorValue> value = actions->get(i);
250         RefPtr<InspectorObject> object;
251         if (!value->asObject(object)) {
252             errorString = ASCIILiteral("BreakpointAction of incorrect type, expected object");
253             return false;
254         }
255
256         String typeString;
257         if (!object->getString(ASCIILiteral("type"), typeString)) {
258             errorString = ASCIILiteral("BreakpointAction had type missing");
259             return false;
260         }
261
262         ScriptBreakpointActionType type;
263         if (!breakpointActionTypeForString(typeString, &type)) {
264             errorString = ASCIILiteral("BreakpointAction had unknown type");
265             return false;
266         }
267
268         // Specifying an identifier is optional. They are used to correlate probe samples
269         // in the frontend across multiple backend probe actions and segregate object groups.
270         int identifier = 0;
271         object->getInteger(ASCIILiteral("id"), identifier);
272
273         String data;
274         object->getString(ASCIILiteral("data"), data);
275
276         result->append(ScriptBreakpointAction(type, identifier, data));
277     }
278
279     return true;
280 }
281
282 void InspectorDebuggerAgent::setBreakpointByUrl(ErrorString& errorString, int lineNumber, const String* const optionalURL, const String* const optionalURLRegex, const int* const optionalColumnNumber, const InspectorObject* options, Inspector::Protocol::Debugger::BreakpointId* outBreakpointIdentifier, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Debugger::Location>>& locations)
283 {
284     locations = Inspector::Protocol::Array<Inspector::Protocol::Debugger::Location>::create();
285     if (!optionalURL == !optionalURLRegex) {
286         errorString = ASCIILiteral("Either url or urlRegex must be specified.");
287         return;
288     }
289
290     String url = optionalURL ? *optionalURL : *optionalURLRegex;
291     int columnNumber = optionalColumnNumber ? *optionalColumnNumber : 0;
292     bool isRegex = optionalURLRegex;
293
294     String breakpointIdentifier = (isRegex ? "/" + url + "/" : url) + ':' + String::number(lineNumber) + ':' + String::number(columnNumber);
295     if (m_javaScriptBreakpoints.contains(breakpointIdentifier)) {
296         errorString = ASCIILiteral("Breakpoint at specified location already exists.");
297         return;
298     }
299
300     String condition = emptyString();
301     bool autoContinue = false;
302     unsigned ignoreCount = 0;
303     RefPtr<InspectorArray> actions;
304     if (options) {
305         options->getString(ASCIILiteral("condition"), condition);
306         options->getBoolean(ASCIILiteral("autoContinue"), autoContinue);
307         options->getArray(ASCIILiteral("actions"), actions);
308         options->getInteger(ASCIILiteral("ignoreCount"), ignoreCount);
309     }
310
311     BreakpointActions breakpointActions;
312     if (!breakpointActionsFromProtocol(errorString, actions, &breakpointActions))
313         return;
314
315     m_javaScriptBreakpoints.set(breakpointIdentifier, buildObjectForBreakpointCookie(url, lineNumber, columnNumber, condition, actions, isRegex, autoContinue, ignoreCount));
316
317     ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition, breakpointActions, autoContinue, ignoreCount);
318     for (ScriptsMap::iterator it = m_scripts.begin(); it != m_scripts.end(); ++it) {
319         String scriptURL = !it->value.sourceURL.isEmpty() ? it->value.sourceURL : it->value.url;
320         if (!matches(scriptURL, url, isRegex))
321             continue;
322
323         RefPtr<Inspector::Protocol::Debugger::Location> location = resolveBreakpoint(breakpointIdentifier, it->key, breakpoint);
324         if (location)
325             locations->addItem(WTF::move(location));
326     }
327     *outBreakpointIdentifier = breakpointIdentifier;
328 }
329
330 static bool parseLocation(ErrorString& errorString, const InspectorObject& location, JSC::SourceID& sourceID, unsigned& lineNumber, unsigned& columnNumber)
331 {
332     String scriptIDStr;
333     if (!location.getString(ASCIILiteral("scriptId"), scriptIDStr) || !location.getInteger(ASCIILiteral("lineNumber"), lineNumber)) {
334         sourceID = JSC::noSourceID;
335         errorString = ASCIILiteral("scriptId and lineNumber are required.");
336         return false;
337     }
338
339     sourceID = scriptIDStr.toIntPtr();
340     columnNumber = 0;
341     location.getInteger(ASCIILiteral("columnNumber"), columnNumber);
342     return true;
343 }
344
345 void InspectorDebuggerAgent::setBreakpoint(ErrorString& errorString, const InspectorObject& location, const InspectorObject* options, Inspector::Protocol::Debugger::BreakpointId* outBreakpointIdentifier, RefPtr<Inspector::Protocol::Debugger::Location>& actualLocation)
346 {
347     JSC::SourceID sourceID;
348     unsigned lineNumber;
349     unsigned columnNumber;
350     if (!parseLocation(errorString, location, sourceID, lineNumber, columnNumber))
351         return;
352
353     String condition = emptyString();
354     bool autoContinue = false;
355     unsigned ignoreCount = 0;
356     RefPtr<InspectorArray> actions;
357     if (options) {
358         options->getString(ASCIILiteral("condition"), condition);
359         options->getBoolean(ASCIILiteral("autoContinue"), autoContinue);
360         options->getArray(ASCIILiteral("actions"), actions);
361         options->getInteger(ASCIILiteral("ignoreCount"), ignoreCount);
362     }
363
364     BreakpointActions breakpointActions;
365     if (!breakpointActionsFromProtocol(errorString, actions, &breakpointActions))
366         return;
367
368     String breakpointIdentifier = String::number(sourceID) + ':' + String::number(lineNumber) + ':' + String::number(columnNumber);
369     if (m_breakpointIdentifierToDebugServerBreakpointIDs.find(breakpointIdentifier) != m_breakpointIdentifierToDebugServerBreakpointIDs.end()) {
370         errorString = ASCIILiteral("Breakpoint at specified location already exists.");
371         return;
372     }
373
374     ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition, breakpointActions, autoContinue, ignoreCount);
375     actualLocation = resolveBreakpoint(breakpointIdentifier, sourceID, breakpoint);
376     if (!actualLocation) {
377         errorString = ASCIILiteral("Could not resolve breakpoint");
378         return;
379     }
380
381     *outBreakpointIdentifier = breakpointIdentifier;
382 }
383
384 void InspectorDebuggerAgent::removeBreakpoint(ErrorString&, const String& breakpointIdentifier)
385 {
386     m_javaScriptBreakpoints.remove(breakpointIdentifier);
387
388     for (JSC::BreakpointID breakpointID : m_breakpointIdentifierToDebugServerBreakpointIDs.take(breakpointIdentifier)) {
389         m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.remove(breakpointID);
390
391         const BreakpointActions& breakpointActions = scriptDebugServer().getActionsForBreakpoint(breakpointID);
392         for (auto& action : breakpointActions)
393             m_injectedScriptManager.releaseObjectGroup(objectGroupForBreakpointAction(action));
394
395         scriptDebugServer().removeBreakpoint(breakpointID);
396     }
397 }
398
399 void InspectorDebuggerAgent::continueToLocation(ErrorString& errorString, const InspectorObject& location)
400 {
401     if (m_continueToLocationBreakpointID != JSC::noBreakpointID) {
402         scriptDebugServer().removeBreakpoint(m_continueToLocationBreakpointID);
403         m_continueToLocationBreakpointID = JSC::noBreakpointID;
404     }
405
406     JSC::SourceID sourceID;
407     unsigned lineNumber;
408     unsigned columnNumber;
409     if (!parseLocation(errorString, location, sourceID, lineNumber, columnNumber))
410         return;
411
412     ScriptBreakpoint breakpoint(lineNumber, columnNumber, "", false, 0);
413     m_continueToLocationBreakpointID = scriptDebugServer().setBreakpoint(sourceID, breakpoint, &lineNumber, &columnNumber);
414     resume(errorString);
415 }
416
417 RefPtr<Inspector::Protocol::Debugger::Location> InspectorDebuggerAgent::resolveBreakpoint(const String& breakpointIdentifier, JSC::SourceID sourceID, const ScriptBreakpoint& breakpoint)
418 {
419     ScriptsMap::iterator scriptIterator = m_scripts.find(sourceID);
420     if (scriptIterator == m_scripts.end())
421         return nullptr;
422     Script& script = scriptIterator->value;
423     if (breakpoint.lineNumber < script.startLine || script.endLine < breakpoint.lineNumber)
424         return nullptr;
425
426     unsigned actualLineNumber;
427     unsigned actualColumnNumber;
428     JSC::BreakpointID debugServerBreakpointID = scriptDebugServer().setBreakpoint(sourceID, breakpoint, &actualLineNumber, &actualColumnNumber);
429     if (debugServerBreakpointID == JSC::noBreakpointID)
430         return nullptr;
431
432     BreakpointIdentifierToDebugServerBreakpointIDsMap::iterator debugServerBreakpointIDsIterator = m_breakpointIdentifierToDebugServerBreakpointIDs.find(breakpointIdentifier);
433     if (debugServerBreakpointIDsIterator == m_breakpointIdentifierToDebugServerBreakpointIDs.end())
434         debugServerBreakpointIDsIterator = m_breakpointIdentifierToDebugServerBreakpointIDs.set(breakpointIdentifier, Vector<JSC::BreakpointID>()).iterator;
435     debugServerBreakpointIDsIterator->value.append(debugServerBreakpointID);
436     
437     m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.set(debugServerBreakpointID, breakpointIdentifier);
438
439     auto location = Inspector::Protocol::Debugger::Location::create()
440         .setScriptId(String::number(sourceID))
441         .setLineNumber(actualLineNumber)
442         .release();
443     location->setColumnNumber(actualColumnNumber);
444     return WTF::move(location);
445 }
446
447 void InspectorDebuggerAgent::searchInContent(ErrorString& error, const String& scriptIDStr, const String& query, const bool* optionalCaseSensitive, const bool* optionalIsRegex, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::GenericTypes::SearchMatch>>& results)
448 {
449     JSC::SourceID sourceID = scriptIDStr.toIntPtr();
450     auto it = m_scripts.find(sourceID);
451     if (it == m_scripts.end()) {
452         error = ASCIILiteral("No script for id: ") + scriptIDStr;
453         return;
454     }
455
456     bool isRegex = optionalIsRegex ? *optionalIsRegex : false;
457     bool caseSensitive = optionalCaseSensitive ? *optionalCaseSensitive : false;
458     results = ContentSearchUtilities::searchInTextByLines(it->value.source, query, caseSensitive, isRegex);
459 }
460
461 void InspectorDebuggerAgent::getScriptSource(ErrorString& error, const String& scriptIDStr, String* scriptSource)
462 {
463     JSC::SourceID sourceID = scriptIDStr.toIntPtr();
464     ScriptsMap::iterator it = m_scripts.find(sourceID);
465     if (it != m_scripts.end())
466         *scriptSource = it->value.source;
467     else
468         error = ASCIILiteral("No script for id: ") + scriptIDStr;
469 }
470
471 void InspectorDebuggerAgent::getFunctionDetails(ErrorString& errorString, const String& functionId, RefPtr<Inspector::Protocol::Debugger::FunctionDetails>& details)
472 {
473     InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(functionId);
474     if (injectedScript.hasNoValue()) {
475         errorString = ASCIILiteral("Function object id is obsolete");
476         return;
477     }
478
479     injectedScript.getFunctionDetails(errorString, functionId, &details);
480 }
481
482 void InspectorDebuggerAgent::schedulePauseOnNextStatement(DebuggerFrontendDispatcher::Reason breakReason, RefPtr<InspectorObject>&& data)
483 {
484     if (m_javaScriptPauseScheduled)
485         return;
486
487     m_breakReason = breakReason;
488     m_breakAuxData = WTF::move(data);
489     scriptDebugServer().setPauseOnNextStatement(true);
490 }
491
492 void InspectorDebuggerAgent::cancelPauseOnNextStatement()
493 {
494     if (m_javaScriptPauseScheduled)
495         return;
496
497     clearBreakDetails();
498     scriptDebugServer().setPauseOnNextStatement(false);
499 }
500
501 void InspectorDebuggerAgent::pause(ErrorString&)
502 {
503     schedulePauseOnNextStatement(DebuggerFrontendDispatcher::Reason::PauseOnNextStatement, nullptr);
504
505     m_javaScriptPauseScheduled = true;
506 }
507
508 void InspectorDebuggerAgent::resume(ErrorString& errorString)
509 {
510     if (!assertPaused(errorString))
511         return;
512
513     scriptDebugServer().continueProgram();
514 }
515
516 void InspectorDebuggerAgent::stepOver(ErrorString& errorString)
517 {
518     if (!assertPaused(errorString))
519         return;
520
521     scriptDebugServer().stepOverStatement();
522 }
523
524 void InspectorDebuggerAgent::stepInto(ErrorString& errorString)
525 {
526     if (!assertPaused(errorString))
527         return;
528
529     scriptDebugServer().stepIntoStatement();
530
531     if (m_listener)
532         m_listener->stepInto();
533 }
534
535 void InspectorDebuggerAgent::stepOut(ErrorString& errorString)
536 {
537     if (!assertPaused(errorString))
538         return;
539
540     scriptDebugServer().stepOutOfFunction();
541 }
542
543 void InspectorDebuggerAgent::setPauseOnExceptions(ErrorString& errorString, const String& stringPauseState)
544 {
545     JSC::Debugger::PauseOnExceptionsState pauseState;
546     if (stringPauseState == "none")
547         pauseState = JSC::Debugger::DontPauseOnExceptions;
548     else if (stringPauseState == "all")
549         pauseState = JSC::Debugger::PauseOnAllExceptions;
550     else if (stringPauseState == "uncaught")
551         pauseState = JSC::Debugger::PauseOnUncaughtExceptions;
552     else {
553         errorString = ASCIILiteral("Unknown pause on exceptions mode: ") + stringPauseState;
554         return;
555     }
556
557     scriptDebugServer().setPauseOnExceptionsState(static_cast<JSC::Debugger::PauseOnExceptionsState>(pauseState));
558     if (scriptDebugServer().pauseOnExceptionsState() != pauseState)
559         errorString = ASCIILiteral("Internal error. Could not change pause on exceptions state");
560 }
561
562 void InspectorDebuggerAgent::evaluateOnCallFrame(ErrorString& errorString, const String& callFrameId, const String& expression, const String* const objectGroup, const bool* const includeCommandLineAPI, const bool* const doNotPauseOnExceptionsAndMuteConsole, const bool* const returnByValue, const bool* generatePreview, const bool* saveResult, RefPtr<Inspector::Protocol::Runtime::RemoteObject>& result, Inspector::Protocol::OptOutput<bool>* wasThrown, Inspector::Protocol::OptOutput<int>* savedResultIndex)
563 {
564     InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(callFrameId);
565     if (injectedScript.hasNoValue()) {
566         errorString = ASCIILiteral("Inspected frame has gone");
567         return;
568     }
569
570     JSC::Debugger::PauseOnExceptionsState previousPauseOnExceptionsState = scriptDebugServer().pauseOnExceptionsState();
571     if (doNotPauseOnExceptionsAndMuteConsole ? *doNotPauseOnExceptionsAndMuteConsole : false) {
572         if (previousPauseOnExceptionsState != JSC::Debugger::DontPauseOnExceptions)
573             scriptDebugServer().setPauseOnExceptionsState(JSC::Debugger::DontPauseOnExceptions);
574         muteConsole();
575     }
576
577     injectedScript.evaluateOnCallFrame(errorString, m_currentCallStack, callFrameId, expression, objectGroup ? *objectGroup : "", includeCommandLineAPI ? *includeCommandLineAPI : false, returnByValue ? *returnByValue : false, generatePreview ? *generatePreview : false, saveResult ? *saveResult : false, &result, wasThrown, savedResultIndex);
578
579     if (doNotPauseOnExceptionsAndMuteConsole ? *doNotPauseOnExceptionsAndMuteConsole : false) {
580         unmuteConsole();
581         if (scriptDebugServer().pauseOnExceptionsState() != previousPauseOnExceptionsState)
582             scriptDebugServer().setPauseOnExceptionsState(previousPauseOnExceptionsState);
583     }
584 }
585
586 void InspectorDebuggerAgent::setOverlayMessage(ErrorString&, const String*)
587 {
588 }
589
590 void InspectorDebuggerAgent::scriptExecutionBlockedByCSP(const String& directiveText)
591 {
592     if (scriptDebugServer().pauseOnExceptionsState() != JSC::Debugger::DontPauseOnExceptions)
593         breakProgram(DebuggerFrontendDispatcher::Reason::CSPViolation, buildCSPViolationPauseReason(directiveText));
594 }
595
596 Ref<Inspector::Protocol::Array<Inspector::Protocol::Debugger::CallFrame>> InspectorDebuggerAgent::currentCallFrames(InjectedScript injectedScript)
597 {
598     ASSERT(!injectedScript.hasNoValue());
599     if (injectedScript.hasNoValue())
600         return Inspector::Protocol::Array<Inspector::Protocol::Debugger::CallFrame>::create();
601
602     return injectedScript.wrapCallFrames(m_currentCallStack);
603 }
604
605 String InspectorDebuggerAgent::sourceMapURLForScript(const Script& script)
606 {
607     return script.sourceMappingURL;
608 }
609
610 void InspectorDebuggerAgent::didParseSource(JSC::SourceID sourceID, const Script& script)
611 {
612     bool hasSourceURL = !script.sourceURL.isEmpty();
613     String scriptURL = hasSourceURL ? script.sourceURL : script.url;
614     bool* hasSourceURLParam = hasSourceURL ? &hasSourceURL : nullptr;
615     String sourceMappingURL = sourceMapURLForScript(script);
616     String* sourceMapURLParam = sourceMappingURL.isNull() ? nullptr : &sourceMappingURL;
617     const bool* isContentScript = script.isContentScript ? &script.isContentScript : nullptr;
618     String scriptIDStr = String::number(sourceID);
619     m_frontendDispatcher->scriptParsed(scriptIDStr, scriptURL, script.startLine, script.startColumn, script.endLine, script.endColumn, isContentScript, sourceMapURLParam, hasSourceURLParam);
620
621     m_scripts.set(sourceID, script);
622
623     if (scriptURL.isEmpty())
624         return;
625
626     for (auto it = m_javaScriptBreakpoints.begin(), end = m_javaScriptBreakpoints.end(); it != end; ++it) {
627         RefPtr<InspectorObject> breakpointObject;
628         if (!it->value->asObject(breakpointObject))
629             return;
630
631         bool isRegex;
632         breakpointObject->getBoolean(ASCIILiteral("isRegex"), isRegex);
633         String url;
634         breakpointObject->getString(ASCIILiteral("url"), url);
635         if (!matches(scriptURL, url, isRegex))
636             continue;
637
638         ScriptBreakpoint breakpoint;
639         breakpointObject->getInteger(ASCIILiteral("lineNumber"), breakpoint.lineNumber);
640         breakpointObject->getInteger(ASCIILiteral("columnNumber"), breakpoint.columnNumber);
641         breakpointObject->getString(ASCIILiteral("condition"), breakpoint.condition);
642         breakpointObject->getBoolean(ASCIILiteral("autoContinue"), breakpoint.autoContinue);
643         breakpointObject->getInteger(ASCIILiteral("ignoreCount"), breakpoint.ignoreCount);
644         ErrorString errorString;
645         RefPtr<InspectorArray> actions;
646         breakpointObject->getArray(ASCIILiteral("actions"), actions);
647         if (!breakpointActionsFromProtocol(errorString, actions, &breakpoint.actions)) {
648             ASSERT_NOT_REACHED();
649             continue;
650         }
651
652         RefPtr<Inspector::Protocol::Debugger::Location> location = resolveBreakpoint(it->key, sourceID, breakpoint);
653         if (location)
654             m_frontendDispatcher->breakpointResolved(it->key, location);
655     }
656 }
657
658 void InspectorDebuggerAgent::failedToParseSource(const String& url, const String& data, int firstLine, int errorLine, const String& errorMessage)
659 {
660     m_frontendDispatcher->scriptFailedToParse(url, data, firstLine, errorLine, errorMessage);
661 }
662
663 void InspectorDebuggerAgent::didPause(JSC::ExecState* scriptState, const Deprecated::ScriptValue& callFrames, const Deprecated::ScriptValue& exceptionOrCaughtValue)
664 {
665     ASSERT(scriptState && !m_pausedScriptState);
666     m_pausedScriptState = scriptState;
667     m_currentCallStack = callFrames;
668
669     InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(scriptState);
670
671     // If a high level pause pause reason is not already set, try to infer a reason from the debugger.
672     if (m_breakReason == DebuggerFrontendDispatcher::Reason::Other) {
673         switch (scriptDebugServer().reasonForPause()) {
674         case JSC::Debugger::PausedForBreakpoint: {
675             JSC::BreakpointID debuggerBreakpointId = scriptDebugServer().pausingBreakpointID();
676             if (debuggerBreakpointId != m_continueToLocationBreakpointID) {
677                 m_breakReason = DebuggerFrontendDispatcher::Reason::Breakpoint;
678                 m_breakAuxData = buildBreakpointPauseReason(debuggerBreakpointId);
679             }
680             break;
681         }
682         case JSC::Debugger::PausedForDebuggerStatement:
683             m_breakReason = DebuggerFrontendDispatcher::Reason::DebuggerStatement;
684             m_breakAuxData = nullptr;
685             break;
686         case JSC::Debugger::PausedForException:
687             m_breakReason = DebuggerFrontendDispatcher::Reason::Exception;
688             m_breakAuxData = buildExceptionPauseReason(exceptionOrCaughtValue, injectedScript);
689             break;
690         case JSC::Debugger::PausedAtStatement:
691         case JSC::Debugger::PausedAfterCall:
692         case JSC::Debugger::PausedBeforeReturn:
693         case JSC::Debugger::PausedAtStartOfProgram:
694         case JSC::Debugger::PausedAtEndOfProgram:
695             // Pause was just stepping. Nothing to report.
696             break;
697         case JSC::Debugger::NotPaused:
698             ASSERT_NOT_REACHED();
699             break;
700         }
701     }
702
703     // Set $exception to the exception or caught value.
704     if (!exceptionOrCaughtValue.hasNoValue() && !injectedScript.hasNoValue()) {
705         injectedScript.setExceptionValue(exceptionOrCaughtValue);
706         m_hasExceptionValue = true;
707     }
708
709     m_frontendDispatcher->paused(currentCallFrames(injectedScript), m_breakReason, m_breakAuxData);
710     m_javaScriptPauseScheduled = false;
711
712     if (m_continueToLocationBreakpointID != JSC::noBreakpointID) {
713         scriptDebugServer().removeBreakpoint(m_continueToLocationBreakpointID);
714         m_continueToLocationBreakpointID = JSC::noBreakpointID;
715     }
716
717     if (m_listener)
718         m_listener->didPause();
719
720     RefPtr<Stopwatch> stopwatch = m_injectedScriptManager.inspectorEnvironment().executionStopwatch();
721     if (stopwatch && stopwatch->isActive()) {
722         stopwatch->stop();
723         m_didPauseStopwatch = true;
724     }
725 }
726
727 void InspectorDebuggerAgent::breakpointActionSound(int breakpointActionIdentifier)
728 {
729     m_frontendDispatcher->playBreakpointActionSound(breakpointActionIdentifier);
730 }
731
732 void InspectorDebuggerAgent::breakpointActionProbe(JSC::ExecState* scriptState, const ScriptBreakpointAction& action, unsigned batchId, unsigned sampleId, const Deprecated::ScriptValue& sample)
733 {
734     InjectedScript injectedScript = m_injectedScriptManager.injectedScriptFor(scriptState);
735     RefPtr<Protocol::Runtime::RemoteObject> payload = injectedScript.wrapObject(sample, objectGroupForBreakpointAction(action), true);
736     auto result = Protocol::Debugger::ProbeSample::create()
737         .setProbeId(action.identifier)
738         .setBatchId(batchId)
739         .setSampleId(sampleId)
740         .setTimestamp(m_injectedScriptManager.inspectorEnvironment().executionStopwatch()->elapsedTime())
741         .setPayload(payload.release())
742         .release();
743
744     m_frontendDispatcher->didSampleProbe(WTF::move(result));
745 }
746
747 void InspectorDebuggerAgent::didContinue()
748 {
749     if (m_didPauseStopwatch) {
750         m_didPauseStopwatch = false;
751         m_injectedScriptManager.inspectorEnvironment().executionStopwatch()->start();
752     }
753
754     m_pausedScriptState = nullptr;
755     m_currentCallStack = Deprecated::ScriptValue();
756     m_injectedScriptManager.releaseObjectGroup(InspectorDebuggerAgent::backtraceObjectGroup);
757     clearBreakDetails();
758     clearExceptionValue();
759
760     m_frontendDispatcher->resumed();
761 }
762
763 void InspectorDebuggerAgent::breakProgram(DebuggerFrontendDispatcher::Reason breakReason, RefPtr<InspectorObject>&& data)
764 {
765     m_breakReason = breakReason;
766     m_breakAuxData = WTF::move(data);
767     scriptDebugServer().breakProgram();
768 }
769
770 void InspectorDebuggerAgent::clearInspectorBreakpointState()
771 {
772     ErrorString dummyError;
773     Vector<String> breakpointIdentifiers;
774     copyKeysToVector(m_breakpointIdentifierToDebugServerBreakpointIDs, breakpointIdentifiers);
775     for (const String& identifier : breakpointIdentifiers)
776         removeBreakpoint(dummyError, identifier);
777
778     m_javaScriptBreakpoints.clear();
779
780     clearDebuggerBreakpointState();
781 }
782
783 void InspectorDebuggerAgent::clearDebuggerBreakpointState()
784 {
785     scriptDebugServer().clearBreakpoints();
786
787     m_pausedScriptState = nullptr;
788     m_currentCallStack = Deprecated::ScriptValue();
789     m_scripts.clear();
790     m_breakpointIdentifierToDebugServerBreakpointIDs.clear();
791     m_debuggerBreakpointIdentifierToInspectorBreakpointIdentifier.clear();
792     m_continueToLocationBreakpointID = JSC::noBreakpointID;
793     clearBreakDetails();
794     m_javaScriptPauseScheduled = false;
795     m_hasExceptionValue = false;
796
797     scriptDebugServer().continueProgram();
798 }
799
800 void InspectorDebuggerAgent::didClearGlobalObject()
801 {
802     // Clear breakpoints from the debugger, but keep the inspector's model of which
803     // pages have what breakpoints, as the mapping is only sent to DebuggerAgent once.
804     clearDebuggerBreakpointState();
805
806     m_frontendDispatcher->globalObjectCleared();
807 }
808
809 bool InspectorDebuggerAgent::assertPaused(ErrorString& errorString)
810 {
811     if (!m_pausedScriptState) {
812         errorString = ASCIILiteral("Can only perform operation while paused.");
813         return false;
814     }
815
816     return true;
817 }
818
819 void InspectorDebuggerAgent::clearBreakDetails()
820 {
821     m_breakReason = DebuggerFrontendDispatcher::Reason::Other;
822     m_breakAuxData = nullptr;
823 }
824
825 void InspectorDebuggerAgent::clearExceptionValue()
826 {
827     if (m_hasExceptionValue) {
828         m_injectedScriptManager.clearExceptionValue();
829         m_hasExceptionValue = false;
830     }
831 }
832
833 } // namespace Inspector