Web Inspector: Cleanup JavaScriptCore/inspector
[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 Computer, 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 #if ENABLE(INSPECTOR)
35
36 #include "DebuggerCallFrame.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 #include <wtf/text/WTFString.h>
45
46 using namespace JSC;
47
48 namespace Inspector {
49
50 ScriptDebugServer::ScriptDebugServer(bool isInWorkerThread)
51     : Debugger(isInWorkerThread)
52     , m_doneProcessingDebuggerEvents(true)
53     , m_callingListeners(false)
54 {
55 }
56
57 ScriptDebugServer::~ScriptDebugServer()
58 {
59 }
60
61 JSC::BreakpointID ScriptDebugServer::setBreakpoint(JSC::SourceID sourceID, const ScriptBreakpoint& scriptBreakpoint, unsigned* actualLineNumber, unsigned* actualColumnNumber)
62 {
63     if (!sourceID)
64         return JSC::noBreakpointID;
65
66     JSC::Breakpoint breakpoint(sourceID, scriptBreakpoint.lineNumber, scriptBreakpoint.columnNumber, scriptBreakpoint.condition, scriptBreakpoint.autoContinue);
67     JSC::BreakpointID id = Debugger::setBreakpoint(breakpoint, *actualLineNumber, *actualColumnNumber);
68     if (id != JSC::noBreakpointID && !scriptBreakpoint.actions.isEmpty()) {
69 #ifndef NDEBUG
70         BreakpointIDToActionsMap::iterator it = m_breakpointIDToActions.find(id);
71         ASSERT(it == m_breakpointIDToActions.end());
72 #endif
73         const Vector<ScriptBreakpointAction> &actions = scriptBreakpoint.actions;
74         m_breakpointIDToActions.set(id, actions);
75     }
76     return id;
77 }
78
79 void ScriptDebugServer::removeBreakpoint(JSC::BreakpointID id)
80 {
81     ASSERT(id != JSC::noBreakpointID);
82     BreakpointIDToActionsMap::iterator it = m_breakpointIDToActions.find(id);
83     if (it != m_breakpointIDToActions.end())
84         m_breakpointIDToActions.remove(it);
85
86     Debugger::removeBreakpoint(id);
87 }
88
89 bool ScriptDebugServer::evaluateBreakpointAction(const ScriptBreakpointAction& breakpointAction)
90 {
91     DebuggerCallFrame* debuggerCallFrame = currentDebuggerCallFrame();
92
93     switch (breakpointAction.type) {
94     case ScriptBreakpointActionTypeLog: {
95         dispatchBreakpointActionLog(debuggerCallFrame->exec(), breakpointAction.data);
96         break;
97     }
98     case ScriptBreakpointActionTypeEvaluate: {
99         JSValue exception;
100         debuggerCallFrame->evaluate(breakpointAction.data, exception);
101         if (exception)
102             reportException(debuggerCallFrame->exec(), exception);
103         break;
104     }
105     case ScriptBreakpointActionTypeSound:
106         dispatchBreakpointActionSound(debuggerCallFrame->exec(), breakpointAction.identifier);
107         break;
108     case ScriptBreakpointActionTypeProbe: {
109         JSValue exception;
110         JSValue result = debuggerCallFrame->evaluate(breakpointAction.data, exception);
111         if (exception)
112             reportException(debuggerCallFrame->exec(), exception);
113         
114         JSC::ExecState* state = debuggerCallFrame->scope()->globalObject()->globalExec();
115         Deprecated::ScriptValue wrappedResult = Deprecated::ScriptValue(state->vm(), exception ? exception : result);
116         dispatchBreakpointActionProbe(state, breakpointAction, wrappedResult);
117         break;
118     }
119     default:
120         ASSERT_NOT_REACHED();
121     }
122
123     return true;
124 }
125
126 void ScriptDebugServer::clearBreakpoints()
127 {
128     Debugger::clearBreakpoints();
129     m_breakpointIDToActions.clear();
130 }
131
132 void ScriptDebugServer::dispatchDidPause(ScriptDebugListener* listener)
133 {
134     ASSERT(isPaused());
135     DebuggerCallFrame* debuggerCallFrame = currentDebuggerCallFrame();
136     JSGlobalObject* globalObject = debuggerCallFrame->scope()->globalObject();
137     JSC::ExecState* state = globalObject->globalExec();
138     RefPtr<JavaScriptCallFrame> javaScriptCallFrame = JavaScriptCallFrame::create(debuggerCallFrame);
139     JSValue jsCallFrame = toJS(state, globalObject, javaScriptCallFrame.get());
140     listener->didPause(state, Deprecated::ScriptValue(state->vm(), jsCallFrame), Deprecated::ScriptValue());
141 }
142
143 void ScriptDebugServer::dispatchBreakpointActionLog(ExecState* exec, const String& message)
144 {
145     if (m_callingListeners)
146         return;
147
148     ListenerSet* listeners = getListenersForGlobalObject(exec->lexicalGlobalObject());
149     if (!listeners)
150         return;
151     ASSERT(!listeners->isEmpty());
152
153     TemporaryChange<bool> change(m_callingListeners, true);
154
155     Vector<ScriptDebugListener*> listenersCopy;
156     copyToVector(*listeners, listenersCopy);
157     for (auto listener : listenersCopy)
158         listener->breakpointActionLog(exec, message);
159 }
160
161 void ScriptDebugServer::dispatchBreakpointActionSound(ExecState* exec, int breakpointActionIdentifier)
162 {
163     if (m_callingListeners)
164         return;
165
166     ListenerSet* listeners = getListenersForGlobalObject(exec->lexicalGlobalObject());
167     if (!listeners)
168         return;
169     ASSERT(!listeners->isEmpty());
170
171     TemporaryChange<bool> change(m_callingListeners, true);
172
173     Vector<ScriptDebugListener*> listenersCopy;
174     copyToVector(*listeners, listenersCopy);
175     for (auto listener : listenersCopy)
176         listener->breakpointActionSound(breakpointActionIdentifier);
177 }
178
179 void ScriptDebugServer::dispatchBreakpointActionProbe(ExecState* exec, const ScriptBreakpointAction& action, const Deprecated::ScriptValue& sample)
180 {
181     if (m_callingListeners)
182         return;
183
184     ListenerSet* listeners = getListenersForGlobalObject(exec->lexicalGlobalObject());
185     if (!listeners)
186         return;
187     ASSERT(!listeners->isEmpty());
188
189     TemporaryChange<bool> change(m_callingListeners, true);
190
191     Vector<ScriptDebugListener*> listenersCopy;
192     copyToVector(*listeners, listenersCopy);
193     for (auto listener : listenersCopy)
194         listener->breakpointActionProbe(exec, action, m_hitCount, sample);
195 }
196
197 void ScriptDebugServer::dispatchDidContinue(ScriptDebugListener* listener)
198 {
199     listener->didContinue();
200 }
201
202 void ScriptDebugServer::dispatchDidParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, bool isContentScript)
203 {
204     JSC::SourceID sourceID = sourceProvider->asID();
205
206     ScriptDebugListener::Script script;
207     script.url = sourceProvider->url();
208     script.source = sourceProvider->source();
209     script.startLine = sourceProvider->startPosition().m_line.zeroBasedInt();
210     script.startColumn = sourceProvider->startPosition().m_column.zeroBasedInt();
211     script.isContentScript = isContentScript;
212
213     int sourceLength = script.source.length();
214     int lineCount = 1;
215     int lastLineStart = 0;
216     for (int i = 0; i < sourceLength; ++i) {
217         if (script.source[i] == '\n') {
218             lineCount += 1;
219             lastLineStart = i + 1;
220         }
221     }
222
223     script.endLine = script.startLine + lineCount - 1;
224     if (lineCount == 1)
225         script.endColumn = script.startColumn + sourceLength;
226     else
227         script.endColumn = sourceLength - lastLineStart;
228
229     Vector<ScriptDebugListener*> copy;
230     copyToVector(listeners, copy);
231     for (size_t i = 0; i < copy.size(); ++i)
232         copy[i]->didParseSource(sourceID, script);
233 }
234
235 void ScriptDebugServer::dispatchFailedToParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, int errorLine, const String& errorMessage)
236 {
237     String url = sourceProvider->url();
238     const String& data = sourceProvider->source();
239     int firstLine = sourceProvider->startPosition().m_line.oneBasedInt();
240
241     Vector<ScriptDebugListener*> copy;
242     copyToVector(listeners, copy);
243     for (size_t i = 0; i < copy.size(); ++i)
244         copy[i]->failedToParseSource(url, data, firstLine, errorLine, errorMessage);
245 }
246
247 void ScriptDebugServer::sourceParsed(ExecState* exec, SourceProvider* sourceProvider, int errorLine, const String& errorMessage)
248 {
249     if (m_callingListeners)
250         return;
251
252     ListenerSet* listeners = getListenersForGlobalObject(exec->lexicalGlobalObject());
253     if (!listeners)
254         return;
255     ASSERT(!listeners->isEmpty());
256
257     TemporaryChange<bool> change(m_callingListeners, true);
258
259     bool isError = errorLine != -1;
260     if (isError)
261         dispatchFailedToParseSource(*listeners, sourceProvider, errorLine, errorMessage);
262     else
263         dispatchDidParseSource(*listeners, sourceProvider, isContentScript(exec));
264 }
265
266 void ScriptDebugServer::dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptExecutionCallback callback)
267 {
268     Vector<ScriptDebugListener*> copy;
269     copyToVector(listeners, copy);
270     for (size_t i = 0; i < copy.size(); ++i)
271         (this->*callback)(copy[i]);
272 }
273
274 void ScriptDebugServer::dispatchFunctionToListeners(JavaScriptExecutionCallback callback, JSGlobalObject* globalObject)
275 {
276     if (m_callingListeners)
277         return;
278
279     TemporaryChange<bool> change(m_callingListeners, true);
280
281     if (ListenerSet* listeners = getListenersForGlobalObject(globalObject)) {
282         ASSERT(!listeners->isEmpty());
283         dispatchFunctionToListeners(*listeners, callback);
284     }
285 }
286
287 void ScriptDebugServer::notifyDoneProcessingDebuggerEvents()
288 {
289     m_doneProcessingDebuggerEvents = true;
290 }
291
292 bool ScriptDebugServer::needPauseHandling(JSGlobalObject* globalObject)
293 {
294     return !!getListenersForGlobalObject(globalObject);
295 }
296
297 void ScriptDebugServer::handleBreakpointHit(const JSC::Breakpoint& breakpoint)
298 {
299     m_hitCount++;
300     BreakpointIDToActionsMap::iterator it = m_breakpointIDToActions.find(breakpoint.id);
301     if (it != m_breakpointIDToActions.end()) {
302         BreakpointActions& actions = it->value;
303         for (size_t i = 0; i < actions.size(); ++i) {
304             if (!evaluateBreakpointAction(actions[i]))
305                 return;
306         }
307     }
308 }
309
310 void ScriptDebugServer::handleExceptionInBreakpointCondition(JSC::ExecState* exec, JSC::JSValue exception) const
311 {
312     reportException(exec, exception);
313 }
314
315 void ScriptDebugServer::handlePause(Debugger::ReasonForPause, JSGlobalObject* vmEntryGlobalObject)
316 {
317     dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidPause, vmEntryGlobalObject);
318     didPause(vmEntryGlobalObject);
319
320     m_doneProcessingDebuggerEvents = false;
321     runEventLoopWhilePaused();
322
323     didContinue(vmEntryGlobalObject);
324     dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidContinue, vmEntryGlobalObject);
325 }
326
327 const Vector<ScriptBreakpointAction>& ScriptDebugServer::getActionsForBreakpoint(JSC::BreakpointID breakpointID)
328 {
329     ASSERT(breakpointID != JSC::noBreakpointID);
330
331     if (m_breakpointIDToActions.contains(breakpointID))
332         return m_breakpointIDToActions.find(breakpointID)->value;
333     
334     static NeverDestroyed<Vector<ScriptBreakpointAction>> emptyActionVector = Vector<ScriptBreakpointAction>();
335     return emptyActionVector;
336 }
337
338 } // namespace Inspector
339
340 #endif // ENABLE(INSPECTOR)