Home | History | Annotate | Download | only in inspector
      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 
     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 
     62 InspectorDebuggerAgent::~InspectorDebuggerAgent()
     63 {
     64     ASSERT(!m_instrumentingAgents->inspectorDebuggerAgent());
     65 }
     66 
     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 
     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 
    102 bool InspectorDebuggerAgent::enabled()
    103 {
    104     return m_inspectorState->getBoolean(DebuggerAgentState::debuggerEnabled);
    105 }
    106 
    107 void InspectorDebuggerAgent::restore()
    108 {
    109     if (m_inspectorState->getBoolean(DebuggerAgentState::debuggerEnabled))
    110         enable(true);
    111 }
    112 
    113 void InspectorDebuggerAgent::setFrontend(InspectorFrontend* frontend)
    114 {
    115     m_frontend = frontend->debugger();
    116 }
    117 
    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 
    130 void InspectorDebuggerAgent::setBreakpointsActive(ErrorString*, bool active)
    131 {
    132     if (active)
    133         scriptDebugServer().activateBreakpoints();
    134     else
    135         scriptDebugServer().deactivateBreakpoints();
    136 }
    137 
    138 void InspectorDebuggerAgent::inspectedURLChanged(const String&)
    139 {
    140     m_scripts.clear();
    141     m_breakpointIdToDebugServerBreakpointIds.clear();
    142 }
    143 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    286 void InspectorDebuggerAgent::getScriptSource(ErrorString*, const String& sourceID, String* scriptSource)
    287 {
    288     *scriptSource = m_scripts.get(sourceID).data;
    289 }
    290 
    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 
    301 void InspectorDebuggerAgent::cancelPauseOnNextStatement()
    302 {
    303     if (m_javaScriptPauseScheduled)
    304         return;
    305     m_breakProgramDetails = 0;
    306     scriptDebugServer().setPauseOnNextStatement(false);
    307 }
    308 
    309 void InspectorDebuggerAgent::pause(ErrorString*)
    310 {
    311     schedulePauseOnNextStatement(JavaScriptPauseEventType, InspectorObject::create());
    312     m_javaScriptPauseScheduled = true;
    313 }
    314 
    315 void InspectorDebuggerAgent::resume(ErrorString*)
    316 {
    317     m_injectedScriptManager->releaseObjectGroup("backtrace");
    318     scriptDebugServer().continueProgram();
    319 }
    320 
    321 void InspectorDebuggerAgent::stepOver(ErrorString*)
    322 {
    323     scriptDebugServer().stepOverStatement();
    324 }
    325 
    326 void InspectorDebuggerAgent::stepInto(ErrorString*)
    327 {
    328     scriptDebugServer().stepIntoStatement();
    329 }
    330 
    331 void InspectorDebuggerAgent::stepOut(ErrorString*)
    332 {
    333     scriptDebugServer().stepOutOfFunction();
    334 }
    335 
    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 
    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 
    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 
    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 
    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 
    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 
    425 void InspectorDebuggerAgent::didContinue()
    426 {
    427     m_pausedScriptState = 0;
    428     m_breakProgramDetails = 0;
    429     m_frontend->resumed();
    430 }
    431 
    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 
    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