initial import
[vuplus_webkit] / Source / WebCore / inspector / InspectorDebuggerAgent.cpp
1 /*
2  * Copyright (C) 2010 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 #include "InspectorDebuggerAgent.h"
32
33 #if ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(INSPECTOR)
34 #include "InjectedScript.h"
35 #include "InjectedScriptManager.h"
36 #include "InspectorFrontend.h"
37 #include "InspectorState.h"
38 #include "InspectorValues.h"
39 #include "InstrumentingAgents.h"
40 #include "RegularExpression.h"
41 #include "ScriptDebugServer.h"
42 #include "ScriptObject.h"
43 #include <wtf/text/WTFString.h>
44
45 namespace WebCore {
46
47 namespace DebuggerAgentState {
48 static const char debuggerEnabled[] = "debuggerEnabled";
49 static const char javaScriptBreakpoints[] = "javaScriptBreakopints";
50 };
51
52 const char* InspectorDebuggerAgent::backtraceObjectGroup = "backtrace-object-group";
53
54 InspectorDebuggerAgent::InspectorDebuggerAgent(InstrumentingAgents* instrumentingAgents, InspectorState* inspectorState, InjectedScriptManager* injectedScriptManager)
55     : m_instrumentingAgents(instrumentingAgents)
56     , m_inspectorState(inspectorState)
57     , m_injectedScriptManager(injectedScriptManager)
58     , m_frontend(0)
59     , m_pausedScriptState(0)
60     , m_javaScriptPauseScheduled(false)
61     , m_listener(0)
62 {
63 }
64
65 InspectorDebuggerAgent::~InspectorDebuggerAgent()
66 {
67     ASSERT(!m_instrumentingAgents->inspectorDebuggerAgent());
68 }
69
70 void InspectorDebuggerAgent::enable()
71 {
72     m_instrumentingAgents->setInspectorDebuggerAgent(this);
73
74     // FIXME(WK44513): breakpoints activated flag should be synchronized between all front-ends
75     scriptDebugServer().setBreakpointsActivated(true);
76     startListeningScriptDebugServer();
77
78     if (m_listener)
79         m_listener->debuggerWasEnabled();
80 }
81
82 void InspectorDebuggerAgent::disable()
83 {
84     m_inspectorState->setObject(DebuggerAgentState::javaScriptBreakpoints, InspectorObject::create());
85     m_instrumentingAgents->setInspectorDebuggerAgent(0);
86
87     stopListeningScriptDebugServer();
88     scriptDebugServer().clearBreakpoints();
89     clear();
90
91     if (m_listener)
92         m_listener->debuggerWasDisabled();
93 }
94
95 bool InspectorDebuggerAgent::enabled()
96 {
97     return m_inspectorState->getBoolean(DebuggerAgentState::debuggerEnabled);
98 }
99
100 void InspectorDebuggerAgent::enable(ErrorString*)
101 {
102     if (enabled())
103         return;
104
105     enable();
106     m_inspectorState->setBoolean(DebuggerAgentState::debuggerEnabled, true);
107
108     ASSERT(m_frontend);
109     m_frontend->debuggerWasEnabled();
110 }
111
112 void InspectorDebuggerAgent::disable(ErrorString*)
113 {
114     if (!enabled())
115         return;
116
117     disable();
118     m_inspectorState->setBoolean(DebuggerAgentState::debuggerEnabled, false);
119
120     if (m_frontend)
121         m_frontend->debuggerWasDisabled();
122 }
123
124 void InspectorDebuggerAgent::restore()
125 {
126     if (enabled())
127         enable();
128 }
129
130 void InspectorDebuggerAgent::setFrontend(InspectorFrontend* frontend)
131 {
132     m_frontend = frontend->debugger();
133 }
134
135 void InspectorDebuggerAgent::clearFrontend()
136 {
137     m_frontend = 0;
138
139     if (!enabled())
140         return;
141
142     disable();
143
144     // FIXME: due to m_state->mute() hack in InspectorController, debuggerEnabled is actually set to false only
145     // in InspectorState, but not in cookie. That's why after navigation debuggerEnabled will be true,
146     // but after front-end re-open it will still be false.
147     m_inspectorState->setBoolean(DebuggerAgentState::debuggerEnabled, false);
148 }
149
150 void InspectorDebuggerAgent::setBreakpointsActive(ErrorString*, bool active)
151 {
152     if (active)
153         scriptDebugServer().activateBreakpoints();
154     else
155         scriptDebugServer().deactivateBreakpoints();
156 }
157
158 void InspectorDebuggerAgent::inspectedURLChanged(const String&)
159 {
160     m_scripts.clear();
161     m_breakpointIdToDebugServerBreakpointIds.clear();
162 }
163
164 static PassRefPtr<InspectorObject> buildObjectForBreakpointCookie(const String& url, int lineNumber, int columnNumber, const String& condition, bool isRegex)
165 {
166     RefPtr<InspectorObject> breakpointObject = InspectorObject::create();
167     breakpointObject->setString("url", url);
168     breakpointObject->setNumber("lineNumber", lineNumber);
169     breakpointObject->setNumber("columnNumber", columnNumber);
170     breakpointObject->setString("condition", condition);
171     breakpointObject->setBoolean("isRegex", isRegex);
172     return breakpointObject;
173 }
174
175 static bool matches(const String& url, const String& pattern, bool isRegex)
176 {
177     if (isRegex) {
178         RegularExpression regex(pattern, TextCaseSensitive);
179         return regex.match(url) != -1;
180     }
181     return url == pattern;
182 }
183
184 void InspectorDebuggerAgent::setBreakpointByUrl(ErrorString* errorString, int lineNumber, const String* const optionalURL, const String* const optionalURLRegex, const int* const optionalColumnNumber, const String* const optionalCondition, String* outBreakpointId, RefPtr<InspectorArray>* locations)
185 {
186     if (!optionalURL == !optionalURLRegex) {
187         *errorString = "Either url or urlRegex must be specified.";
188         return;
189     }
190
191     String url = optionalURL ? *optionalURL : *optionalURLRegex;
192     int columnNumber = optionalColumnNumber ? *optionalColumnNumber : 0;
193     String condition = optionalCondition ? *optionalCondition : "";
194     bool isRegex = optionalURLRegex;
195
196     String breakpointId = (isRegex ? "/" + url + "/" : url) + ':' + String::number(lineNumber) + ':' + String::number(columnNumber);
197     RefPtr<InspectorObject> breakpointsCookie = m_inspectorState->getObject(DebuggerAgentState::javaScriptBreakpoints);
198     if (breakpointsCookie->find(breakpointId) != breakpointsCookie->end()) {
199         *errorString = "Breakpoint at specified location already exists.";
200         return;
201     }
202
203     breakpointsCookie->setObject(breakpointId, buildObjectForBreakpointCookie(url, lineNumber, columnNumber, condition, isRegex));
204     m_inspectorState->setObject(DebuggerAgentState::javaScriptBreakpoints, breakpointsCookie);
205
206     ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition);
207     for (ScriptsMap::iterator it = m_scripts.begin(); it != m_scripts.end(); ++it) {
208         if (!matches(it->second.url, url, isRegex))
209             continue;
210         RefPtr<InspectorObject> location = resolveBreakpoint(breakpointId, it->first, breakpoint);
211         if (location)
212             (*locations)->pushObject(location);
213     }
214     *outBreakpointId = breakpointId;
215 }
216
217 static bool parseLocation(ErrorString* errorString, RefPtr<InspectorObject> location, String* scriptId, int* lineNumber, int* columnNumber)
218 {
219     if (!location->getString("scriptId", scriptId) || !location->getNumber("lineNumber", lineNumber)) {
220         // FIXME: replace with input validation.
221         *errorString = "scriptId and lineNumber are required.";
222         return false;
223     }
224     *columnNumber = 0;
225     location->getNumber("columnNumber", columnNumber);
226     return true;
227 }
228
229 void InspectorDebuggerAgent::setBreakpoint(ErrorString* errorString, PassRefPtr<InspectorObject> location, const String* const optionalCondition, String* outBreakpointId, RefPtr<InspectorObject>* actualLocation)
230 {
231     String scriptId;
232     int lineNumber;
233     int columnNumber;
234
235     if (!parseLocation(errorString, location, &scriptId, &lineNumber, &columnNumber))
236         return;
237
238     String condition = optionalCondition ? *optionalCondition : emptyString();
239
240     String breakpointId = scriptId + ':' + String::number(lineNumber) + ':' + String::number(columnNumber);
241     if (m_breakpointIdToDebugServerBreakpointIds.find(breakpointId) != m_breakpointIdToDebugServerBreakpointIds.end())
242         return;
243     ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition);
244     *actualLocation = resolveBreakpoint(breakpointId, scriptId, breakpoint);
245     if (*actualLocation)
246         *outBreakpointId = breakpointId;
247     else
248         *errorString = "Could not resolve breakpoint";
249 }
250
251 void InspectorDebuggerAgent::removeBreakpoint(ErrorString*, const String& breakpointId)
252 {
253     RefPtr<InspectorObject> breakpointsCookie = m_inspectorState->getObject(DebuggerAgentState::javaScriptBreakpoints);
254     breakpointsCookie->remove(breakpointId);
255     m_inspectorState->setObject(DebuggerAgentState::javaScriptBreakpoints, breakpointsCookie);
256
257     BreakpointIdToDebugServerBreakpointIdsMap::iterator debugServerBreakpointIdsIterator = m_breakpointIdToDebugServerBreakpointIds.find(breakpointId);
258     if (debugServerBreakpointIdsIterator == m_breakpointIdToDebugServerBreakpointIds.end())
259         return;
260     for (size_t i = 0; i < debugServerBreakpointIdsIterator->second.size(); ++i)
261         scriptDebugServer().removeBreakpoint(debugServerBreakpointIdsIterator->second[i]);
262     m_breakpointIdToDebugServerBreakpointIds.remove(debugServerBreakpointIdsIterator);
263 }
264
265 void InspectorDebuggerAgent::continueToLocation(ErrorString* errorString, PassRefPtr<InspectorObject> location)
266 {
267     if (!m_continueToLocationBreakpointId.isEmpty()) {
268         scriptDebugServer().removeBreakpoint(m_continueToLocationBreakpointId);
269         m_continueToLocationBreakpointId = "";
270     }
271
272     String scriptId;
273     int lineNumber;
274     int columnNumber;
275
276     if (!parseLocation(errorString, location, &scriptId, &lineNumber, &columnNumber))
277         return;
278
279     ScriptBreakpoint breakpoint(lineNumber, columnNumber, "");
280     m_continueToLocationBreakpointId = scriptDebugServer().setBreakpoint(scriptId, breakpoint, &lineNumber, &columnNumber);
281     resume(errorString);
282 }
283
284 PassRefPtr<InspectorObject> InspectorDebuggerAgent::resolveBreakpoint(const String& breakpointId, const String& scriptId, const ScriptBreakpoint& breakpoint)
285 {
286     ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId);
287     if (scriptIterator == m_scripts.end())
288         return 0;
289     Script& script = scriptIterator->second;
290     if (breakpoint.lineNumber < script.startLine || script.endLine < breakpoint.lineNumber)
291         return 0;
292
293     int actualLineNumber;
294     int actualColumnNumber;
295     String debugServerBreakpointId = scriptDebugServer().setBreakpoint(scriptId, breakpoint, &actualLineNumber, &actualColumnNumber);
296     if (debugServerBreakpointId.isEmpty())
297         return 0;
298
299     BreakpointIdToDebugServerBreakpointIdsMap::iterator debugServerBreakpointIdsIterator = m_breakpointIdToDebugServerBreakpointIds.find(breakpointId);
300     if (debugServerBreakpointIdsIterator == m_breakpointIdToDebugServerBreakpointIds.end())
301         debugServerBreakpointIdsIterator = m_breakpointIdToDebugServerBreakpointIds.set(breakpointId, Vector<String>()).first;
302     debugServerBreakpointIdsIterator->second.append(debugServerBreakpointId);
303
304     RefPtr<InspectorObject> location = InspectorObject::create();
305     location->setString("scriptId", scriptId);
306     location->setNumber("lineNumber", actualLineNumber);
307     location->setNumber("columnNumber", actualColumnNumber);
308     return location;
309 }
310
311 static PassRefPtr<InspectorObject> scriptToInspectorObject(ScriptObject scriptObject)
312 {
313     if (scriptObject.hasNoValue())
314         return 0;
315     RefPtr<InspectorValue> value = scriptObject.toInspectorValue(scriptObject.scriptState());
316     if (!value)
317         return 0;
318     return value->asObject();
319 }
320
321 void InspectorDebuggerAgent::setScriptSource(ErrorString* error, const String& scriptId, const String& newContent, const bool* const preview, RefPtr<InspectorArray>* newCallFrames, RefPtr<InspectorObject>* result)
322 {
323     bool previewOnly = preview && *preview;
324     ScriptObject resultObject;
325     if (!scriptDebugServer().setScriptSource(scriptId, newContent, previewOnly, error, &m_currentCallStack, &resultObject))
326         return;
327     *newCallFrames = currentCallFrames();
328     RefPtr<InspectorObject> object = scriptToInspectorObject(resultObject);
329     if (object)
330         *result = object;
331 }
332
333 void InspectorDebuggerAgent::getScriptSource(ErrorString*, const String& scriptId, String* scriptSource)
334 {
335     *scriptSource = m_scripts.get(scriptId).source;
336 }
337
338 void InspectorDebuggerAgent::schedulePauseOnNextStatement(DebuggerEventType type, PassRefPtr<InspectorValue> data)
339 {
340     if (m_javaScriptPauseScheduled)
341         return;
342     m_breakProgramDetails = InspectorObject::create();
343     m_breakProgramDetails->setNumber("eventType", type);
344     m_breakProgramDetails->setValue("eventData", data);
345     scriptDebugServer().setPauseOnNextStatement(true);
346 }
347
348 void InspectorDebuggerAgent::cancelPauseOnNextStatement()
349 {
350     if (m_javaScriptPauseScheduled)
351         return;
352     m_breakProgramDetails = 0;
353     scriptDebugServer().setPauseOnNextStatement(false);
354 }
355
356 void InspectorDebuggerAgent::pause(ErrorString*)
357 {
358     schedulePauseOnNextStatement(JavaScriptPauseEventType, InspectorObject::create());
359     m_javaScriptPauseScheduled = true;
360 }
361
362 void InspectorDebuggerAgent::resume(ErrorString* errorString)
363 {
364     if (!assertPaused(errorString))
365         return;
366     m_injectedScriptManager->releaseObjectGroup(InspectorDebuggerAgent::backtraceObjectGroup);
367     scriptDebugServer().continueProgram();
368 }
369
370 void InspectorDebuggerAgent::stepOver(ErrorString* errorString)
371 {
372     if (!assertPaused(errorString))
373         return;
374     scriptDebugServer().stepOverStatement();
375 }
376
377 void InspectorDebuggerAgent::stepInto(ErrorString* errorString)
378 {
379     if (!assertPaused(errorString))
380         return;
381     scriptDebugServer().stepIntoStatement();
382 }
383
384 void InspectorDebuggerAgent::stepOut(ErrorString* errorString)
385 {
386     if (!assertPaused(errorString))
387         return;
388     scriptDebugServer().stepOutOfFunction();
389 }
390
391 void InspectorDebuggerAgent::setPauseOnExceptions(ErrorString* errorString, const String& stringPauseState)
392 {
393     ScriptDebugServer::PauseOnExceptionsState pauseState;
394     if (stringPauseState == "none")
395         pauseState = ScriptDebugServer::DontPauseOnExceptions;
396     else if (stringPauseState == "all")
397         pauseState = ScriptDebugServer::PauseOnAllExceptions;
398     else if (stringPauseState == "uncaught")
399         pauseState = ScriptDebugServer::PauseOnUncaughtExceptions;
400     else {
401         *errorString = "Unknown pause on exceptions mode: " + stringPauseState;
402         return;
403     }
404     scriptDebugServer().setPauseOnExceptionsState(static_cast<ScriptDebugServer::PauseOnExceptionsState>(pauseState));
405     if (scriptDebugServer().pauseOnExceptionsState() != pauseState)
406         *errorString = "Internal error. Could not change pause on exceptions state";
407 }
408
409 void InspectorDebuggerAgent::evaluateOnCallFrame(ErrorString* errorString, const String& callFrameId, const String& expression, const String* const objectGroup, const bool* const includeCommandLineAPI, const bool* const returnByValue, RefPtr<InspectorObject>* result, bool* wasThrown)
410 {
411     InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(callFrameId);
412     if (injectedScript.hasNoValue()) {
413         *errorString = "Inspected frame has gone";
414         return;
415     }
416     injectedScript.evaluateOnCallFrame(errorString, m_currentCallStack, callFrameId, expression, objectGroup ? *objectGroup : "", includeCommandLineAPI ? *includeCommandLineAPI : false, returnByValue ? *returnByValue : false, result, wasThrown);
417 }
418
419 PassRefPtr<InspectorArray> InspectorDebuggerAgent::currentCallFrames()
420 {
421     if (!m_pausedScriptState)
422         return InspectorArray::create();
423     InjectedScript injectedScript = m_injectedScriptManager->injectedScriptFor(m_pausedScriptState);
424     if (injectedScript.hasNoValue()) {
425         ASSERT_NOT_REACHED();
426         return InspectorArray::create();
427     }
428     return injectedScript.wrapCallFrames(m_currentCallStack);
429 }
430
431 // JavaScriptDebugListener functions
432
433 void InspectorDebuggerAgent::didParseSource(const String& scriptId, const Script& script)
434 {
435     // Don't send script content to the front end until it's really needed.
436     m_frontend->scriptParsed(scriptId, script.url, script.startLine, script.startColumn, script.endLine, script.endColumn, script.isContentScript);
437
438     m_scripts.set(scriptId, script);
439
440     if (script.url.isEmpty())
441         return;
442
443     RefPtr<InspectorObject> breakpointsCookie = m_inspectorState->getObject(DebuggerAgentState::javaScriptBreakpoints);
444     for (InspectorObject::iterator it = breakpointsCookie->begin(); it != breakpointsCookie->end(); ++it) {
445         RefPtr<InspectorObject> breakpointObject = it->second->asObject();
446         bool isRegex;
447         breakpointObject->getBoolean("isRegex", &isRegex);
448         String url;
449         breakpointObject->getString("url", &url);
450         if (!matches(script.url, url, isRegex))
451             continue;
452         ScriptBreakpoint breakpoint;
453         breakpointObject->getNumber("lineNumber", &breakpoint.lineNumber);
454         breakpointObject->getNumber("columnNumber", &breakpoint.columnNumber);
455         breakpointObject->getString("condition", &breakpoint.condition);
456         RefPtr<InspectorObject> location = resolveBreakpoint(it->first, scriptId, breakpoint);
457         if (location)
458             m_frontend->breakpointResolved(it->first, location);
459     }
460 }
461
462 void InspectorDebuggerAgent::failedToParseSource(const String& url, const String& data, int firstLine, int errorLine, const String& errorMessage)
463 {
464     m_frontend->scriptFailedToParse(url, data, firstLine, errorLine, errorMessage);
465 }
466
467 void InspectorDebuggerAgent::didPause(ScriptState* scriptState, const ScriptValue& callFrames, const ScriptValue& exception)
468 {
469     ASSERT(scriptState && !m_pausedScriptState);
470     m_pausedScriptState = scriptState;
471     m_currentCallStack = callFrames;
472
473     if (!m_breakProgramDetails)
474         m_breakProgramDetails = InspectorObject::create();
475     m_breakProgramDetails->setValue("callFrames", currentCallFrames());
476
477     if (!exception.hasNoValue()) {
478         InjectedScript injectedScript = m_injectedScriptManager->injectedScriptFor(scriptState);
479         if (!injectedScript.hasNoValue())
480             m_breakProgramDetails->setValue("exception", injectedScript.wrapObject(exception, "backtrace"));
481     }
482
483     m_frontend->paused(m_breakProgramDetails);
484     m_javaScriptPauseScheduled = false;
485
486     if (!m_continueToLocationBreakpointId.isEmpty()) {
487         scriptDebugServer().removeBreakpoint(m_continueToLocationBreakpointId);
488         m_continueToLocationBreakpointId = "";
489     }
490 }
491
492 void InspectorDebuggerAgent::didContinue()
493 {
494     m_pausedScriptState = 0;
495     m_currentCallStack = ScriptValue();
496     m_breakProgramDetails = 0;
497     m_frontend->resumed();
498 }
499
500 void InspectorDebuggerAgent::breakProgram(DebuggerEventType type, PassRefPtr<InspectorValue> data)
501 {
502     m_breakProgramDetails = InspectorObject::create();
503     m_breakProgramDetails->setNumber("eventType", type);
504     m_breakProgramDetails->setValue("eventData", data);
505     scriptDebugServer().breakProgram();
506 }
507
508 void InspectorDebuggerAgent::clear()
509 {
510     m_pausedScriptState = 0;
511     m_currentCallStack = ScriptValue();
512     m_scripts.clear();
513     m_breakpointIdToDebugServerBreakpointIds.clear();
514     m_continueToLocationBreakpointId = String();
515     m_breakProgramDetails.clear();
516     m_javaScriptPauseScheduled = false;
517 }
518
519 bool InspectorDebuggerAgent::assertPaused(ErrorString* errorString)
520 {
521     if (!m_pausedScriptState) {
522         *errorString = "Can only perform operation while paused.";
523         return false;
524     }
525     return true;
526 }
527
528 } // namespace WebCore
529
530 #endif // ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(INSPECTOR)