2 * Copyright (C) 2008, 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2010-2011 Google Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
32 #if ENABLE(JAVASCRIPT_DEBUGGER)
34 #include "ScriptDebugServer.h"
36 #include "EventLoop.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>
53 ScriptDebugServer::ScriptDebugServer()
54 : m_callingListeners(false)
55 , m_pauseOnExceptionsState(DontPauseOnExceptions)
56 , m_pauseOnNextStatement(false)
58 , m_doneProcessingDebuggerEvents(true)
59 , m_breakpointsActivated(true)
60 , m_pauseOnCallFrame(0)
61 , m_recompileTimer(this, &ScriptDebugServer::recompileAllJSFunctions)
65 ScriptDebugServer::~ScriptDebugServer()
67 deleteAllValues(m_pageListenersMap);
70 String ScriptDebugServer::setBreakpoint(const String& sourceID, const ScriptBreakpoint& scriptBreakpoint, int* actualLineNumber, int* actualColumnNumber)
72 intptr_t sourceIDValue = sourceID.toIntPtr();
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))
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);
87 void ScriptDebugServer::removeBreakpoint(const String& breakpointId)
89 Vector<String> tokens;
90 breakpointId.split(":", tokens);
91 if (tokens.size() != 2)
94 intptr_t sourceIDValue = tokens[0].toIntPtr(&success);
97 unsigned lineNumber = tokens[1].toUInt(&success);
100 SourceIdToBreakpointsMap::iterator it = m_sourceIdToBreakpoints.find(sourceIDValue);
101 if (it != m_sourceIdToBreakpoints.end())
102 it->second.remove(lineNumber + 1);
105 bool ScriptDebugServer::hasBreakpoint(intptr_t sourceID, const TextPosition0& position) const
107 if (!m_breakpointsActivated)
110 SourceIdToBreakpointsMap::const_iterator it = m_sourceIdToBreakpoints.find(sourceID);
111 if (it == m_sourceIdToBreakpoints.end())
113 int lineNumber = position.m_line.convertAsOneBasedInt();
116 LineToBreakpointMap::const_iterator breakIt = it->second.find(lineNumber);
117 if (breakIt == it->second.end())
120 // An empty condition counts as no condition which is equivalent to "true".
121 if (breakIt->second.condition.isEmpty())
125 JSValue result = m_currentCallFrame->evaluate(stringToUString(breakIt->second.condition), exception);
127 // An erroneous condition counts as "false".
130 return result.toBoolean(m_currentCallFrame->scopeChain()->globalObject->globalExec());
133 void ScriptDebugServer::clearBreakpoints()
135 m_sourceIdToBreakpoints.clear();
138 void ScriptDebugServer::setBreakpointsActivated(bool activated)
140 m_breakpointsActivated = activated;
143 void ScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pause)
145 m_pauseOnExceptionsState = pause;
148 void ScriptDebugServer::setPauseOnNextStatement(bool pause)
150 m_pauseOnNextStatement = pause;
153 void ScriptDebugServer::breakProgram()
155 // FIXME(WK43332): implement this.
158 void ScriptDebugServer::continueProgram()
163 m_pauseOnNextStatement = false;
164 m_doneProcessingDebuggerEvents = true;
167 void ScriptDebugServer::stepIntoStatement()
172 m_pauseOnNextStatement = true;
173 m_doneProcessingDebuggerEvents = true;
176 void ScriptDebugServer::stepOverStatement()
181 m_pauseOnCallFrame = m_currentCallFrame.get();
182 m_doneProcessingDebuggerEvents = true;
185 void ScriptDebugServer::stepOutOfFunction()
190 m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->caller() : 0;
191 m_doneProcessingDebuggerEvents = true;
194 bool ScriptDebugServer::setScriptSource(const String&, const String&, bool, String*, ScriptValue*, ScriptObject*)
196 // FIXME(40300): implement this.
200 void ScriptDebugServer::dispatchDidPause(ScriptDebugListener* listener)
203 JSGlobalObject* globalObject = m_currentCallFrame->scopeChain()->globalObject.get();
204 ScriptState* state = globalObject->globalExec();
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());
212 jsCallFrame = jsUndefined();
214 listener->didPause(state, ScriptValue(state->globalData(), jsCallFrame), ScriptValue());
217 void ScriptDebugServer::dispatchDidContinue(ScriptDebugListener* listener)
219 listener->didContinue();
222 void ScriptDebugServer::dispatchDidParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, bool isContentScript)
224 String sourceID = ustringToString(JSC::UString::number(sourceProvider->asID()));
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;
233 int sourceLength = script.source.length();
235 int lastLineStart = 0;
236 for (int i = 0; i < sourceLength - 1; ++i) {
237 if (script.source[i] == '\n') {
239 lastLineStart = i + 1;
243 script.endLine = script.startLine + lineCount - 1;
245 script.endColumn = script.startColumn + sourceLength;
247 script.endColumn = sourceLength - lastLineStart;
249 Vector<ScriptDebugListener*> copy;
250 copyToVector(listeners, copy);
251 for (size_t i = 0; i < copy.size(); ++i)
252 copy[i]->didParseSource(sourceID, script);
255 void ScriptDebugServer::dispatchFailedToParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, int errorLine, const String& errorMessage)
257 String url = ustringToString(sourceProvider->url());
258 String data = ustringToString(JSC::UString(sourceProvider->data(), sourceProvider->length()));
259 int firstLine = sourceProvider->startPosition().m_line.oneBasedInt();
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);
267 static bool isContentScript(ExecState* exec)
269 return currentWorld(exec) != mainThreadNormalWorld();
272 void ScriptDebugServer::detach(JSGlobalObject* globalObject)
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;
282 Debugger::detach(globalObject);
285 void ScriptDebugServer::sourceParsed(ExecState* exec, SourceProvider* sourceProvider, int errorLine, const UString& errorMessage)
287 if (m_callingListeners)
290 ListenerSet* listeners = getListenersForGlobalObject(exec->lexicalGlobalObject());
293 ASSERT(!listeners->isEmpty());
295 m_callingListeners = true;
297 bool isError = errorLine != -1;
299 dispatchFailedToParseSource(*listeners, sourceProvider, errorLine, ustringToString(errorMessage));
301 dispatchDidParseSource(*listeners, sourceProvider, isContentScript(exec));
303 m_callingListeners = false;
306 void ScriptDebugServer::dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptExecutionCallback callback)
308 Vector<ScriptDebugListener*> copy;
309 copyToVector(listeners, copy);
310 for (size_t i = 0; i < copy.size(); ++i)
311 (this->*callback)(copy[i]);
314 void ScriptDebugServer::dispatchFunctionToListeners(JavaScriptExecutionCallback callback, JSGlobalObject* globalObject)
316 if (m_callingListeners)
319 m_callingListeners = true;
321 if (ListenerSet* listeners = getListenersForGlobalObject(globalObject)) {
322 ASSERT(!listeners->isEmpty());
323 dispatchFunctionToListeners(*listeners, callback);
326 m_callingListeners = false;
329 void ScriptDebugServer::createCallFrameAndPauseIfNeeded(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
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());
336 void ScriptDebugServer::updateCallFrameAndPauseIfNeeded(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
338 ASSERT(m_currentCallFrame);
339 if (!m_currentCallFrame)
342 TextPosition0 textPosition(WTF::OneBasedNumber::fromOneBasedInt(lineNumber).convertToZeroBased(), WTF::ZeroBasedNumber::base());
343 m_currentCallFrame->update(debuggerCallFrame, sourceID, textPosition);
344 pauseIfNeeded(debuggerCallFrame.dynamicGlobalObject());
347 void ScriptDebugServer::pauseIfNeeded(JSGlobalObject* dynamicGlobalObject)
352 if (!getListenersForGlobalObject(dynamicGlobalObject))
355 bool pauseNow = m_pauseOnNextStatement;
356 pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
357 pauseNow |= hasBreakpoint(m_currentCallFrame->sourceID(), m_currentCallFrame->position());
361 m_pauseOnCallFrame = 0;
362 m_pauseOnNextStatement = false;
365 dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidPause, dynamicGlobalObject);
366 didPause(dynamicGlobalObject);
368 TimerBase::fireTimersInNestedEventLoop();
371 m_doneProcessingDebuggerEvents = false;
372 while (!m_doneProcessingDebuggerEvents && !loop.ended())
375 didContinue(dynamicGlobalObject);
376 dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidContinue, dynamicGlobalObject);
381 void ScriptDebugServer::callEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
384 createCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
387 void ScriptDebugServer::atStatement(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
390 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
393 void ScriptDebugServer::returnEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
398 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
400 // detach may have been called during pauseIfNeeded
401 if (!m_currentCallFrame)
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();
410 void ScriptDebugServer::exception(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber, bool hasHandler)
415 if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasHandler))
416 m_pauseOnNextStatement = true;
418 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
421 void ScriptDebugServer::willExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
424 createCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
427 void ScriptDebugServer::didExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
432 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
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();
440 void ScriptDebugServer::didReachBreakpoint(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
445 m_pauseOnNextStatement = true;
446 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
449 void ScriptDebugServer::recompileAllJSFunctionsSoon()
451 m_recompileTimer.startOneShot(0);
454 } // namespace WebCore
456 #endif // ENABLE(JAVASCRIPT_DEBUGGER)