Web Inspector: Remove duplication among ScriptDebugServer subclasses
[WebKit-https.git] / Source / JavaScriptCore / inspector / ScriptDebugServer.cpp
1 /*
2  * Copyright (C) 2008, 2009, 2013, 2014 Apple Inc. All rights reserved.
3  * Copyright (C) 2010-2011 Google Inc. All rights reserved.
4  * Copyright (C) 2013 University of Washington. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1.  Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  * 2.  Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
16  *     its contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "ScriptDebugServer.h"
33
34 #include "DebuggerCallFrame.h"
35 #include "DebuggerScope.h"
36 #include "Exception.h"
37 #include "JSJavaScriptCallFrame.h"
38 #include "JSLock.h"
39 #include "JavaScriptCallFrame.h"
40 #include "ScriptValue.h"
41 #include "SourceProvider.h"
42 #include <wtf/NeverDestroyed.h>
43 #include <wtf/TemporaryChange.h>
44
45 using namespace JSC;
46
47 namespace Inspector {
48
49 ScriptDebugServer::ScriptDebugServer(VM& vm, bool isInWorkerThread)
50     : Debugger(vm, isInWorkerThread)
51 {
52 }
53
54 ScriptDebugServer::~ScriptDebugServer()
55 {
56 }
57
58 JSC::BreakpointID ScriptDebugServer::setBreakpoint(JSC::SourceID sourceID, const ScriptBreakpoint& scriptBreakpoint, unsigned* actualLineNumber, unsigned* actualColumnNumber)
59 {
60     if (!sourceID)
61         return JSC::noBreakpointID;
62
63     JSC::Breakpoint breakpoint(sourceID, scriptBreakpoint.lineNumber, scriptBreakpoint.columnNumber, scriptBreakpoint.condition, scriptBreakpoint.autoContinue, scriptBreakpoint.ignoreCount);
64     JSC::BreakpointID id = Debugger::setBreakpoint(breakpoint, *actualLineNumber, *actualColumnNumber);
65     if (id != JSC::noBreakpointID && !scriptBreakpoint.actions.isEmpty()) {
66 #ifndef NDEBUG
67         BreakpointIDToActionsMap::iterator it = m_breakpointIDToActions.find(id);
68         ASSERT(it == m_breakpointIDToActions.end());
69 #endif
70         const BreakpointActions& actions = scriptBreakpoint.actions;
71         m_breakpointIDToActions.set(id, actions);
72     }
73     return id;
74 }
75
76 void ScriptDebugServer::removeBreakpoint(JSC::BreakpointID id)
77 {
78     ASSERT(id != JSC::noBreakpointID);
79     BreakpointIDToActionsMap::iterator it = m_breakpointIDToActions.find(id);
80     if (it != m_breakpointIDToActions.end())
81         m_breakpointIDToActions.remove(it);
82
83     Debugger::removeBreakpoint(id);
84 }
85
86 bool ScriptDebugServer::evaluateBreakpointAction(const ScriptBreakpointAction& breakpointAction)
87 {
88     DebuggerCallFrame* debuggerCallFrame = currentDebuggerCallFrame();
89
90     switch (breakpointAction.type) {
91     case ScriptBreakpointActionTypeLog: {
92         dispatchBreakpointActionLog(debuggerCallFrame->exec(), breakpointAction.data);
93         break;
94     }
95     case ScriptBreakpointActionTypeEvaluate: {
96         NakedPtr<Exception> exception;
97         debuggerCallFrame->evaluate(breakpointAction.data, exception);
98         if (exception)
99             reportException(debuggerCallFrame->exec(), exception);
100         break;
101     }
102     case ScriptBreakpointActionTypeSound:
103         dispatchBreakpointActionSound(debuggerCallFrame->exec(), breakpointAction.identifier);
104         break;
105     case ScriptBreakpointActionTypeProbe: {
106         NakedPtr<Exception> exception;
107         JSValue result = debuggerCallFrame->evaluate(breakpointAction.data, exception);
108         if (exception)
109             reportException(debuggerCallFrame->exec(), exception);
110         
111         JSC::ExecState* state = debuggerCallFrame->scope()->globalObject()->globalExec();
112         Deprecated::ScriptValue wrappedResult = Deprecated::ScriptValue(state->vm(), exception ? exception->value() : result);
113         dispatchBreakpointActionProbe(state, breakpointAction, wrappedResult);
114         break;
115     }
116     default:
117         ASSERT_NOT_REACHED();
118     }
119
120     return true;
121 }
122
123 void ScriptDebugServer::clearBreakpoints()
124 {
125     Debugger::clearBreakpoints();
126     m_breakpointIDToActions.clear();
127 }
128
129 void ScriptDebugServer::dispatchDidPause(ScriptDebugListener* listener)
130 {
131     ASSERT(isPaused());
132     DebuggerCallFrame* debuggerCallFrame = currentDebuggerCallFrame();
133     JSGlobalObject* globalObject = debuggerCallFrame->scope()->globalObject();
134     JSC::ExecState* state = globalObject->globalExec();
135     RefPtr<JavaScriptCallFrame> javaScriptCallFrame = JavaScriptCallFrame::create(debuggerCallFrame);
136     JSValue jsCallFrame = toJS(state, globalObject, javaScriptCallFrame.get());
137
138     listener->didPause(state, Deprecated::ScriptValue(state->vm(), jsCallFrame), exceptionOrCaughtValue(state));
139 }
140
141 void ScriptDebugServer::dispatchBreakpointActionLog(ExecState* exec, const String& message)
142 {
143     if (m_callingListeners)
144         return;
145
146     if (m_listeners.isEmpty())
147         return;
148
149     TemporaryChange<bool> change(m_callingListeners, true);
150
151     Vector<ScriptDebugListener*> listenersCopy;
152     copyToVector(m_listeners, listenersCopy);
153     for (auto* listener : listenersCopy)
154         listener->breakpointActionLog(exec, message);
155 }
156
157 void ScriptDebugServer::dispatchBreakpointActionSound(ExecState*, int breakpointActionIdentifier)
158 {
159     if (m_callingListeners)
160         return;
161
162     if (m_listeners.isEmpty())
163         return;
164
165     TemporaryChange<bool> change(m_callingListeners, true);
166
167     Vector<ScriptDebugListener*> listenersCopy;
168     copyToVector(m_listeners, listenersCopy);
169     for (auto* listener : listenersCopy)
170         listener->breakpointActionSound(breakpointActionIdentifier);
171 }
172
173 void ScriptDebugServer::dispatchBreakpointActionProbe(ExecState* exec, const ScriptBreakpointAction& action, const Deprecated::ScriptValue& sampleValue)
174 {
175     if (m_callingListeners)
176         return;
177
178     if (m_listeners.isEmpty())
179         return;
180
181     TemporaryChange<bool> change(m_callingListeners, true);
182
183     unsigned sampleId = m_nextProbeSampleId++;
184
185     Vector<ScriptDebugListener*> listenersCopy;
186     copyToVector(m_listeners, listenersCopy);
187     for (auto* listener : listenersCopy)
188         listener->breakpointActionProbe(exec, action, m_currentProbeBatchId, sampleId, sampleValue);
189 }
190
191 void ScriptDebugServer::dispatchDidContinue(ScriptDebugListener* listener)
192 {
193     listener->didContinue();
194 }
195
196 void ScriptDebugServer::dispatchDidParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, bool isContentScript)
197 {
198     JSC::SourceID sourceID = sourceProvider->asID();
199
200     ScriptDebugListener::Script script;
201     script.url = sourceProvider->url();
202     script.source = sourceProvider->source();
203     script.startLine = sourceProvider->startPosition().m_line.zeroBasedInt();
204     script.startColumn = sourceProvider->startPosition().m_column.zeroBasedInt();
205     script.isContentScript = isContentScript;
206     script.sourceURL = sourceProvider->sourceURL();
207     script.sourceMappingURL = sourceProvider->sourceMappingURL();
208
209     int sourceLength = script.source.length();
210     int lineCount = 1;
211     int lastLineStart = 0;
212     for (int i = 0; i < sourceLength; ++i) {
213         if (script.source[i] == '\n') {
214             lineCount += 1;
215             lastLineStart = i + 1;
216         }
217     }
218
219     script.endLine = script.startLine + lineCount - 1;
220     if (lineCount == 1)
221         script.endColumn = script.startColumn + sourceLength;
222     else
223         script.endColumn = sourceLength - lastLineStart;
224
225     Vector<ScriptDebugListener*> copy;
226     copyToVector(listeners, copy);
227     for (size_t i = 0; i < copy.size(); ++i)
228         copy[i]->didParseSource(sourceID, script);
229 }
230
231 void ScriptDebugServer::dispatchFailedToParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, int errorLine, const String& errorMessage)
232 {
233     String url = sourceProvider->url();
234     const String& data = sourceProvider->source();
235     int firstLine = sourceProvider->startPosition().m_line.oneBasedInt();
236
237     Vector<ScriptDebugListener*> copy;
238     copyToVector(listeners, copy);
239     for (size_t i = 0; i < copy.size(); ++i)
240         copy[i]->failedToParseSource(url, data, firstLine, errorLine, errorMessage);
241 }
242
243 void ScriptDebugServer::sourceParsed(ExecState* exec, SourceProvider* sourceProvider, int errorLine, const String& errorMessage)
244 {
245     if (m_callingListeners)
246         return;
247
248     if (m_listeners.isEmpty())
249         return;
250
251     TemporaryChange<bool> change(m_callingListeners, true);
252
253     bool isError = errorLine != -1;
254     if (isError)
255         dispatchFailedToParseSource(m_listeners, sourceProvider, errorLine, errorMessage);
256     else
257         dispatchDidParseSource(m_listeners, sourceProvider, isContentScript(exec));
258 }
259
260 void ScriptDebugServer::dispatchFunctionToListeners(JavaScriptExecutionCallback callback)
261 {
262     if (m_callingListeners)
263         return;
264
265     if (m_listeners.isEmpty())
266         return;
267
268     TemporaryChange<bool> change(m_callingListeners, true);
269
270     dispatchFunctionToListeners(m_listeners, callback);
271 }
272
273 void ScriptDebugServer::dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptExecutionCallback callback)
274 {
275     Vector<ScriptDebugListener*> copy;
276     copyToVector(listeners, copy);
277     for (size_t i = 0; i < copy.size(); ++i)
278         (this->*callback)(copy[i]);
279 }
280
281 void ScriptDebugServer::notifyDoneProcessingDebuggerEvents()
282 {
283     m_doneProcessingDebuggerEvents = true;
284 }
285
286 void ScriptDebugServer::handleBreakpointHit(JSC::JSGlobalObject* globalObject, const JSC::Breakpoint& breakpoint)
287 {
288     ASSERT(isAttached(globalObject));
289
290     m_currentProbeBatchId++;
291
292     BreakpointIDToActionsMap::iterator it = m_breakpointIDToActions.find(breakpoint.id);
293     if (it != m_breakpointIDToActions.end()) {
294         BreakpointActions actions = it->value;
295         for (size_t i = 0; i < actions.size(); ++i) {
296             if (!evaluateBreakpointAction(actions[i]))
297                 return;
298             if (!isAttached(globalObject))
299                 return;
300         }
301     }
302 }
303
304 void ScriptDebugServer::handleExceptionInBreakpointCondition(JSC::ExecState* exec, JSC::Exception* exception) const
305 {
306     reportException(exec, exception);
307 }
308
309 void ScriptDebugServer::handlePause(JSGlobalObject* vmEntryGlobalObject, Debugger::ReasonForPause)
310 {
311     dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidPause);
312     didPause(vmEntryGlobalObject);
313
314     m_doneProcessingDebuggerEvents = false;
315     runEventLoopWhilePaused();
316
317     didContinue(vmEntryGlobalObject);
318     dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidContinue);
319 }
320
321 const BreakpointActions& ScriptDebugServer::getActionsForBreakpoint(JSC::BreakpointID breakpointID)
322 {
323     ASSERT(breakpointID != JSC::noBreakpointID);
324
325     if (m_breakpointIDToActions.contains(breakpointID))
326         return m_breakpointIDToActions.find(breakpointID)->value;
327     
328     static NeverDestroyed<BreakpointActions> emptyActionVector = BreakpointActions();
329     return emptyActionVector;
330 }
331
332 void ScriptDebugServer::addListener(ScriptDebugListener* listener)
333 {
334     ASSERT(listener);
335
336     bool wasEmpty = m_listeners.isEmpty();
337     m_listeners.add(listener);
338
339     // First listener. Attach the debugger.
340     if (wasEmpty)
341         attachDebugger();
342 }
343
344 void ScriptDebugServer::removeListener(ScriptDebugListener* listener, bool isBeingDestroyed)
345 {
346     ASSERT(listener);
347
348     m_listeners.remove(listener);
349
350     // Last listener. Detach the debugger.
351     if (m_listeners.isEmpty())
352         detachDebugger(isBeingDestroyed);
353 }
354
355 Deprecated::ScriptValue ScriptDebugServer::exceptionOrCaughtValue(JSC::ExecState* state)
356 {
357     if (reasonForPause() == PausedForException)
358         return Deprecated::ScriptValue(state->vm(), currentException());
359
360     RefPtr<DebuggerCallFrame> debuggerCallFrame = currentDebuggerCallFrame();
361     while (debuggerCallFrame) {
362         DebuggerScope* scope = debuggerCallFrame->scope();
363         if (scope->isCatchScope())
364             return Deprecated::ScriptValue(state->vm(), scope->caughtValue(state));
365         debuggerCallFrame = debuggerCallFrame->callerFrame();
366     }
367
368     return Deprecated::ScriptValue();
369 }
370
371 } // namespace Inspector