initial import
[vuplus_webkit] / Source / WebCore / bindings / js / ScriptDebugServer.cpp
1 /*
2  * Copyright (C) 2008, 2009 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 Computer, 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
32 #if ENABLE(JAVASCRIPT_DEBUGGER)
33
34 #include "ScriptDebugServer.h"
35
36 #include "EventLoop.h"
37 #include "Frame.h"
38 #include "JSJavaScriptCallFrame.h"
39 #include "JavaScriptCallFrame.h"
40 #include "ScriptBreakpoint.h"
41 #include "ScriptDebugListener.h"
42 #include "ScriptValue.h"
43 #include <debugger/DebuggerCallFrame.h>
44 #include <parser/SourceProvider.h>
45 #include <runtime/JSLock.h>
46 #include <wtf/MainThread.h>
47 #include <wtf/text/WTFString.h>
48
49 using namespace JSC;
50
51 namespace WebCore {
52
53 ScriptDebugServer::ScriptDebugServer()
54     : m_callingListeners(false)
55     , m_pauseOnExceptionsState(DontPauseOnExceptions)
56     , m_pauseOnNextStatement(false)
57     , m_paused(false)
58     , m_doneProcessingDebuggerEvents(true)
59     , m_breakpointsActivated(true)
60     , m_pauseOnCallFrame(0)
61     , m_recompileTimer(this, &ScriptDebugServer::recompileAllJSFunctions)
62 {
63 }
64
65 ScriptDebugServer::~ScriptDebugServer()
66 {
67     deleteAllValues(m_pageListenersMap);
68 }
69
70 String ScriptDebugServer::setBreakpoint(const String& sourceID, const ScriptBreakpoint& scriptBreakpoint, int* actualLineNumber, int* actualColumnNumber)
71 {
72     intptr_t sourceIDValue = sourceID.toIntPtr();
73     if (!sourceIDValue)
74         return "";
75     SourceIdToBreakpointsMap::iterator it = m_sourceIdToBreakpoints.find(sourceIDValue);
76     if (it == m_sourceIdToBreakpoints.end())
77         it = m_sourceIdToBreakpoints.set(sourceIDValue, LineToBreakpointMap()).first;
78     if (it->second.contains(scriptBreakpoint.lineNumber + 1))
79         return "";
80     it->second.set(scriptBreakpoint.lineNumber + 1, scriptBreakpoint);
81     *actualLineNumber = scriptBreakpoint.lineNumber;
82     // FIXME(WK53003): implement setting breakpoints by line:column.
83     *actualColumnNumber = 0;
84     return sourceID + ":" + String::number(scriptBreakpoint.lineNumber);
85 }
86
87 void ScriptDebugServer::removeBreakpoint(const String& breakpointId)
88 {
89     Vector<String> tokens;
90     breakpointId.split(":", tokens);
91     if (tokens.size() != 2)
92         return;
93     bool success;
94     intptr_t sourceIDValue = tokens[0].toIntPtr(&success);
95     if (!success)
96         return;
97     unsigned lineNumber = tokens[1].toUInt(&success);
98     if (!success)
99         return;
100     SourceIdToBreakpointsMap::iterator it = m_sourceIdToBreakpoints.find(sourceIDValue);
101     if (it != m_sourceIdToBreakpoints.end())
102         it->second.remove(lineNumber + 1);
103 }
104
105 bool ScriptDebugServer::hasBreakpoint(intptr_t sourceID, const TextPosition0& position) const
106 {
107     if (!m_breakpointsActivated)
108         return false;
109
110     SourceIdToBreakpointsMap::const_iterator it = m_sourceIdToBreakpoints.find(sourceID);
111     if (it == m_sourceIdToBreakpoints.end())
112         return false;
113     int lineNumber = position.m_line.convertAsOneBasedInt();
114     if (lineNumber <= 0)
115         return false;
116     LineToBreakpointMap::const_iterator breakIt = it->second.find(lineNumber);
117     if (breakIt == it->second.end())
118         return false;
119
120     // An empty condition counts as no condition which is equivalent to "true".
121     if (breakIt->second.condition.isEmpty())
122         return true;
123
124     JSValue exception;
125     JSValue result = m_currentCallFrame->evaluate(stringToUString(breakIt->second.condition), exception);
126     if (exception) {
127         // An erroneous condition counts as "false".
128         return false;
129     }
130     return result.toBoolean(m_currentCallFrame->scopeChain()->globalObject->globalExec());
131 }
132
133 void ScriptDebugServer::clearBreakpoints()
134 {
135     m_sourceIdToBreakpoints.clear();
136 }
137
138 void ScriptDebugServer::setBreakpointsActivated(bool activated)
139 {
140     m_breakpointsActivated = activated;
141 }
142
143 void ScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pause)
144 {
145     m_pauseOnExceptionsState = pause;
146 }
147
148 void ScriptDebugServer::setPauseOnNextStatement(bool pause)
149 {
150     m_pauseOnNextStatement = pause;
151 }
152
153 void ScriptDebugServer::breakProgram()
154 {
155     // FIXME(WK43332): implement this.
156 }
157
158 void ScriptDebugServer::continueProgram()
159 {
160     if (!m_paused)
161         return;
162
163     m_pauseOnNextStatement = false;
164     m_doneProcessingDebuggerEvents = true;
165 }
166
167 void ScriptDebugServer::stepIntoStatement()
168 {
169     if (!m_paused)
170         return;
171
172     m_pauseOnNextStatement = true;
173     m_doneProcessingDebuggerEvents = true;
174 }
175
176 void ScriptDebugServer::stepOverStatement()
177 {
178     if (!m_paused)
179         return;
180
181     m_pauseOnCallFrame = m_currentCallFrame.get();
182     m_doneProcessingDebuggerEvents = true;
183 }
184
185 void ScriptDebugServer::stepOutOfFunction()
186 {
187     if (!m_paused)
188         return;
189
190     m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->caller() : 0;
191     m_doneProcessingDebuggerEvents = true;
192 }
193
194 bool ScriptDebugServer::setScriptSource(const String&, const String&, bool, String*, ScriptValue*, ScriptObject*)
195 {
196     // FIXME(40300): implement this.
197     return false;
198 }
199
200 void ScriptDebugServer::dispatchDidPause(ScriptDebugListener* listener)
201 {
202     ASSERT(m_paused);
203     JSGlobalObject* globalObject = m_currentCallFrame->scopeChain()->globalObject.get();
204     ScriptState* state = globalObject->globalExec();
205     JSValue jsCallFrame;
206     {
207         if (m_currentCallFrame->isValid() && globalObject->inherits(&JSDOMGlobalObject::s_info)) {
208             JSDOMGlobalObject* domGlobalObject = static_cast<JSDOMGlobalObject*>(globalObject);
209             JSLock lock(SilenceAssertionsOnly);
210             jsCallFrame = toJS(state, domGlobalObject, m_currentCallFrame.get());
211         } else
212             jsCallFrame = jsUndefined();
213     }
214     listener->didPause(state, ScriptValue(state->globalData(), jsCallFrame), ScriptValue());
215 }
216
217 void ScriptDebugServer::dispatchDidContinue(ScriptDebugListener* listener)
218 {
219     listener->didContinue();
220 }
221
222 void ScriptDebugServer::dispatchDidParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, bool isContentScript)
223 {
224     String sourceID = ustringToString(JSC::UString::number(sourceProvider->asID()));
225
226     ScriptDebugListener::Script script;
227     script.url = ustringToString(sourceProvider->url());
228     script.source = ustringToString(JSC::UString(sourceProvider->data(), sourceProvider->length()));
229     script.startLine = sourceProvider->startPosition().m_line.convertAsZeroBasedInt();
230     script.startColumn = sourceProvider->startPosition().m_column.convertAsZeroBasedInt();
231     script.isContentScript = isContentScript;
232
233     int sourceLength = script.source.length();
234     int lineCount = 1;
235     int lastLineStart = 0;
236     for (int i = 0; i < sourceLength - 1; ++i) {
237         if (script.source[i] == '\n') {
238             lineCount += 1;
239             lastLineStart = i + 1;
240         }
241     }
242
243     script.endLine = script.startLine + lineCount - 1;
244     if (lineCount == 1)
245         script.endColumn = script.startColumn + sourceLength;
246     else
247         script.endColumn = sourceLength - lastLineStart;
248
249     Vector<ScriptDebugListener*> copy;
250     copyToVector(listeners, copy);
251     for (size_t i = 0; i < copy.size(); ++i)
252         copy[i]->didParseSource(sourceID, script);
253 }
254
255 void ScriptDebugServer::dispatchFailedToParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, int errorLine, const String& errorMessage)
256 {
257     String url = ustringToString(sourceProvider->url());
258     String data = ustringToString(JSC::UString(sourceProvider->data(), sourceProvider->length()));
259     int firstLine = sourceProvider->startPosition().m_line.oneBasedInt();
260
261     Vector<ScriptDebugListener*> copy;
262     copyToVector(listeners, copy);
263     for (size_t i = 0; i < copy.size(); ++i)
264         copy[i]->failedToParseSource(url, data, firstLine, errorLine, errorMessage);
265 }
266
267 static bool isContentScript(ExecState* exec)
268 {
269     return currentWorld(exec) != mainThreadNormalWorld();
270 }
271
272 void ScriptDebugServer::detach(JSGlobalObject* globalObject)
273 {
274     // If we're detaching from the currently executing global object, manually tear down our
275     // stack, since we won't get further debugger callbacks to do so. Also, resume execution,
276     // since there's no point in staying paused once a window closes.
277     if (m_currentCallFrame && m_currentCallFrame->dynamicGlobalObject() == globalObject) {
278         m_currentCallFrame = 0;
279         m_pauseOnCallFrame = 0;
280         continueProgram();
281     }
282     Debugger::detach(globalObject);
283 }
284
285 void ScriptDebugServer::sourceParsed(ExecState* exec, SourceProvider* sourceProvider, int errorLine, const UString& errorMessage)
286 {
287     if (m_callingListeners)
288         return;
289
290     ListenerSet* listeners = getListenersForGlobalObject(exec->lexicalGlobalObject());
291     if (!listeners)
292         return;
293     ASSERT(!listeners->isEmpty());
294
295     m_callingListeners = true;
296
297     bool isError = errorLine != -1;
298     if (isError)
299         dispatchFailedToParseSource(*listeners, sourceProvider, errorLine, ustringToString(errorMessage));
300     else
301         dispatchDidParseSource(*listeners, sourceProvider, isContentScript(exec));
302
303     m_callingListeners = false;
304 }
305
306 void ScriptDebugServer::dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptExecutionCallback callback)
307 {
308     Vector<ScriptDebugListener*> copy;
309     copyToVector(listeners, copy);
310     for (size_t i = 0; i < copy.size(); ++i)
311         (this->*callback)(copy[i]);
312 }
313
314 void ScriptDebugServer::dispatchFunctionToListeners(JavaScriptExecutionCallback callback, JSGlobalObject* globalObject)
315 {
316     if (m_callingListeners)
317         return;
318
319     m_callingListeners = true;
320
321     if (ListenerSet* listeners = getListenersForGlobalObject(globalObject)) {
322         ASSERT(!listeners->isEmpty());
323         dispatchFunctionToListeners(*listeners, callback);
324     }
325
326     m_callingListeners = false;
327 }
328
329 void ScriptDebugServer::createCallFrameAndPauseIfNeeded(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
330 {
331     TextPosition0 textPosition(WTF::OneBasedNumber::fromOneBasedInt(lineNumber).convertToZeroBased(), WTF::ZeroBasedNumber::base());
332     m_currentCallFrame = JavaScriptCallFrame::create(debuggerCallFrame, m_currentCallFrame, sourceID, textPosition);
333     pauseIfNeeded(debuggerCallFrame.dynamicGlobalObject());
334 }
335
336 void ScriptDebugServer::updateCallFrameAndPauseIfNeeded(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
337 {
338     ASSERT(m_currentCallFrame);
339     if (!m_currentCallFrame)
340         return;
341
342     TextPosition0 textPosition(WTF::OneBasedNumber::fromOneBasedInt(lineNumber).convertToZeroBased(), WTF::ZeroBasedNumber::base());
343     m_currentCallFrame->update(debuggerCallFrame, sourceID, textPosition);
344     pauseIfNeeded(debuggerCallFrame.dynamicGlobalObject());
345 }
346
347 void ScriptDebugServer::pauseIfNeeded(JSGlobalObject* dynamicGlobalObject)
348 {
349     if (m_paused)
350         return;
351  
352     if (!getListenersForGlobalObject(dynamicGlobalObject))
353         return;
354
355     bool pauseNow = m_pauseOnNextStatement;
356     pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
357     pauseNow |= hasBreakpoint(m_currentCallFrame->sourceID(), m_currentCallFrame->position());
358     if (!pauseNow)
359         return;
360
361     m_pauseOnCallFrame = 0;
362     m_pauseOnNextStatement = false;
363     m_paused = true;
364
365     dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidPause, dynamicGlobalObject);
366     didPause(dynamicGlobalObject);
367
368     TimerBase::fireTimersInNestedEventLoop();
369
370     EventLoop loop;
371     m_doneProcessingDebuggerEvents = false;
372     while (!m_doneProcessingDebuggerEvents && !loop.ended())
373         loop.cycle();
374
375     didContinue(dynamicGlobalObject);
376     dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidContinue, dynamicGlobalObject);
377
378     m_paused = false;
379 }
380
381 void ScriptDebugServer::callEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
382 {
383     if (!m_paused)
384         createCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
385 }
386
387 void ScriptDebugServer::atStatement(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
388 {
389     if (!m_paused)
390         updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
391 }
392
393 void ScriptDebugServer::returnEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
394 {
395     if (m_paused)
396         return;
397
398     updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
399
400     // detach may have been called during pauseIfNeeded
401     if (!m_currentCallFrame)
402         return;
403
404     // Treat stepping over a return statement like stepping out.
405     if (m_currentCallFrame == m_pauseOnCallFrame)
406         m_pauseOnCallFrame = m_currentCallFrame->caller();
407     m_currentCallFrame = m_currentCallFrame->caller();
408 }
409
410 void ScriptDebugServer::exception(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber, bool hasHandler)
411 {
412     if (m_paused)
413         return;
414
415     if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasHandler))
416         m_pauseOnNextStatement = true;
417
418     updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
419 }
420
421 void ScriptDebugServer::willExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
422 {
423     if (!m_paused)
424         createCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
425 }
426
427 void ScriptDebugServer::didExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
428 {
429     if (m_paused)
430         return;
431
432     updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
433
434     // Treat stepping over the end of a program like stepping out.
435     if (m_currentCallFrame == m_pauseOnCallFrame)
436         m_pauseOnCallFrame = m_currentCallFrame->caller();
437     m_currentCallFrame = m_currentCallFrame->caller();
438 }
439
440 void ScriptDebugServer::didReachBreakpoint(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
441 {
442     if (m_paused)
443         return;
444
445     m_pauseOnNextStatement = true;
446     updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
447 }
448
449 void ScriptDebugServer::recompileAllJSFunctionsSoon()
450 {
451     m_recompileTimer.startOneShot(0);
452 }
453
454 } // namespace WebCore
455
456 #endif // ENABLE(JAVASCRIPT_DEBUGGER)