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