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