2011-02-24 Ilya Tikhonovsky <loislo@chromium.org>
[WebKit-https.git] / Source / WebCore / inspector / InspectorDebuggerAgent.cpp
1 /*
2  * Copyright (C) 2010 Apple Inc. All rights reserved.
3  * Copyright (C) 2010 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 #include "InspectorDebuggerAgent.h"
32
33 #if ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(INSPECTOR)
34 #include "InjectedScript.h"
35 #include "InjectedScriptHost.h"
36 #include "InspectorFrontend.h"
37 #include "InspectorState.h"
38 #include "InspectorValues.h"
39 #include "PlatformString.h"
40 #include "ScriptDebugServer.h"
41 #include <wtf/text/StringConcatenate.h>
42
43 namespace WebCore {
44
45 namespace DebuggerAgentState {
46 static const char javaScriptBreakpoints[] = "javaScriptBreakopints";
47 };
48
49 PassOwnPtr<InspectorDebuggerAgent> InspectorDebuggerAgent::create(InspectorAgent* inspectorAgent, InspectorFrontend* frontend, bool eraseStickyBreakpoints)
50 {
51     OwnPtr<InspectorDebuggerAgent> agent = adoptPtr(new InspectorDebuggerAgent(inspectorAgent, frontend, eraseStickyBreakpoints));
52     ScriptDebugServer::shared().clearBreakpoints();
53     // FIXME(WK44513): breakpoints activated flag should be synchronized between all front-ends
54     ScriptDebugServer::shared().setBreakpointsActivated(true);
55     ScriptDebugServer::shared().addListener(agent.get(), inspectorAgent->inspectedPage());
56     return agent.release();
57 }
58
59 InspectorDebuggerAgent::InspectorDebuggerAgent(InspectorAgent* inspectorAgent, InspectorFrontend* frontend, bool eraseStickyBreakpoints)
60     : m_inspectorAgent(inspectorAgent)
61     , m_frontend(frontend)
62     , m_pausedScriptState(0)
63     , m_javaScriptPauseScheduled(false)
64 {
65     if (eraseStickyBreakpoints)
66         inspectorAgent->state()->setObject(DebuggerAgentState::javaScriptBreakpoints, InspectorObject::create());
67 }
68
69 InspectorDebuggerAgent::~InspectorDebuggerAgent()
70 {
71     ScriptDebugServer::shared().removeListener(this, m_inspectorAgent->inspectedPage());
72     m_pausedScriptState = 0;
73 }
74
75 void InspectorDebuggerAgent::activateBreakpoints(ErrorString*)
76 {
77     ScriptDebugServer::shared().activateBreakpoints();
78 }
79
80 void InspectorDebuggerAgent::deactivateBreakpoints(ErrorString*)
81 {
82     ScriptDebugServer::shared().deactivateBreakpoints();
83 }
84
85 void InspectorDebuggerAgent::inspectedURLChanged(const String&)
86 {
87     m_scripts.clear();
88     m_breakpointIdToDebugServerBreakpointIds.clear();
89 }
90
91 void InspectorDebuggerAgent::setJavaScriptBreakpoint(ErrorString*, const String& url, int lineNumber, int columnNumber, const String& condition, bool enabled, String* outBreakpointId, RefPtr<InspectorArray>* locations)
92 {
93     String breakpointId = makeString(url, ":", String::number(lineNumber), ":", String::number(columnNumber));
94     RefPtr<InspectorObject> breakpointsCookie = m_inspectorAgent->state()->getObject(DebuggerAgentState::javaScriptBreakpoints);
95     if (breakpointsCookie->find(breakpointId) != breakpointsCookie->end())
96         return;
97     RefPtr<InspectorObject> breakpointObject = InspectorObject::create();
98     breakpointObject->setString("url", url);
99     breakpointObject->setNumber("lineNumber", lineNumber);
100     breakpointObject->setNumber("columnNumber", columnNumber);
101     breakpointObject->setString("condition", condition);
102     breakpointObject->setBoolean("enabled", enabled);
103     breakpointsCookie->setObject(breakpointId, breakpointObject);
104     m_inspectorAgent->state()->setObject(DebuggerAgentState::javaScriptBreakpoints, breakpointsCookie);
105
106     ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition, enabled);
107     for (ScriptsMap::iterator it = m_scripts.begin(); it != m_scripts.end(); ++it) {
108         if (it->second.url != url)
109             continue;
110         int actualLineNumber = 0, actualColumnNumber = 0;
111         if (!resolveBreakpoint(breakpointId, it->first, breakpoint, &actualLineNumber, &actualColumnNumber))
112             continue;
113         RefPtr<InspectorObject> location = InspectorObject::create();
114         location->setString("sourceID", it->first);
115         location->setNumber("lineNumber", actualLineNumber);
116         location->setNumber("columnNumber", actualColumnNumber);
117         locations->get()->pushObject(location);
118     }
119     *outBreakpointId = breakpointId;
120 }
121
122 void InspectorDebuggerAgent::setJavaScriptBreakpointBySourceId(ErrorString*, const String& sourceId, int lineNumber, int columnNumber, const String& condition, bool enabled, String* outBreakpointId, int* actualLineNumber, int* actualColumnNumber)
123 {
124     String breakpointId = makeString(sourceId, ":", String::number(lineNumber), ":", String::number(columnNumber));
125     if (m_breakpointIdToDebugServerBreakpointIds.find(breakpointId) != m_breakpointIdToDebugServerBreakpointIds.end())
126         return;
127     ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition, enabled);
128     if (!resolveBreakpoint(breakpointId, sourceId, breakpoint, actualLineNumber, actualColumnNumber))
129         return;
130     *outBreakpointId = breakpointId;
131 }
132
133 void InspectorDebuggerAgent::removeJavaScriptBreakpoint(ErrorString*, const String& breakpointId)
134 {
135     RefPtr<InspectorObject> breakpointsCookie = m_inspectorAgent->state()->getObject(DebuggerAgentState::javaScriptBreakpoints);
136     breakpointsCookie->remove(breakpointId);
137     m_inspectorAgent->state()->setObject(DebuggerAgentState::javaScriptBreakpoints, breakpointsCookie);
138
139     BreakpointIdToDebugServerBreakpointIdsMap::iterator debugServerBreakpointIdsIterator = m_breakpointIdToDebugServerBreakpointIds.find(breakpointId);
140     if (debugServerBreakpointIdsIterator == m_breakpointIdToDebugServerBreakpointIds.end())
141         return;
142     for (size_t i = 0; i < debugServerBreakpointIdsIterator->second.size(); ++i)
143         ScriptDebugServer::shared().removeBreakpoint(debugServerBreakpointIdsIterator->second[i]);
144     m_breakpointIdToDebugServerBreakpointIds.remove(debugServerBreakpointIdsIterator);
145 }
146
147 void InspectorDebuggerAgent::continueToLocation(ErrorString* error, const String& sourceId, int lineNumber, int columnNumber)
148 {
149     if (!m_continueToLocationBreakpointId.isEmpty()) {
150         ScriptDebugServer::shared().removeBreakpoint(m_continueToLocationBreakpointId);
151         m_continueToLocationBreakpointId = "";
152     }
153     ScriptBreakpoint breakpoint(lineNumber, columnNumber, "", true);
154     m_continueToLocationBreakpointId = ScriptDebugServer::shared().setBreakpoint(sourceId, breakpoint, &lineNumber, &columnNumber);
155     resume(error);
156 }
157
158 bool InspectorDebuggerAgent::resolveBreakpoint(const String& breakpointId, const String& sourceId, const ScriptBreakpoint& breakpoint, int* actualLineNumber, int* actualColumnNumber)
159 {
160     ScriptsMap::iterator scriptIterator = m_scripts.find(sourceId);
161     if (scriptIterator == m_scripts.end())
162         return false;
163     Script& script = scriptIterator->second;
164     if (breakpoint.lineNumber < script.lineOffset)
165         return false;
166     if (!script.linesCount) {
167         script.linesCount = 1;
168         for (size_t i = 0; i < script.data.length(); ++i) {
169             if (script.data[i] == '\n')
170                 script.linesCount += 1;
171         }
172     }
173     if (breakpoint.lineNumber >= script.lineOffset + script.linesCount)
174         return false;
175
176     String debugServerBreakpointId = ScriptDebugServer::shared().setBreakpoint(sourceId, breakpoint, actualLineNumber, actualColumnNumber);
177     if (debugServerBreakpointId.isEmpty())
178         return false;
179
180     BreakpointIdToDebugServerBreakpointIdsMap::iterator debugServerBreakpointIdsIterator = m_breakpointIdToDebugServerBreakpointIds.find(breakpointId);
181     if (debugServerBreakpointIdsIterator == m_breakpointIdToDebugServerBreakpointIds.end())
182         debugServerBreakpointIdsIterator = m_breakpointIdToDebugServerBreakpointIds.set(breakpointId, Vector<String>()).first;
183     debugServerBreakpointIdsIterator->second.append(debugServerBreakpointId);
184
185     return true;
186 }
187
188 void InspectorDebuggerAgent::editScriptSource(ErrorString*, const String& sourceID, const String& newContent, bool* success, String* result, RefPtr<InspectorValue>* newCallFrames)
189 {
190     if ((*success = ScriptDebugServer::shared().editScriptSource(sourceID, newContent, *result)))
191         *newCallFrames = currentCallFrames();
192 }
193
194 void InspectorDebuggerAgent::getScriptSource(ErrorString*, const String& sourceID, String* scriptSource)
195 {
196     *scriptSource = m_scripts.get(sourceID).data;
197 }
198
199 void InspectorDebuggerAgent::schedulePauseOnNextStatement(DebuggerEventType type, PassRefPtr<InspectorValue> data)
200 {
201     if (m_javaScriptPauseScheduled)
202         return;
203     m_breakProgramDetails = InspectorObject::create();
204     m_breakProgramDetails->setNumber("eventType", type);
205     m_breakProgramDetails->setValue("eventData", data);
206     ScriptDebugServer::shared().setPauseOnNextStatement(true);
207 }
208
209 void InspectorDebuggerAgent::cancelPauseOnNextStatement()
210 {
211     if (m_javaScriptPauseScheduled)
212         return;
213     m_breakProgramDetails = 0;
214     ScriptDebugServer::shared().setPauseOnNextStatement(false);
215 }
216
217 void InspectorDebuggerAgent::pause(ErrorString*)
218 {
219     schedulePauseOnNextStatement(JavaScriptPauseEventType, InspectorObject::create());
220     m_javaScriptPauseScheduled = true;
221 }
222
223 void InspectorDebuggerAgent::resume(ErrorString*)
224 {
225     ScriptDebugServer::shared().continueProgram();
226 }
227
228 void InspectorDebuggerAgent::stepOver(ErrorString*)
229 {
230     ScriptDebugServer::shared().stepOverStatement();
231 }
232
233 void InspectorDebuggerAgent::stepInto(ErrorString*)
234 {
235     ScriptDebugServer::shared().stepIntoStatement();
236 }
237
238 void InspectorDebuggerAgent::stepOut(ErrorString*)
239 {
240     ScriptDebugServer::shared().stepOutOfFunction();
241 }
242
243 void InspectorDebuggerAgent::setPauseOnExceptionsState(ErrorString*, long pauseState, long* newState)
244 {
245     ScriptDebugServer::shared().setPauseOnExceptionsState(static_cast<ScriptDebugServer::PauseOnExceptionsState>(pauseState));
246     *newState = ScriptDebugServer::shared().pauseOnExceptionsState();
247 }
248
249 void InspectorDebuggerAgent::evaluateOnCallFrame(ErrorString*, PassRefPtr<InspectorObject> callFrameId, const String& expression, const String& objectGroup, bool includeCommandLineAPI, RefPtr<InspectorValue>* result)
250 {
251     InjectedScript injectedScript = m_inspectorAgent->injectedScriptHost()->injectedScriptForObjectId(callFrameId.get());
252     if (!injectedScript.hasNoValue())
253         injectedScript.evaluateOnCallFrame(callFrameId, expression, objectGroup, includeCommandLineAPI, result);
254 }
255
256 void InspectorDebuggerAgent::getCompletionsOnCallFrame(ErrorString*, PassRefPtr<InspectorObject> callFrameId, const String& expression, bool includeCommandLineAPI, RefPtr<InspectorValue>* result)
257 {
258     InjectedScript injectedScript = m_inspectorAgent->injectedScriptHost()->injectedScriptForObjectId(callFrameId.get());
259     if (!injectedScript.hasNoValue())
260         injectedScript.getCompletionsOnCallFrame(callFrameId, expression, includeCommandLineAPI, result);
261 }
262
263 PassRefPtr<InspectorValue> InspectorDebuggerAgent::currentCallFrames()
264 {
265     if (!m_pausedScriptState)
266         return InspectorValue::null();
267     InjectedScript injectedScript = m_inspectorAgent->injectedScriptHost()->injectedScriptFor(m_pausedScriptState);
268     if (injectedScript.hasNoValue()) {
269         ASSERT_NOT_REACHED();
270         return InspectorValue::null();
271     }
272     return injectedScript.callFrames();
273 }
274
275 // JavaScriptDebugListener functions
276
277 void InspectorDebuggerAgent::didParseSource(const String& sourceID, const String& url, const String& data, int lineOffset, int columnOffset, ScriptWorldType worldType)
278 {
279     // Don't send script content to the front end until it's really needed.
280     m_frontend->parsedScriptSource(sourceID, url, lineOffset, columnOffset, data.length(), worldType);
281
282     m_scripts.set(sourceID, Script(url, data, lineOffset, columnOffset));
283
284     if (url.isEmpty())
285         return;
286
287     RefPtr<InspectorObject> breakpointsCookie = m_inspectorAgent->state()->getObject(DebuggerAgentState::javaScriptBreakpoints);
288     for (InspectorObject::iterator it = breakpointsCookie->begin(); it != breakpointsCookie->end(); ++it) {
289         RefPtr<InspectorObject> breakpointObject = it->second->asObject();
290         String breakpointURL;
291         breakpointObject->getString("url", &breakpointURL);
292         if (breakpointURL != url)
293             continue;
294         ScriptBreakpoint breakpoint;
295         breakpointObject->getNumber("lineNumber", &breakpoint.lineNumber);
296         breakpointObject->getNumber("columnNumber", &breakpoint.columnNumber);
297         breakpointObject->getString("condition", &breakpoint.condition);
298         breakpointObject->getBoolean("enabled", &breakpoint.enabled);
299         int actualLineNumber = 0, actualColumnNumber = 0;
300         if (resolveBreakpoint(it->first, sourceID, breakpoint, &actualLineNumber, &actualColumnNumber))
301             m_frontend->breakpointResolved(it->first, sourceID, actualLineNumber, actualColumnNumber);
302     }
303 }
304
305 void InspectorDebuggerAgent::failedToParseSource(const String& url, const String& data, int firstLine, int errorLine, const String& errorMessage)
306 {
307     m_frontend->failedToParseScriptSource(url, data, firstLine, errorLine, errorMessage);
308 }
309
310 void InspectorDebuggerAgent::didPause(ScriptState* scriptState)
311 {
312     ASSERT(scriptState && !m_pausedScriptState);
313     m_pausedScriptState = scriptState;
314
315     if (!m_breakProgramDetails)
316         m_breakProgramDetails = InspectorObject::create();
317     m_breakProgramDetails->setValue("callFrames", currentCallFrames());
318
319     m_frontend->pausedScript(m_breakProgramDetails);
320     m_javaScriptPauseScheduled = false;
321
322     if (!m_continueToLocationBreakpointId.isEmpty()) {
323         ScriptDebugServer::shared().removeBreakpoint(m_continueToLocationBreakpointId);
324         m_continueToLocationBreakpointId = "";
325     }
326 }
327
328 void InspectorDebuggerAgent::didContinue()
329 {
330     m_pausedScriptState = 0;
331     m_breakProgramDetails = 0;
332     m_frontend->resumedScript();
333 }
334
335 void InspectorDebuggerAgent::breakProgram(DebuggerEventType type, PassRefPtr<InspectorValue> data)
336 {
337     m_breakProgramDetails = InspectorObject::create();
338     m_breakProgramDetails->setNumber("eventType", type);
339     m_breakProgramDetails->setValue("eventData", data);
340     ScriptDebugServer::shared().breakProgram();
341 }
342
343 } // namespace WebCore
344
345 #endif // ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(INSPECTOR)