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