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