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