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 "ContentSearchUtils.h"
37 #include "EventLoop.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>
54 ScriptDebugServer::ScriptDebugServer()
55 : m_callingListeners(false)
56 , m_pauseOnExceptionsState(DontPauseOnExceptions)
57 , m_pauseOnNextStatement(false)
59 , m_doneProcessingDebuggerEvents(true)
60 , m_breakpointsActivated(true)
61 , m_pauseOnCallFrame(0)
62 , m_recompileTimer(this, &ScriptDebugServer::recompileAllJSFunctions)
66 ScriptDebugServer::~ScriptDebugServer()
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()).iterator;
78 if (it->second.contains(scriptBreakpoint.lineNumber + 1))
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);
88 void ScriptDebugServer::removeBreakpoint(const String& breakpointId)
90 Vector<String> tokens;
91 breakpointId.split(":", tokens);
92 if (tokens.size() != 2)
95 intptr_t sourceIDValue = tokens[0].toIntPtr(&success);
98 unsigned lineNumber = tokens[1].toUInt(&success);
101 SourceIdToBreakpointsMap::iterator it = m_sourceIdToBreakpoints.find(sourceIDValue);
102 if (it != m_sourceIdToBreakpoints.end())
103 it->second.remove(lineNumber + 1);
106 bool ScriptDebugServer::hasBreakpoint(intptr_t sourceID, const TextPosition& position) const
108 if (!m_breakpointsActivated)
111 SourceIdToBreakpointsMap::const_iterator it = m_sourceIdToBreakpoints.find(sourceID);
112 if (it == m_sourceIdToBreakpoints.end())
114 int lineNumber = position.m_line.oneBasedInt();
117 LineToBreakpointMap::const_iterator breakIt = it->second.find(lineNumber);
118 if (breakIt == it->second.end())
121 // An empty condition counts as no condition which is equivalent to "true".
122 if (breakIt->second.condition.isEmpty())
126 JSValue result = m_currentCallFrame->evaluate(stringToUString(breakIt->second.condition), exception);
128 // An erroneous condition counts as "false".
131 return result.toBoolean();
134 void ScriptDebugServer::clearBreakpoints()
136 m_sourceIdToBreakpoints.clear();
139 void ScriptDebugServer::setBreakpointsActivated(bool activated)
141 m_breakpointsActivated = activated;
144 void ScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pause)
146 m_pauseOnExceptionsState = pause;
149 void ScriptDebugServer::setPauseOnNextStatement(bool pause)
151 m_pauseOnNextStatement = pause;
154 void ScriptDebugServer::breakProgram()
156 // FIXME(WK43332): implement this.
159 void ScriptDebugServer::continueProgram()
164 m_pauseOnNextStatement = false;
165 m_doneProcessingDebuggerEvents = true;
168 void ScriptDebugServer::stepIntoStatement()
173 m_pauseOnNextStatement = true;
174 m_doneProcessingDebuggerEvents = true;
177 void ScriptDebugServer::stepOverStatement()
182 m_pauseOnCallFrame = m_currentCallFrame.get();
183 m_doneProcessingDebuggerEvents = true;
186 void ScriptDebugServer::stepOutOfFunction()
191 m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->caller() : 0;
192 m_doneProcessingDebuggerEvents = true;
195 bool ScriptDebugServer::canSetScriptSource()
200 bool ScriptDebugServer::setScriptSource(const String&, const String&, bool, String*, ScriptValue*, ScriptObject*)
202 // FIXME(40300): implement this.
206 void ScriptDebugServer::dispatchDidPause(ScriptDebugListener* listener)
209 JSGlobalObject* globalObject = m_currentCallFrame->scopeChain()->globalObject.get();
210 ScriptState* state = globalObject->globalExec();
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());
218 jsCallFrame = jsUndefined();
220 listener->didPause(state, ScriptValue(state->globalData(), jsCallFrame), ScriptValue());
223 void ScriptDebugServer::dispatchDidContinue(ScriptDebugListener* listener)
225 listener->didContinue();
228 void ScriptDebugServer::dispatchDidParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, bool isContentScript)
230 String sourceID = ustringToString(JSC::UString::number(sourceProvider->asID()));
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;
239 #if ENABLE(INSPECTOR)
240 if (script.url.isEmpty())
241 script.url = ContentSearchUtils::findSourceURL(script.source);
244 int sourceLength = script.source.length();
246 int lastLineStart = 0;
247 for (int i = 0; i < sourceLength; ++i) {
248 if (script.source[i] == '\n') {
250 lastLineStart = i + 1;
254 script.endLine = script.startLine + lineCount - 1;
256 script.endColumn = script.startColumn + sourceLength;
258 script.endColumn = sourceLength - lastLineStart;
260 Vector<ScriptDebugListener*> copy;
261 copyToVector(listeners, copy);
262 for (size_t i = 0; i < copy.size(); ++i)
263 copy[i]->didParseSource(sourceID, script);
266 void ScriptDebugServer::dispatchFailedToParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, int errorLine, const String& errorMessage)
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();
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);
278 static bool isContentScript(ExecState* exec)
280 return currentWorld(exec) != mainThreadNormalWorld();
283 void ScriptDebugServer::detach(JSGlobalObject* globalObject)
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;
293 Debugger::detach(globalObject);
296 void ScriptDebugServer::sourceParsed(ExecState* exec, SourceProvider* sourceProvider, int errorLine, const UString& errorMessage)
298 if (m_callingListeners)
301 ListenerSet* listeners = getListenersForGlobalObject(exec->lexicalGlobalObject());
304 ASSERT(!listeners->isEmpty());
306 m_callingListeners = true;
308 bool isError = errorLine != -1;
310 dispatchFailedToParseSource(*listeners, sourceProvider, errorLine, ustringToString(errorMessage));
312 dispatchDidParseSource(*listeners, sourceProvider, isContentScript(exec));
314 m_callingListeners = false;
317 void ScriptDebugServer::dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptExecutionCallback callback)
319 Vector<ScriptDebugListener*> copy;
320 copyToVector(listeners, copy);
321 for (size_t i = 0; i < copy.size(); ++i)
322 (this->*callback)(copy[i]);
325 void ScriptDebugServer::dispatchFunctionToListeners(JavaScriptExecutionCallback callback, JSGlobalObject* globalObject)
327 if (m_callingListeners)
330 m_callingListeners = true;
332 if (ListenerSet* listeners = getListenersForGlobalObject(globalObject)) {
333 ASSERT(!listeners->isEmpty());
334 dispatchFunctionToListeners(*listeners, callback);
337 m_callingListeners = false;
340 void ScriptDebugServer::createCallFrameAndPauseIfNeeded(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
342 TextPosition textPosition(OrdinalNumber::fromOneBasedInt(lineNumber), OrdinalNumber::first());
343 m_currentCallFrame = JavaScriptCallFrame::create(debuggerCallFrame, m_currentCallFrame, sourceID, textPosition);
344 pauseIfNeeded(debuggerCallFrame.dynamicGlobalObject());
347 void ScriptDebugServer::updateCallFrameAndPauseIfNeeded(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
349 ASSERT(m_currentCallFrame);
350 if (!m_currentCallFrame)
353 TextPosition textPosition(OrdinalNumber::fromOneBasedInt(lineNumber), OrdinalNumber::first());
354 m_currentCallFrame->update(debuggerCallFrame, sourceID, textPosition);
355 pauseIfNeeded(debuggerCallFrame.dynamicGlobalObject());
358 void ScriptDebugServer::pauseIfNeeded(JSGlobalObject* dynamicGlobalObject)
363 if (!getListenersForGlobalObject(dynamicGlobalObject))
366 bool pauseNow = m_pauseOnNextStatement;
367 pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
368 pauseNow |= hasBreakpoint(m_currentCallFrame->sourceID(), m_currentCallFrame->position());
372 m_pauseOnCallFrame = 0;
373 m_pauseOnNextStatement = false;
376 dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidPause, dynamicGlobalObject);
377 didPause(dynamicGlobalObject);
379 TimerBase::fireTimersInNestedEventLoop();
382 m_doneProcessingDebuggerEvents = false;
383 while (!m_doneProcessingDebuggerEvents && !loop.ended())
386 didContinue(dynamicGlobalObject);
387 dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidContinue, dynamicGlobalObject);
392 void ScriptDebugServer::callEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
395 createCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
398 void ScriptDebugServer::atStatement(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
401 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
404 void ScriptDebugServer::returnEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
409 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
411 // detach may have been called during pauseIfNeeded
412 if (!m_currentCallFrame)
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();
421 void ScriptDebugServer::exception(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber, bool hasHandler)
426 if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasHandler))
427 m_pauseOnNextStatement = true;
429 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
432 void ScriptDebugServer::willExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
435 createCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
438 void ScriptDebugServer::didExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
443 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
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();
451 void ScriptDebugServer::didReachBreakpoint(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
456 m_pauseOnNextStatement = true;
457 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
460 void ScriptDebugServer::recompileAllJSFunctionsSoon()
462 m_recompileTimer.startOneShot(0);
465 } // namespace WebCore
467 #endif // ENABLE(JAVASCRIPT_DEBUGGER)