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
44namespace WebCore {
45
46namespace DebuggerAgentState {
47static const char debuggerEnabled[] = "debuggerEnabled";
48static const char javaScriptBreakpoints[] = "javaScriptBreakopints";
49};
50
51InspectorDebuggerAgent::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
62InspectorDebuggerAgent::~InspectorDebuggerAgent()
63{
64    ASSERT(!m_instrumentingAgents->inspectorDebuggerAgent());
65}
66
67void 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
85void 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
102bool InspectorDebuggerAgent::enabled()
103{
104    return m_inspectorState->getBoolean(DebuggerAgentState::debuggerEnabled);
105}
106
107void InspectorDebuggerAgent::restore()
108{
109    if (m_inspectorState->getBoolean(DebuggerAgentState::debuggerEnabled))
110        enable(true);
111}
112
113void InspectorDebuggerAgent::setFrontend(InspectorFrontend* frontend)
114{
115    m_frontend = frontend->debugger();
116}
117
118void 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
130void InspectorDebuggerAgent::setBreakpointsActive(ErrorString*, bool active)
131{
132    if (active)
133        scriptDebugServer().activateBreakpoints();
134    else
135        scriptDebugServer().deactivateBreakpoints();
136}
137
138void InspectorDebuggerAgent::inspectedURLChanged(const String&)
139{
140    m_scripts.clear();
141    m_breakpointIdToDebugServerBreakpointIds.clear();
142}
143
144static 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
154void 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
177static 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
189void 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
211void 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
225void 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
244PassRefPtr<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
280void 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
286void InspectorDebuggerAgent::getScriptSource(ErrorString*, const String& sourceID, String* scriptSource)
287{
288    *scriptSource = m_scripts.get(sourceID).data;
289}
290
291void 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
301void InspectorDebuggerAgent::cancelPauseOnNextStatement()
302{
303    if (m_javaScriptPauseScheduled)
304        return;
305    m_breakProgramDetails = 0;
306    scriptDebugServer().setPauseOnNextStatement(false);
307}
308
309void InspectorDebuggerAgent::pause(ErrorString*)
310{
311    schedulePauseOnNextStatement(JavaScriptPauseEventType, InspectorObject::create());
312    m_javaScriptPauseScheduled = true;
313}
314
315void InspectorDebuggerAgent::resume(ErrorString*)
316{
317    m_injectedScriptManager->releaseObjectGroup("backtrace");
318    scriptDebugServer().continueProgram();
319}
320
321void InspectorDebuggerAgent::stepOver(ErrorString*)
322{
323    scriptDebugServer().stepOverStatement();
324}
325
326void InspectorDebuggerAgent::stepInto(ErrorString*)
327{
328    scriptDebugServer().stepIntoStatement();
329}
330
331void InspectorDebuggerAgent::stepOut(ErrorString*)
332{
333    scriptDebugServer().stepOutOfFunction();
334}
335
336void 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
354void 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
361PassRefPtr<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
375void 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
402void 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
407void 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
425void InspectorDebuggerAgent::didContinue()
426{
427    m_pausedScriptState = 0;
428    m_breakProgramDetails = 0;
429    m_frontend->resumed();
430}
431
432void 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
440void 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