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