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