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 "core/inspector/InspectorDebuggerAgent.h" 32 #include "core/inspector/JavaScriptCallFrame.h" 33 34 #include "InspectorFrontend.h" 35 #include "bindings/v8/ScriptDebugServer.h" 36 #include "bindings/v8/ScriptObject.h" 37 #include "core/inspector/ContentSearchUtils.h" 38 #include "core/inspector/InjectedScript.h" 39 #include "core/inspector/InjectedScriptManager.h" 40 #include "core/inspector/InspectorPageAgent.h" 41 #include "core/inspector/InspectorState.h" 42 #include "core/inspector/InstrumentingAgents.h" 43 #include "core/inspector/ScriptArguments.h" 44 #include "core/inspector/ScriptCallStack.h" 45 #include "core/loader/cache/Resource.h" 46 #include "core/platform/JSONValues.h" 47 #include "core/platform/text/RegularExpression.h" 48 #include "wtf/text/WTFString.h" 49 50 using WebCore::TypeBuilder::Array; 51 using WebCore::TypeBuilder::Debugger::FunctionDetails; 52 using WebCore::TypeBuilder::Debugger::ScriptId; 53 using WebCore::TypeBuilder::Runtime::RemoteObject; 54 55 namespace WebCore { 56 57 namespace DebuggerAgentState { 58 static const char debuggerEnabled[] = "debuggerEnabled"; 59 static const char javaScriptBreakpoints[] = "javaScriptBreakopints"; 60 static const char pauseOnExceptionsState[] = "pauseOnExceptionsState"; 61 62 // Breakpoint properties. 63 static const char url[] = "url"; 64 static const char isRegex[] = "isRegex"; 65 static const char lineNumber[] = "lineNumber"; 66 static const char columnNumber[] = "columnNumber"; 67 static const char condition[] = "condition"; 68 static const char isAnti[] = "isAnti"; 69 static const char skipStackPattern[] = "skipStackPattern"; 70 }; 71 72 static const int numberOfStepsBeforeStepOut = 10; 73 74 const char* InspectorDebuggerAgent::backtraceObjectGroup = "backtrace"; 75 76 static String breakpointIdSuffix(InspectorDebuggerAgent::BreakpointSource source) 77 { 78 switch (source) { 79 case InspectorDebuggerAgent::UserBreakpointSource: 80 break; 81 case InspectorDebuggerAgent::DebugCommandBreakpointSource: 82 return ":debug"; 83 case InspectorDebuggerAgent::MonitorCommandBreakpointSource: 84 return ":monitor"; 85 } 86 return String(); 87 } 88 89 static String generateBreakpointId(const String& scriptId, int lineNumber, int columnNumber, InspectorDebuggerAgent::BreakpointSource source) 90 { 91 return scriptId + ':' + String::number(lineNumber) + ':' + String::number(columnNumber) + breakpointIdSuffix(source); 92 } 93 94 InspectorDebuggerAgent::InspectorDebuggerAgent(InstrumentingAgents* instrumentingAgents, InspectorCompositeState* inspectorState, InjectedScriptManager* injectedScriptManager) 95 : InspectorBaseAgent<InspectorDebuggerAgent>("Debugger", instrumentingAgents, inspectorState) 96 , m_injectedScriptManager(injectedScriptManager) 97 , m_frontend(0) 98 , m_pausedScriptState(0) 99 , m_javaScriptPauseScheduled(false) 100 , m_listener(0) 101 , m_skipStepInCount(numberOfStepsBeforeStepOut) 102 { 103 // FIXME: make breakReason optional so that there was no need to init it with "other". 104 clearBreakDetails(); 105 m_state->setLong(DebuggerAgentState::pauseOnExceptionsState, ScriptDebugServer::DontPauseOnExceptions); 106 } 107 108 InspectorDebuggerAgent::~InspectorDebuggerAgent() 109 { 110 ASSERT(!m_instrumentingAgents->inspectorDebuggerAgent()); 111 } 112 113 void InspectorDebuggerAgent::enable() 114 { 115 m_instrumentingAgents->setInspectorDebuggerAgent(this); 116 117 // FIXME(WK44513): breakpoints activated flag should be synchronized between all front-ends 118 scriptDebugServer().setBreakpointsActivated(true); 119 startListeningScriptDebugServer(); 120 121 if (m_listener) 122 m_listener->debuggerWasEnabled(); 123 } 124 125 void InspectorDebuggerAgent::disable() 126 { 127 m_state->setObject(DebuggerAgentState::javaScriptBreakpoints, JSONObject::create()); 128 m_state->setLong(DebuggerAgentState::pauseOnExceptionsState, ScriptDebugServer::DontPauseOnExceptions); 129 m_state->setString(DebuggerAgentState::skipStackPattern, ""); 130 m_instrumentingAgents->setInspectorDebuggerAgent(0); 131 132 stopListeningScriptDebugServer(); 133 scriptDebugServer().clearBreakpoints(); 134 scriptDebugServer().clearCompiledScripts(); 135 clear(); 136 137 if (m_listener) 138 m_listener->debuggerWasDisabled(); 139 } 140 141 bool InspectorDebuggerAgent::enabled() 142 { 143 return m_state->getBoolean(DebuggerAgentState::debuggerEnabled); 144 } 145 146 void InspectorDebuggerAgent::enable(ErrorString*) 147 { 148 if (enabled()) 149 return; 150 151 enable(); 152 m_state->setBoolean(DebuggerAgentState::debuggerEnabled, true); 153 154 ASSERT(m_frontend); 155 } 156 157 void InspectorDebuggerAgent::disable(ErrorString*) 158 { 159 if (!enabled()) 160 return; 161 162 disable(); 163 m_state->setBoolean(DebuggerAgentState::debuggerEnabled, false); 164 } 165 166 static PassOwnPtr<RegularExpression> compileSkipCallFramePattern(String patternText) 167 { 168 if (patternText.isEmpty()) 169 return nullptr; 170 OwnPtr<RegularExpression> result = adoptPtr(new RegularExpression(patternText, TextCaseSensitive)); 171 if (!result->isValid()) 172 result.clear(); 173 return result.release(); 174 } 175 176 void InspectorDebuggerAgent::restore() 177 { 178 if (enabled()) { 179 m_frontend->globalObjectCleared(); 180 enable(); 181 long pauseState = m_state->getLong(DebuggerAgentState::pauseOnExceptionsState); 182 String error; 183 setPauseOnExceptionsImpl(&error, pauseState); 184 m_cachedSkipStackRegExp = compileSkipCallFramePattern(m_state->getString(DebuggerAgentState::skipStackPattern)); 185 } 186 } 187 188 void InspectorDebuggerAgent::setFrontend(InspectorFrontend* frontend) 189 { 190 m_frontend = frontend->debugger(); 191 } 192 193 void InspectorDebuggerAgent::clearFrontend() 194 { 195 m_frontend = 0; 196 197 if (!enabled()) 198 return; 199 200 disable(); 201 202 // FIXME: due to m_state->mute() hack in InspectorController, debuggerEnabled is actually set to false only 203 // in InspectorState, but not in cookie. That's why after navigation debuggerEnabled will be true, 204 // but after front-end re-open it will still be false. 205 m_state->setBoolean(DebuggerAgentState::debuggerEnabled, false); 206 } 207 208 void InspectorDebuggerAgent::setBreakpointsActive(ErrorString*, bool active) 209 { 210 scriptDebugServer().setBreakpointsActivated(active); 211 } 212 213 bool InspectorDebuggerAgent::isPaused() 214 { 215 return scriptDebugServer().isPaused(); 216 } 217 218 bool InspectorDebuggerAgent::runningNestedMessageLoop() 219 { 220 return scriptDebugServer().runningNestedMessageLoop(); 221 } 222 223 void InspectorDebuggerAgent::addMessageToConsole(MessageSource source, MessageType type) 224 { 225 if (source == ConsoleAPIMessageSource && type == AssertMessageType && scriptDebugServer().pauseOnExceptionsState() != ScriptDebugServer::DontPauseOnExceptions) 226 breakProgram(InspectorFrontend::Debugger::Reason::Assert, 0); 227 } 228 229 void InspectorDebuggerAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel, const String&, PassRefPtr<ScriptCallStack>, unsigned long) 230 { 231 addMessageToConsole(source, type); 232 } 233 234 void InspectorDebuggerAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel, const String&, ScriptState*, PassRefPtr<ScriptArguments>, unsigned long) 235 { 236 addMessageToConsole(source, type); 237 } 238 239 240 static PassRefPtr<JSONObject> buildObjectForBreakpointCookie(const String& url, int lineNumber, int columnNumber, const String& condition, bool isRegex, bool isAnti) 241 { 242 RefPtr<JSONObject> breakpointObject = JSONObject::create(); 243 breakpointObject->setString(DebuggerAgentState::url, url); 244 breakpointObject->setNumber(DebuggerAgentState::lineNumber, lineNumber); 245 breakpointObject->setNumber(DebuggerAgentState::columnNumber, columnNumber); 246 breakpointObject->setString(DebuggerAgentState::condition, condition); 247 breakpointObject->setBoolean(DebuggerAgentState::isRegex, isRegex); 248 breakpointObject->setBoolean(DebuggerAgentState::isAnti, isAnti); 249 return breakpointObject; 250 } 251 252 static bool matches(const String& url, const String& pattern, bool isRegex) 253 { 254 if (isRegex) { 255 RegularExpression regex(pattern, TextCaseSensitive); 256 return regex.match(url) != -1; 257 } 258 return url == pattern; 259 } 260 261 void InspectorDebuggerAgent::setBreakpointByUrl(ErrorString* errorString, int lineNumber, const String* const optionalURL, const String* const optionalURLRegex, const int* const optionalColumnNumber, const String* const optionalCondition, const bool* isAntiBreakpoint, TypeBuilder::Debugger::BreakpointId* outBreakpointId, RefPtr<TypeBuilder::Array<TypeBuilder::Debugger::Location> >& locations) 262 { 263 locations = Array<TypeBuilder::Debugger::Location>::create(); 264 if (!optionalURL == !optionalURLRegex) { 265 *errorString = "Either url or urlRegex must be specified."; 266 return; 267 } 268 269 bool isAntiBreakpointValue = isAntiBreakpoint && *isAntiBreakpoint; 270 271 String url = optionalURL ? *optionalURL : *optionalURLRegex; 272 int columnNumber; 273 if (optionalColumnNumber) { 274 columnNumber = *optionalColumnNumber; 275 if (columnNumber < 0) { 276 *errorString = "Incorrect column number"; 277 return; 278 } 279 } else { 280 columnNumber = isAntiBreakpointValue ? -1 : 0; 281 } 282 String condition = optionalCondition ? *optionalCondition : ""; 283 bool isRegex = optionalURLRegex; 284 285 String breakpointId = (isRegex ? "/" + url + "/" : url) + ':' + String::number(lineNumber) + ':' + String::number(columnNumber); 286 RefPtr<JSONObject> breakpointsCookie = m_state->getObject(DebuggerAgentState::javaScriptBreakpoints); 287 if (breakpointsCookie->find(breakpointId) != breakpointsCookie->end()) { 288 *errorString = "Breakpoint at specified location already exists."; 289 return; 290 } 291 292 breakpointsCookie->setObject(breakpointId, buildObjectForBreakpointCookie(url, lineNumber, columnNumber, condition, isRegex, isAntiBreakpointValue)); 293 m_state->setObject(DebuggerAgentState::javaScriptBreakpoints, breakpointsCookie); 294 295 if (!isAntiBreakpointValue) { 296 ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition); 297 for (ScriptsMap::iterator it = m_scripts.begin(); it != m_scripts.end(); ++it) { 298 if (!matches(it->value.url, url, isRegex)) 299 continue; 300 RefPtr<TypeBuilder::Debugger::Location> location = resolveBreakpoint(breakpointId, it->key, breakpoint, UserBreakpointSource); 301 if (location) 302 locations->addItem(location); 303 } 304 } 305 *outBreakpointId = breakpointId; 306 } 307 308 static bool parseLocation(ErrorString* errorString, PassRefPtr<JSONObject> location, String* scriptId, int* lineNumber, int* columnNumber) 309 { 310 if (!location->getString("scriptId", scriptId) || !location->getNumber("lineNumber", lineNumber)) { 311 // FIXME: replace with input validation. 312 *errorString = "scriptId and lineNumber are required."; 313 return false; 314 } 315 *columnNumber = 0; 316 location->getNumber("columnNumber", columnNumber); 317 return true; 318 } 319 320 void InspectorDebuggerAgent::setBreakpoint(ErrorString* errorString, const RefPtr<JSONObject>& location, const String* const optionalCondition, TypeBuilder::Debugger::BreakpointId* outBreakpointId, RefPtr<TypeBuilder::Debugger::Location>& actualLocation) 321 { 322 String scriptId; 323 int lineNumber; 324 int columnNumber; 325 326 if (!parseLocation(errorString, location, &scriptId, &lineNumber, &columnNumber)) 327 return; 328 329 String condition = optionalCondition ? *optionalCondition : emptyString(); 330 331 String breakpointId = generateBreakpointId(scriptId, lineNumber, columnNumber, UserBreakpointSource); 332 if (m_breakpointIdToDebugServerBreakpointIds.find(breakpointId) != m_breakpointIdToDebugServerBreakpointIds.end()) { 333 *errorString = "Breakpoint at specified location already exists."; 334 return; 335 } 336 ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition); 337 actualLocation = resolveBreakpoint(breakpointId, scriptId, breakpoint, UserBreakpointSource); 338 if (actualLocation) 339 *outBreakpointId = breakpointId; 340 else 341 *errorString = "Could not resolve breakpoint"; 342 } 343 344 void InspectorDebuggerAgent::removeBreakpoint(ErrorString*, const String& breakpointId) 345 { 346 RefPtr<JSONObject> breakpointsCookie = m_state->getObject(DebuggerAgentState::javaScriptBreakpoints); 347 JSONObject::iterator it = breakpointsCookie->find(breakpointId); 348 bool isAntibreakpoint = false; 349 if (it != breakpointsCookie->end()) { 350 RefPtr<JSONObject> breakpointObject = it->value->asObject(); 351 breakpointObject->getBoolean(DebuggerAgentState::isAnti, &isAntibreakpoint); 352 breakpointsCookie->remove(breakpointId); 353 m_state->setObject(DebuggerAgentState::javaScriptBreakpoints, breakpointsCookie); 354 } 355 356 if (!isAntibreakpoint) 357 removeBreakpoint(breakpointId); 358 } 359 360 void InspectorDebuggerAgent::removeBreakpoint(const String& breakpointId) 361 { 362 BreakpointIdToDebugServerBreakpointIdsMap::iterator debugServerBreakpointIdsIterator = m_breakpointIdToDebugServerBreakpointIds.find(breakpointId); 363 if (debugServerBreakpointIdsIterator == m_breakpointIdToDebugServerBreakpointIds.end()) 364 return; 365 for (size_t i = 0; i < debugServerBreakpointIdsIterator->value.size(); ++i) { 366 const String& debugServerBreakpointId = debugServerBreakpointIdsIterator->value[i]; 367 scriptDebugServer().removeBreakpoint(debugServerBreakpointId); 368 m_serverBreakpoints.remove(debugServerBreakpointId); 369 } 370 m_breakpointIdToDebugServerBreakpointIds.remove(debugServerBreakpointIdsIterator); 371 } 372 373 void InspectorDebuggerAgent::continueToLocation(ErrorString* errorString, const RefPtr<JSONObject>& location, const bool* interstateLocationOpt) 374 { 375 bool interstateLocation = interstateLocationOpt ? *interstateLocationOpt : false; 376 if (!m_continueToLocationBreakpointId.isEmpty()) { 377 scriptDebugServer().removeBreakpoint(m_continueToLocationBreakpointId); 378 m_continueToLocationBreakpointId = ""; 379 } 380 381 String scriptId; 382 int lineNumber; 383 int columnNumber; 384 385 if (!parseLocation(errorString, location, &scriptId, &lineNumber, &columnNumber)) 386 return; 387 388 ScriptBreakpoint breakpoint(lineNumber, columnNumber, ""); 389 m_continueToLocationBreakpointId = scriptDebugServer().setBreakpoint(scriptId, breakpoint, &lineNumber, &columnNumber, interstateLocation); 390 resume(errorString); 391 } 392 393 void InspectorDebuggerAgent::getStepInPositions(ErrorString* errorString, const String& callFrameId, RefPtr<Array<TypeBuilder::Debugger::Location> >& positions) 394 { 395 if (!isPaused() || m_currentCallStack.isNull()) { 396 *errorString = "Attempt to access callframe when debugger is not on pause"; 397 return; 398 } 399 InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(callFrameId); 400 if (injectedScript.hasNoValue()) { 401 *errorString = "Inspected frame has gone"; 402 return; 403 } 404 405 injectedScript.getStepInPositions(errorString, m_currentCallStack, callFrameId, positions); 406 } 407 408 void InspectorDebuggerAgent::getBacktrace(ErrorString* errorString, RefPtr<Array<TypeBuilder::Debugger::CallFrame> >& callFrames) 409 { 410 if (!assertPaused(errorString)) 411 return; 412 scriptDebugServer().updateCallStack(&m_currentCallStack); 413 callFrames = currentCallFrames(); 414 } 415 416 String InspectorDebuggerAgent::scriptURL(JavaScriptCallFrame* frame) 417 { 418 String scriptIdString = String::number(frame->sourceID()); 419 ScriptsMap::iterator it = m_scripts.find(scriptIdString); 420 if (it == m_scripts.end()) 421 return String(); 422 return it->value.url; 423 } 424 425 ScriptDebugListener::SkipPauseRequest InspectorDebuggerAgent::shouldSkipExceptionPause(RefPtr<JavaScriptCallFrame>& topFrame) 426 { 427 String topFrameScriptUrl = scriptURL(topFrame.get()); 428 if (m_cachedSkipStackRegExp && !topFrameScriptUrl.isEmpty() && m_cachedSkipStackRegExp->match(topFrameScriptUrl) != -1) 429 return ScriptDebugListener::Continue; 430 431 // Prepare top frame parameters; 432 int topFrameLineNumber = topFrame->line(); 433 int topFrameColumnNumber = topFrame->column(); 434 435 // Match against breakpoints. 436 if (topFrameScriptUrl.isEmpty()) 437 return ScriptDebugListener::NoSkip; 438 439 RefPtr<JSONObject> breakpointsCookie = m_state->getObject(DebuggerAgentState::javaScriptBreakpoints); 440 for (JSONObject::iterator it = breakpointsCookie->begin(); it != breakpointsCookie->end(); ++it) { 441 RefPtr<JSONObject> breakpointObject = it->value->asObject(); 442 bool isAntibreakpoint; 443 breakpointObject->getBoolean(DebuggerAgentState::isAnti, &isAntibreakpoint); 444 if (!isAntibreakpoint) 445 continue; 446 447 int breakLineNumber; 448 breakpointObject->getNumber(DebuggerAgentState::lineNumber, &breakLineNumber); 449 int breakColumnNumber; 450 breakpointObject->getNumber(DebuggerAgentState::columnNumber, &breakColumnNumber); 451 452 if (breakLineNumber != topFrameLineNumber) 453 continue; 454 455 if (breakColumnNumber != -1 && breakColumnNumber != topFrameColumnNumber) 456 continue; 457 458 bool isRegex; 459 breakpointObject->getBoolean(DebuggerAgentState::isRegex, &isRegex); 460 String url; 461 breakpointObject->getString(DebuggerAgentState::url, &url); 462 if (!matches(topFrameScriptUrl, url, isRegex)) 463 continue; 464 465 return ScriptDebugListener::Continue; 466 } 467 468 return ScriptDebugListener::NoSkip; 469 } 470 471 ScriptDebugListener::SkipPauseRequest InspectorDebuggerAgent::shouldSkipBreakpointPause(RefPtr<JavaScriptCallFrame>& topFrame) 472 { 473 return ScriptDebugListener::NoSkip; 474 } 475 476 ScriptDebugListener::SkipPauseRequest InspectorDebuggerAgent::shouldSkipStepPause(RefPtr<JavaScriptCallFrame>& topFrame) 477 { 478 if (m_cachedSkipStackRegExp) { 479 String scriptUrl = scriptURL(topFrame.get()); 480 if (!scriptUrl.isEmpty() && m_cachedSkipStackRegExp->match(scriptUrl) != -1) { 481 if (m_skipStepInCount > 0) { 482 --m_skipStepInCount; 483 return ScriptDebugListener::StepInto; 484 } 485 return ScriptDebugListener::StepOut; 486 } 487 } 488 return ScriptDebugListener::NoSkip; 489 } 490 491 PassRefPtr<TypeBuilder::Debugger::Location> InspectorDebuggerAgent::resolveBreakpoint(const String& breakpointId, const String& scriptId, const ScriptBreakpoint& breakpoint, BreakpointSource source) 492 { 493 ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId); 494 if (scriptIterator == m_scripts.end()) 495 return 0; 496 Script& script = scriptIterator->value; 497 if (breakpoint.lineNumber < script.startLine || script.endLine < breakpoint.lineNumber) 498 return 0; 499 500 int actualLineNumber; 501 int actualColumnNumber; 502 String debugServerBreakpointId = scriptDebugServer().setBreakpoint(scriptId, breakpoint, &actualLineNumber, &actualColumnNumber, false); 503 if (debugServerBreakpointId.isEmpty()) 504 return 0; 505 506 m_serverBreakpoints.set(debugServerBreakpointId, std::make_pair(breakpointId, source)); 507 508 BreakpointIdToDebugServerBreakpointIdsMap::iterator debugServerBreakpointIdsIterator = m_breakpointIdToDebugServerBreakpointIds.find(breakpointId); 509 if (debugServerBreakpointIdsIterator == m_breakpointIdToDebugServerBreakpointIds.end()) 510 debugServerBreakpointIdsIterator = m_breakpointIdToDebugServerBreakpointIds.set(breakpointId, Vector<String>()).iterator; 511 debugServerBreakpointIdsIterator->value.append(debugServerBreakpointId); 512 513 RefPtr<TypeBuilder::Debugger::Location> location = TypeBuilder::Debugger::Location::create() 514 .setScriptId(scriptId) 515 .setLineNumber(actualLineNumber); 516 location->setColumnNumber(actualColumnNumber); 517 return location; 518 } 519 520 static PassRefPtr<JSONObject> scriptToInspectorObject(ScriptObject scriptObject) 521 { 522 if (scriptObject.hasNoValue()) 523 return 0; 524 RefPtr<JSONValue> value = scriptObject.toJSONValue(scriptObject.scriptState()); 525 if (!value) 526 return 0; 527 return value->asObject(); 528 } 529 530 void InspectorDebuggerAgent::searchInContent(ErrorString* error, const String& scriptId, const String& query, const bool* const optionalCaseSensitive, const bool* const optionalIsRegex, RefPtr<Array<WebCore::TypeBuilder::Page::SearchMatch> >& results) 531 { 532 bool isRegex = optionalIsRegex ? *optionalIsRegex : false; 533 bool caseSensitive = optionalCaseSensitive ? *optionalCaseSensitive : false; 534 535 ScriptsMap::iterator it = m_scripts.find(scriptId); 536 if (it != m_scripts.end()) 537 results = ContentSearchUtils::searchInTextByLines(it->value.source, query, caseSensitive, isRegex); 538 else 539 *error = "No script for id: " + scriptId; 540 } 541 542 void InspectorDebuggerAgent::setScriptSource(ErrorString* error, RefPtr<TypeBuilder::Debugger::SetScriptSourceError>& errorData, const String& scriptId, const String& newContent, const bool* const preview, RefPtr<Array<TypeBuilder::Debugger::CallFrame> >& newCallFrames, RefPtr<JSONObject>& result) 543 { 544 bool previewOnly = preview && *preview; 545 ScriptObject resultObject; 546 if (!scriptDebugServer().setScriptSource(scriptId, newContent, previewOnly, error, errorData, &m_currentCallStack, &resultObject)) 547 return; 548 newCallFrames = currentCallFrames(); 549 RefPtr<JSONObject> object = scriptToInspectorObject(resultObject); 550 if (object) 551 result = object; 552 } 553 void InspectorDebuggerAgent::restartFrame(ErrorString* errorString, const String& callFrameId, RefPtr<Array<TypeBuilder::Debugger::CallFrame> >& newCallFrames, RefPtr<JSONObject>& result) 554 { 555 if (!isPaused() || m_currentCallStack.isNull()) { 556 *errorString = "Attempt to access callframe when debugger is not on pause"; 557 return; 558 } 559 InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(callFrameId); 560 if (injectedScript.hasNoValue()) { 561 *errorString = "Inspected frame has gone"; 562 return; 563 } 564 565 injectedScript.restartFrame(errorString, m_currentCallStack, callFrameId, &result); 566 scriptDebugServer().updateCallStack(&m_currentCallStack); 567 newCallFrames = currentCallFrames(); 568 } 569 570 void InspectorDebuggerAgent::getScriptSource(ErrorString* error, const String& scriptId, String* scriptSource) 571 { 572 ScriptsMap::iterator it = m_scripts.find(scriptId); 573 if (it != m_scripts.end()) 574 *scriptSource = it->value.source; 575 else 576 *error = "No script for id: " + scriptId; 577 } 578 579 void InspectorDebuggerAgent::getFunctionDetails(ErrorString* errorString, const String& functionId, RefPtr<TypeBuilder::Debugger::FunctionDetails>& details) 580 { 581 InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(functionId); 582 if (injectedScript.hasNoValue()) { 583 *errorString = "Function object id is obsolete"; 584 return; 585 } 586 injectedScript.getFunctionDetails(errorString, functionId, &details); 587 } 588 589 void InspectorDebuggerAgent::schedulePauseOnNextStatement(InspectorFrontend::Debugger::Reason::Enum breakReason, PassRefPtr<JSONObject> data) 590 { 591 if (m_javaScriptPauseScheduled) 592 return; 593 m_breakReason = breakReason; 594 m_breakAuxData = data; 595 scriptDebugServer().setPauseOnNextStatement(true); 596 } 597 598 void InspectorDebuggerAgent::cancelPauseOnNextStatement() 599 { 600 if (m_javaScriptPauseScheduled) 601 return; 602 clearBreakDetails(); 603 scriptDebugServer().setPauseOnNextStatement(false); 604 } 605 606 void InspectorDebuggerAgent::didFireTimer() 607 { 608 cancelPauseOnNextStatement(); 609 } 610 611 void InspectorDebuggerAgent::didHandleEvent() 612 { 613 cancelPauseOnNextStatement(); 614 } 615 616 void InspectorDebuggerAgent::pause(ErrorString*) 617 { 618 if (m_javaScriptPauseScheduled) 619 return; 620 clearBreakDetails(); 621 scriptDebugServer().setPauseOnNextStatement(true); 622 m_javaScriptPauseScheduled = true; 623 } 624 625 void InspectorDebuggerAgent::resume(ErrorString* errorString) 626 { 627 if (!assertPaused(errorString)) 628 return; 629 m_injectedScriptManager->releaseObjectGroup(InspectorDebuggerAgent::backtraceObjectGroup); 630 scriptDebugServer().continueProgram(); 631 } 632 633 void InspectorDebuggerAgent::stepOver(ErrorString* errorString) 634 { 635 if (!assertPaused(errorString)) 636 return; 637 m_injectedScriptManager->releaseObjectGroup(InspectorDebuggerAgent::backtraceObjectGroup); 638 scriptDebugServer().stepOverStatement(); 639 } 640 641 void InspectorDebuggerAgent::stepInto(ErrorString* errorString) 642 { 643 if (!assertPaused(errorString)) 644 return; 645 m_injectedScriptManager->releaseObjectGroup(InspectorDebuggerAgent::backtraceObjectGroup); 646 scriptDebugServer().stepIntoStatement(); 647 if (m_listener) 648 m_listener->stepInto(); 649 } 650 651 void InspectorDebuggerAgent::stepOut(ErrorString* errorString) 652 { 653 if (!assertPaused(errorString)) 654 return; 655 m_injectedScriptManager->releaseObjectGroup(InspectorDebuggerAgent::backtraceObjectGroup); 656 scriptDebugServer().stepOutOfFunction(); 657 } 658 659 void InspectorDebuggerAgent::setPauseOnExceptions(ErrorString* errorString, const String& stringPauseState) 660 { 661 ScriptDebugServer::PauseOnExceptionsState pauseState; 662 if (stringPauseState == "none") 663 pauseState = ScriptDebugServer::DontPauseOnExceptions; 664 else if (stringPauseState == "all") 665 pauseState = ScriptDebugServer::PauseOnAllExceptions; 666 else if (stringPauseState == "uncaught") 667 pauseState = ScriptDebugServer::PauseOnUncaughtExceptions; 668 else { 669 *errorString = "Unknown pause on exceptions mode: " + stringPauseState; 670 return; 671 } 672 setPauseOnExceptionsImpl(errorString, pauseState); 673 } 674 675 void InspectorDebuggerAgent::setPauseOnExceptionsImpl(ErrorString* errorString, int pauseState) 676 { 677 scriptDebugServer().setPauseOnExceptionsState(static_cast<ScriptDebugServer::PauseOnExceptionsState>(pauseState)); 678 if (scriptDebugServer().pauseOnExceptionsState() != pauseState) 679 *errorString = "Internal error. Could not change pause on exceptions state"; 680 else 681 m_state->setLong(DebuggerAgentState::pauseOnExceptionsState, pauseState); 682 } 683 684 void InspectorDebuggerAgent::evaluateOnCallFrame(ErrorString* errorString, const String& callFrameId, const String& expression, const String* const objectGroup, const bool* const includeCommandLineAPI, const bool* const doNotPauseOnExceptionsAndMuteConsole, const bool* const returnByValue, const bool* generatePreview, RefPtr<TypeBuilder::Runtime::RemoteObject>& result, TypeBuilder::OptOutput<bool>* wasThrown) 685 { 686 if (!isPaused() || m_currentCallStack.isNull()) { 687 *errorString = "Attempt to access callframe when debugger is not on pause"; 688 return; 689 } 690 InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(callFrameId); 691 if (injectedScript.hasNoValue()) { 692 *errorString = "Inspected frame has gone"; 693 return; 694 } 695 696 ScriptDebugServer::PauseOnExceptionsState previousPauseOnExceptionsState = scriptDebugServer().pauseOnExceptionsState(); 697 if (doNotPauseOnExceptionsAndMuteConsole ? *doNotPauseOnExceptionsAndMuteConsole : false) { 698 if (previousPauseOnExceptionsState != ScriptDebugServer::DontPauseOnExceptions) 699 scriptDebugServer().setPauseOnExceptionsState(ScriptDebugServer::DontPauseOnExceptions); 700 muteConsole(); 701 } 702 703 injectedScript.evaluateOnCallFrame(errorString, m_currentCallStack, callFrameId, expression, objectGroup ? *objectGroup : "", includeCommandLineAPI ? *includeCommandLineAPI : false, returnByValue ? *returnByValue : false, generatePreview ? *generatePreview : false, &result, wasThrown); 704 705 if (doNotPauseOnExceptionsAndMuteConsole ? *doNotPauseOnExceptionsAndMuteConsole : false) { 706 unmuteConsole(); 707 if (scriptDebugServer().pauseOnExceptionsState() != previousPauseOnExceptionsState) 708 scriptDebugServer().setPauseOnExceptionsState(previousPauseOnExceptionsState); 709 } 710 } 711 712 void InspectorDebuggerAgent::compileScript(ErrorString* errorString, const String& expression, const String& sourceURL, TypeBuilder::OptOutput<ScriptId>* scriptId, TypeBuilder::OptOutput<String>* syntaxErrorMessage) 713 { 714 InjectedScript injectedScript = injectedScriptForEval(errorString, 0); 715 if (injectedScript.hasNoValue()) { 716 *errorString = "Inspected frame has gone"; 717 return; 718 } 719 720 String scriptIdValue; 721 String exceptionMessage; 722 scriptDebugServer().compileScript(injectedScript.scriptState(), expression, sourceURL, &scriptIdValue, &exceptionMessage); 723 if (!scriptIdValue && !exceptionMessage) { 724 *errorString = "Script compilation failed"; 725 return; 726 } 727 *syntaxErrorMessage = exceptionMessage; 728 *scriptId = scriptIdValue; 729 } 730 731 void InspectorDebuggerAgent::runScript(ErrorString* errorString, const ScriptId& scriptId, const int* executionContextId, const String* const objectGroup, const bool* const doNotPauseOnExceptionsAndMuteConsole, RefPtr<TypeBuilder::Runtime::RemoteObject>& result, TypeBuilder::OptOutput<bool>* wasThrown) 732 { 733 InjectedScript injectedScript = injectedScriptForEval(errorString, executionContextId); 734 if (injectedScript.hasNoValue()) { 735 *errorString = "Inspected frame has gone"; 736 return; 737 } 738 739 ScriptDebugServer::PauseOnExceptionsState previousPauseOnExceptionsState = scriptDebugServer().pauseOnExceptionsState(); 740 if (doNotPauseOnExceptionsAndMuteConsole && *doNotPauseOnExceptionsAndMuteConsole) { 741 if (previousPauseOnExceptionsState != ScriptDebugServer::DontPauseOnExceptions) 742 scriptDebugServer().setPauseOnExceptionsState(ScriptDebugServer::DontPauseOnExceptions); 743 muteConsole(); 744 } 745 746 ScriptValue value; 747 bool wasThrownValue; 748 String exceptionMessage; 749 scriptDebugServer().runScript(injectedScript.scriptState(), scriptId, &value, &wasThrownValue, &exceptionMessage); 750 *wasThrown = wasThrownValue; 751 if (value.hasNoValue()) { 752 *errorString = "Script execution failed"; 753 return; 754 } 755 result = injectedScript.wrapObject(value, objectGroup ? *objectGroup : ""); 756 if (wasThrownValue) 757 result->setDescription(exceptionMessage); 758 759 if (doNotPauseOnExceptionsAndMuteConsole && *doNotPauseOnExceptionsAndMuteConsole) { 760 unmuteConsole(); 761 if (scriptDebugServer().pauseOnExceptionsState() != previousPauseOnExceptionsState) 762 scriptDebugServer().setPauseOnExceptionsState(previousPauseOnExceptionsState); 763 } 764 } 765 766 void InspectorDebuggerAgent::setOverlayMessage(ErrorString*, const String*) 767 { 768 } 769 770 void InspectorDebuggerAgent::setVariableValue(ErrorString* errorString, int scopeNumber, const String& variableName, const RefPtr<JSONObject>& newValue, const String* callFrameId, const String* functionObjectId) 771 { 772 InjectedScript injectedScript; 773 if (callFrameId) { 774 if (!isPaused() || m_currentCallStack.isNull()) { 775 *errorString = "Attempt to access callframe when debugger is not on pause"; 776 return; 777 } 778 injectedScript = m_injectedScriptManager->injectedScriptForObjectId(*callFrameId); 779 if (injectedScript.hasNoValue()) { 780 *errorString = "Inspected frame has gone"; 781 return; 782 } 783 } else if (functionObjectId) { 784 injectedScript = m_injectedScriptManager->injectedScriptForObjectId(*functionObjectId); 785 if (injectedScript.hasNoValue()) { 786 *errorString = "Function object id cannot be resolved"; 787 return; 788 } 789 } else { 790 *errorString = "Either call frame or function object must be specified"; 791 return; 792 } 793 String newValueString = newValue->toJSONString(); 794 795 injectedScript.setVariableValue(errorString, m_currentCallStack, callFrameId, functionObjectId, scopeNumber, variableName, newValueString); 796 } 797 798 void InspectorDebuggerAgent::skipStackFrames(ErrorString* errorString, const String* pattern) 799 { 800 OwnPtr<RegularExpression> compiled; 801 String patternValue = pattern ? *pattern : ""; 802 if (!patternValue.isEmpty()) { 803 compiled = compileSkipCallFramePattern(patternValue); 804 if (!compiled) { 805 *errorString = "Invalid regular expression"; 806 return; 807 } 808 } 809 m_state->setString(DebuggerAgentState::skipStackPattern, patternValue); 810 m_cachedSkipStackRegExp = compiled.release(); 811 } 812 813 void InspectorDebuggerAgent::scriptExecutionBlockedByCSP(const String& directiveText) 814 { 815 if (scriptDebugServer().pauseOnExceptionsState() != ScriptDebugServer::DontPauseOnExceptions) { 816 RefPtr<JSONObject> directive = JSONObject::create(); 817 directive->setString("directiveText", directiveText); 818 breakProgram(InspectorFrontend::Debugger::Reason::CSPViolation, directive.release()); 819 } 820 } 821 822 PassRefPtr<Array<TypeBuilder::Debugger::CallFrame> > InspectorDebuggerAgent::currentCallFrames() 823 { 824 if (!m_pausedScriptState) 825 return Array<TypeBuilder::Debugger::CallFrame>::create(); 826 InjectedScript injectedScript = m_injectedScriptManager->injectedScriptFor(m_pausedScriptState); 827 if (injectedScript.hasNoValue()) { 828 ASSERT_NOT_REACHED(); 829 return Array<TypeBuilder::Debugger::CallFrame>::create(); 830 } 831 return injectedScript.wrapCallFrames(m_currentCallStack); 832 } 833 834 String InspectorDebuggerAgent::sourceMapURLForScript(const Script& script) 835 { 836 bool deprecated; 837 String sourceMapURL = ContentSearchUtils::findSourceMapURL(script.source, ContentSearchUtils::JavaScriptMagicComment, &deprecated); 838 if (!sourceMapURL.isEmpty()) { 839 // FIXME: add deprecated console message here. 840 return sourceMapURL; 841 } 842 843 if (script.url.isEmpty()) 844 return String(); 845 846 InspectorPageAgent* pageAgent = m_instrumentingAgents->inspectorPageAgent(); 847 if (!pageAgent) 848 return String(); 849 return pageAgent->resourceSourceMapURL(script.url); 850 } 851 852 // JavaScriptDebugListener functions 853 854 void InspectorDebuggerAgent::didParseSource(const String& scriptId, const Script& script) 855 { 856 // Don't send script content to the front end until it's really needed. 857 const bool* isContentScript = script.isContentScript ? &script.isContentScript : 0; 858 String sourceMapURL = sourceMapURLForScript(script); 859 String* sourceMapURLParam = sourceMapURL.isNull() ? 0 : &sourceMapURL; 860 String sourceURL; 861 if (!script.startLine && !script.startColumn) { 862 bool deprecated; 863 sourceURL = ContentSearchUtils::findSourceURL(script.source, ContentSearchUtils::JavaScriptMagicComment, &deprecated); 864 // FIXME: add deprecated console message here. 865 } 866 bool hasSourceURL = !sourceURL.isEmpty(); 867 String scriptURL = hasSourceURL ? sourceURL : script.url; 868 bool* hasSourceURLParam = hasSourceURL ? &hasSourceURL : 0; 869 m_frontend->scriptParsed(scriptId, scriptURL, script.startLine, script.startColumn, script.endLine, script.endColumn, isContentScript, sourceMapURLParam, hasSourceURLParam); 870 871 m_scripts.set(scriptId, script); 872 873 if (scriptURL.isEmpty()) 874 return; 875 876 RefPtr<JSONObject> breakpointsCookie = m_state->getObject(DebuggerAgentState::javaScriptBreakpoints); 877 for (JSONObject::iterator it = breakpointsCookie->begin(); it != breakpointsCookie->end(); ++it) { 878 RefPtr<JSONObject> breakpointObject = it->value->asObject(); 879 bool isAntibreakpoint; 880 breakpointObject->getBoolean(DebuggerAgentState::isAnti, &isAntibreakpoint); 881 if (isAntibreakpoint) 882 continue; 883 bool isRegex; 884 breakpointObject->getBoolean(DebuggerAgentState::isRegex, &isRegex); 885 String url; 886 breakpointObject->getString(DebuggerAgentState::url, &url); 887 if (!matches(scriptURL, url, isRegex)) 888 continue; 889 ScriptBreakpoint breakpoint; 890 breakpointObject->getNumber(DebuggerAgentState::lineNumber, &breakpoint.lineNumber); 891 breakpointObject->getNumber(DebuggerAgentState::columnNumber, &breakpoint.columnNumber); 892 breakpointObject->getString(DebuggerAgentState::condition, &breakpoint.condition); 893 RefPtr<TypeBuilder::Debugger::Location> location = resolveBreakpoint(it->key, scriptId, breakpoint, UserBreakpointSource); 894 if (location) 895 m_frontend->breakpointResolved(it->key, location); 896 } 897 } 898 899 void InspectorDebuggerAgent::failedToParseSource(const String& url, const String& data, int firstLine, int errorLine, const String& errorMessage) 900 { 901 m_frontend->scriptFailedToParse(url, data, firstLine, errorLine, errorMessage); 902 } 903 904 void InspectorDebuggerAgent::didPause(ScriptState* scriptState, const ScriptValue& callFrames, const ScriptValue& exception, const Vector<String>& hitBreakpoints) 905 { 906 ASSERT(scriptState && !m_pausedScriptState); 907 m_pausedScriptState = scriptState; 908 m_currentCallStack = callFrames; 909 910 m_skipStepInCount = numberOfStepsBeforeStepOut; 911 912 if (!exception.hasNoValue()) { 913 InjectedScript injectedScript = m_injectedScriptManager->injectedScriptFor(scriptState); 914 if (!injectedScript.hasNoValue()) { 915 m_breakReason = InspectorFrontend::Debugger::Reason::Exception; 916 m_breakAuxData = injectedScript.wrapObject(exception, "backtrace")->openAccessors(); 917 // m_breakAuxData might be null after this. 918 } 919 } 920 921 RefPtr<Array<String> > hitBreakpointIds = Array<String>::create(); 922 923 for (Vector<String>::const_iterator i = hitBreakpoints.begin(); i != hitBreakpoints.end(); ++i) { 924 DebugServerBreakpointToBreakpointIdAndSourceMap::iterator breakpointIterator = m_serverBreakpoints.find(*i); 925 if (breakpointIterator != m_serverBreakpoints.end()) { 926 const String& localId = breakpointIterator->value.first; 927 hitBreakpointIds->addItem(localId); 928 929 BreakpointSource source = breakpointIterator->value.second; 930 if (m_breakReason == InspectorFrontend::Debugger::Reason::Other && source == DebugCommandBreakpointSource) 931 m_breakReason = InspectorFrontend::Debugger::Reason::DebugCommand; 932 } 933 } 934 935 m_frontend->paused(currentCallFrames(), m_breakReason, m_breakAuxData, hitBreakpointIds); 936 m_javaScriptPauseScheduled = false; 937 938 if (!m_continueToLocationBreakpointId.isEmpty()) { 939 scriptDebugServer().removeBreakpoint(m_continueToLocationBreakpointId); 940 m_continueToLocationBreakpointId = ""; 941 } 942 if (m_listener) 943 m_listener->didPause(); 944 } 945 946 void InspectorDebuggerAgent::didContinue() 947 { 948 m_pausedScriptState = 0; 949 m_currentCallStack = ScriptValue(); 950 clearBreakDetails(); 951 m_frontend->resumed(); 952 } 953 954 bool InspectorDebuggerAgent::canBreakProgram() 955 { 956 return scriptDebugServer().canBreakProgram(); 957 } 958 959 void InspectorDebuggerAgent::breakProgram(InspectorFrontend::Debugger::Reason::Enum breakReason, PassRefPtr<JSONObject> data) 960 { 961 m_breakReason = breakReason; 962 m_breakAuxData = data; 963 scriptDebugServer().breakProgram(); 964 } 965 966 void InspectorDebuggerAgent::clear() 967 { 968 m_pausedScriptState = 0; 969 m_currentCallStack = ScriptValue(); 970 m_scripts.clear(); 971 m_breakpointIdToDebugServerBreakpointIds.clear(); 972 m_continueToLocationBreakpointId = String(); 973 clearBreakDetails(); 974 m_javaScriptPauseScheduled = false; 975 ErrorString error; 976 setOverlayMessage(&error, 0); 977 } 978 979 bool InspectorDebuggerAgent::assertPaused(ErrorString* errorString) 980 { 981 if (!m_pausedScriptState) { 982 *errorString = "Can only perform operation while paused."; 983 return false; 984 } 985 return true; 986 } 987 988 void InspectorDebuggerAgent::clearBreakDetails() 989 { 990 m_breakReason = InspectorFrontend::Debugger::Reason::Other; 991 m_breakAuxData = 0; 992 } 993 994 void InspectorDebuggerAgent::setBreakpoint(const String& scriptId, int lineNumber, int columnNumber, BreakpointSource source, const String& condition) 995 { 996 String breakpointId = generateBreakpointId(scriptId, lineNumber, columnNumber, source); 997 ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition); 998 resolveBreakpoint(breakpointId, scriptId, breakpoint, source); 999 } 1000 1001 void InspectorDebuggerAgent::removeBreakpoint(const String& scriptId, int lineNumber, int columnNumber, BreakpointSource source) 1002 { 1003 removeBreakpoint(generateBreakpointId(scriptId, lineNumber, columnNumber, source)); 1004 } 1005 1006 void InspectorDebuggerAgent::reset() 1007 { 1008 m_scripts.clear(); 1009 m_breakpointIdToDebugServerBreakpointIds.clear(); 1010 if (m_frontend) 1011 m_frontend->globalObjectCleared(); 1012 } 1013 1014 } // namespace WebCore 1015 1016