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