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