2 * Copyright (C) 2010 Apple Inc. All rights reserved.
3 * Copyright (C) 2010-2011 Google Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
31 #include "InspectorDebuggerAgent.h"
33 #if ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(INSPECTOR)
34 #include "ContentSearchUtils.h"
35 #include "InjectedScript.h"
36 #include "InjectedScriptManager.h"
37 #include "InspectorFrontend.h"
38 #include "InspectorState.h"
39 #include "InspectorValues.h"
40 #include "InstrumentingAgents.h"
41 #include "RegularExpression.h"
42 #include "ScriptDebugServer.h"
43 #include "ScriptObject.h"
44 #include <wtf/text/WTFString.h>
48 namespace DebuggerAgentState {
49 static const char debuggerEnabled[] = "debuggerEnabled";
50 static const char javaScriptBreakpoints[] = "javaScriptBreakopints";
53 const char* InspectorDebuggerAgent::backtraceObjectGroup = "backtrace-object-group";
55 InspectorDebuggerAgent::InspectorDebuggerAgent(InstrumentingAgents* instrumentingAgents, InspectorState* inspectorState, InjectedScriptManager* injectedScriptManager)
56 : InspectorBaseAgent<InspectorDebuggerAgent>("Debugger", instrumentingAgents, inspectorState)
57 , m_injectedScriptManager(injectedScriptManager)
59 , m_pausedScriptState(0)
60 , m_javaScriptPauseScheduled(false)
63 // FIXME: make breakReason optional so that there was no need to init it with "other".
67 InspectorDebuggerAgent::~InspectorDebuggerAgent()
69 ASSERT(!m_instrumentingAgents->inspectorDebuggerAgent());
72 void InspectorDebuggerAgent::enable()
74 m_instrumentingAgents->setInspectorDebuggerAgent(this);
76 // FIXME(WK44513): breakpoints activated flag should be synchronized between all front-ends
77 scriptDebugServer().setBreakpointsActivated(true);
78 startListeningScriptDebugServer();
81 m_listener->debuggerWasEnabled();
84 void InspectorDebuggerAgent::disable()
86 m_state->setObject(DebuggerAgentState::javaScriptBreakpoints, InspectorObject::create());
87 m_instrumentingAgents->setInspectorDebuggerAgent(0);
89 stopListeningScriptDebugServer();
90 scriptDebugServer().clearBreakpoints();
94 m_listener->debuggerWasDisabled();
97 bool InspectorDebuggerAgent::enabled()
99 return m_state->getBoolean(DebuggerAgentState::debuggerEnabled);
102 void InspectorDebuggerAgent::getAgentCapabilities(InspectorArray* capabilities)
104 if (scriptDebugServer().canSetScriptSource())
105 capabilities->pushString(InspectorFrontend::Debugger::capabilitySetScriptSource);
108 void InspectorDebuggerAgent::enable(ErrorString*)
114 m_state->setBoolean(DebuggerAgentState::debuggerEnabled, true);
119 void InspectorDebuggerAgent::disable(ErrorString*)
125 m_state->setBoolean(DebuggerAgentState::debuggerEnabled, false);
128 void InspectorDebuggerAgent::restore()
131 m_frontend->globalObjectCleared();
136 void InspectorDebuggerAgent::setFrontend(InspectorFrontend* frontend)
138 m_frontend = frontend->debugger();
141 void InspectorDebuggerAgent::clearFrontend()
150 // FIXME: due to m_state->mute() hack in InspectorController, debuggerEnabled is actually set to false only
151 // in InspectorState, but not in cookie. That's why after navigation debuggerEnabled will be true,
152 // but after front-end re-open it will still be false.
153 m_state->setBoolean(DebuggerAgentState::debuggerEnabled, false);
156 void InspectorDebuggerAgent::setBreakpointsActive(ErrorString*, bool active)
159 scriptDebugServer().activateBreakpoints();
161 scriptDebugServer().deactivateBreakpoints();
164 void InspectorDebuggerAgent::didClearMainFrameWindowObject()
167 m_breakpointIdToDebugServerBreakpointIds.clear();
169 m_frontend->globalObjectCleared();
172 static PassRefPtr<InspectorObject> buildObjectForBreakpointCookie(const String& url, int lineNumber, int columnNumber, const String& condition, bool isRegex)
174 RefPtr<InspectorObject> breakpointObject = InspectorObject::create();
175 breakpointObject->setString("url", url);
176 breakpointObject->setNumber("lineNumber", lineNumber);
177 breakpointObject->setNumber("columnNumber", columnNumber);
178 breakpointObject->setString("condition", condition);
179 breakpointObject->setBoolean("isRegex", isRegex);
180 return breakpointObject;
183 static bool matches(const String& url, const String& pattern, bool isRegex)
186 RegularExpression regex(pattern, TextCaseSensitive);
187 return regex.match(url) != -1;
189 return url == pattern;
192 void InspectorDebuggerAgent::setBreakpointByUrl(ErrorString* errorString, int lineNumber, const String* const optionalURL, const String* const optionalURLRegex, const int* const optionalColumnNumber, const String* const optionalCondition, String* outBreakpointId, RefPtr<InspectorArray>* locations)
194 if (!optionalURL == !optionalURLRegex) {
195 *errorString = "Either url or urlRegex must be specified.";
199 String url = optionalURL ? *optionalURL : *optionalURLRegex;
200 int columnNumber = optionalColumnNumber ? *optionalColumnNumber : 0;
201 String condition = optionalCondition ? *optionalCondition : "";
202 bool isRegex = optionalURLRegex;
204 String breakpointId = (isRegex ? "/" + url + "/" : url) + ':' + String::number(lineNumber) + ':' + String::number(columnNumber);
205 RefPtr<InspectorObject> breakpointsCookie = m_state->getObject(DebuggerAgentState::javaScriptBreakpoints);
206 if (breakpointsCookie->find(breakpointId) != breakpointsCookie->end()) {
207 *errorString = "Breakpoint at specified location already exists.";
211 breakpointsCookie->setObject(breakpointId, buildObjectForBreakpointCookie(url, lineNumber, columnNumber, condition, isRegex));
212 m_state->setObject(DebuggerAgentState::javaScriptBreakpoints, breakpointsCookie);
214 ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition);
215 for (ScriptsMap::iterator it = m_scripts.begin(); it != m_scripts.end(); ++it) {
216 if (!matches(it->second.url, url, isRegex))
218 RefPtr<InspectorObject> location = resolveBreakpoint(breakpointId, it->first, breakpoint);
220 (*locations)->pushObject(location);
222 *outBreakpointId = breakpointId;
225 static bool parseLocation(ErrorString* errorString, RefPtr<InspectorObject> location, String* scriptId, int* lineNumber, int* columnNumber)
227 if (!location->getString("scriptId", scriptId) || !location->getNumber("lineNumber", lineNumber)) {
228 // FIXME: replace with input validation.
229 *errorString = "scriptId and lineNumber are required.";
233 location->getNumber("columnNumber", columnNumber);
237 void InspectorDebuggerAgent::setBreakpoint(ErrorString* errorString, PassRefPtr<InspectorObject> location, const String* const optionalCondition, String* outBreakpointId, RefPtr<InspectorObject>* actualLocation)
243 if (!parseLocation(errorString, location, &scriptId, &lineNumber, &columnNumber))
246 String condition = optionalCondition ? *optionalCondition : emptyString();
248 String breakpointId = scriptId + ':' + String::number(lineNumber) + ':' + String::number(columnNumber);
249 if (m_breakpointIdToDebugServerBreakpointIds.find(breakpointId) != m_breakpointIdToDebugServerBreakpointIds.end())
251 ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition);
252 *actualLocation = resolveBreakpoint(breakpointId, scriptId, breakpoint);
254 *outBreakpointId = breakpointId;
256 *errorString = "Could not resolve breakpoint";
259 void InspectorDebuggerAgent::removeBreakpoint(ErrorString*, const String& breakpointId)
261 RefPtr<InspectorObject> breakpointsCookie = m_state->getObject(DebuggerAgentState::javaScriptBreakpoints);
262 breakpointsCookie->remove(breakpointId);
263 m_state->setObject(DebuggerAgentState::javaScriptBreakpoints, breakpointsCookie);
265 BreakpointIdToDebugServerBreakpointIdsMap::iterator debugServerBreakpointIdsIterator = m_breakpointIdToDebugServerBreakpointIds.find(breakpointId);
266 if (debugServerBreakpointIdsIterator == m_breakpointIdToDebugServerBreakpointIds.end())
268 for (size_t i = 0; i < debugServerBreakpointIdsIterator->second.size(); ++i)
269 scriptDebugServer().removeBreakpoint(debugServerBreakpointIdsIterator->second[i]);
270 m_breakpointIdToDebugServerBreakpointIds.remove(debugServerBreakpointIdsIterator);
273 void InspectorDebuggerAgent::continueToLocation(ErrorString* errorString, PassRefPtr<InspectorObject> location)
275 if (!m_continueToLocationBreakpointId.isEmpty()) {
276 scriptDebugServer().removeBreakpoint(m_continueToLocationBreakpointId);
277 m_continueToLocationBreakpointId = "";
284 if (!parseLocation(errorString, location, &scriptId, &lineNumber, &columnNumber))
287 ScriptBreakpoint breakpoint(lineNumber, columnNumber, "");
288 m_continueToLocationBreakpointId = scriptDebugServer().setBreakpoint(scriptId, breakpoint, &lineNumber, &columnNumber);
292 PassRefPtr<InspectorObject> InspectorDebuggerAgent::resolveBreakpoint(const String& breakpointId, const String& scriptId, const ScriptBreakpoint& breakpoint)
294 ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId);
295 if (scriptIterator == m_scripts.end())
297 Script& script = scriptIterator->second;
298 if (breakpoint.lineNumber < script.startLine || script.endLine < breakpoint.lineNumber)
301 int actualLineNumber;
302 int actualColumnNumber;
303 String debugServerBreakpointId = scriptDebugServer().setBreakpoint(scriptId, breakpoint, &actualLineNumber, &actualColumnNumber);
304 if (debugServerBreakpointId.isEmpty())
307 BreakpointIdToDebugServerBreakpointIdsMap::iterator debugServerBreakpointIdsIterator = m_breakpointIdToDebugServerBreakpointIds.find(breakpointId);
308 if (debugServerBreakpointIdsIterator == m_breakpointIdToDebugServerBreakpointIds.end())
309 debugServerBreakpointIdsIterator = m_breakpointIdToDebugServerBreakpointIds.set(breakpointId, Vector<String>()).first;
310 debugServerBreakpointIdsIterator->second.append(debugServerBreakpointId);
312 RefPtr<InspectorObject> location = InspectorObject::create();
313 location->setString("scriptId", scriptId);
314 location->setNumber("lineNumber", actualLineNumber);
315 location->setNumber("columnNumber", actualColumnNumber);
319 static PassRefPtr<InspectorObject> scriptToInspectorObject(ScriptObject scriptObject)
321 if (scriptObject.hasNoValue())
323 RefPtr<InspectorValue> value = scriptObject.toInspectorValue(scriptObject.scriptState());
326 return value->asObject();
329 void InspectorDebuggerAgent::searchInContent(ErrorString* error, const String& scriptId, const String& query, const bool* const optionalCaseSensitive, const bool* const optionalIsRegex, RefPtr<InspectorArray>* results)
331 bool isRegex = optionalIsRegex ? *optionalIsRegex : false;
332 bool caseSensitive = optionalCaseSensitive ? *optionalCaseSensitive : false;
334 ScriptsMap::iterator it = m_scripts.find(scriptId);
335 if (it != m_scripts.end())
336 *results = ContentSearchUtils::searchInTextByLines(it->second.source, query, caseSensitive, isRegex);
338 *error = "No script for id: " + scriptId;
341 void InspectorDebuggerAgent::setScriptSource(ErrorString* error, const String& scriptId, const String& newContent, const bool* const preview, RefPtr<InspectorArray>* newCallFrames, RefPtr<InspectorObject>* result)
343 bool previewOnly = preview && *preview;
344 ScriptObject resultObject;
345 if (!scriptDebugServer().setScriptSource(scriptId, newContent, previewOnly, error, &m_currentCallStack, &resultObject))
347 *newCallFrames = currentCallFrames();
348 RefPtr<InspectorObject> object = scriptToInspectorObject(resultObject);
353 void InspectorDebuggerAgent::getScriptSource(ErrorString* error, const String& scriptId, String* scriptSource)
355 ScriptsMap::iterator it = m_scripts.find(scriptId);
356 if (it != m_scripts.end())
357 *scriptSource = it->second.source;
359 *error = "No script for id: " + scriptId;
362 void InspectorDebuggerAgent::getFunctionLocation(ErrorString* errorString, const String& functionId, RefPtr<InspectorObject>* location)
364 InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(functionId);
365 if (injectedScript.hasNoValue()) {
366 *errorString = "Inspected frame has gone";
369 injectedScript.getFunctionLocation(errorString, functionId, location);
372 void InspectorDebuggerAgent::schedulePauseOnNextStatement(const String& breakReason, PassRefPtr<InspectorObject> data)
374 if (m_javaScriptPauseScheduled)
376 m_breakReason = breakReason;
377 m_breakAuxData = data;
378 scriptDebugServer().setPauseOnNextStatement(true);
381 void InspectorDebuggerAgent::cancelPauseOnNextStatement()
383 if (m_javaScriptPauseScheduled)
386 scriptDebugServer().setPauseOnNextStatement(false);
389 void InspectorDebuggerAgent::pause(ErrorString*)
391 if (m_javaScriptPauseScheduled)
394 scriptDebugServer().setPauseOnNextStatement(true);
395 m_javaScriptPauseScheduled = true;
398 void InspectorDebuggerAgent::resume(ErrorString* errorString)
400 if (!assertPaused(errorString))
402 m_injectedScriptManager->releaseObjectGroup(InspectorDebuggerAgent::backtraceObjectGroup);
403 scriptDebugServer().continueProgram();
406 void InspectorDebuggerAgent::stepOver(ErrorString* errorString)
408 if (!assertPaused(errorString))
410 scriptDebugServer().stepOverStatement();
413 void InspectorDebuggerAgent::stepInto(ErrorString* errorString)
415 if (!assertPaused(errorString))
417 scriptDebugServer().stepIntoStatement();
420 void InspectorDebuggerAgent::stepOut(ErrorString* errorString)
422 if (!assertPaused(errorString))
424 scriptDebugServer().stepOutOfFunction();
427 void InspectorDebuggerAgent::setPauseOnExceptions(ErrorString* errorString, const String& stringPauseState)
429 ScriptDebugServer::PauseOnExceptionsState pauseState;
430 if (stringPauseState == "none")
431 pauseState = ScriptDebugServer::DontPauseOnExceptions;
432 else if (stringPauseState == "all")
433 pauseState = ScriptDebugServer::PauseOnAllExceptions;
434 else if (stringPauseState == "uncaught")
435 pauseState = ScriptDebugServer::PauseOnUncaughtExceptions;
437 *errorString = "Unknown pause on exceptions mode: " + stringPauseState;
440 scriptDebugServer().setPauseOnExceptionsState(static_cast<ScriptDebugServer::PauseOnExceptionsState>(pauseState));
441 if (scriptDebugServer().pauseOnExceptionsState() != pauseState)
442 *errorString = "Internal error. Could not change pause on exceptions state";
445 void InspectorDebuggerAgent::evaluateOnCallFrame(ErrorString* errorString, const String& callFrameId, const String& expression, const String* const objectGroup, const bool* const includeCommandLineAPI, const bool* const returnByValue, RefPtr<InspectorObject>* result, bool* wasThrown)
447 InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(callFrameId);
448 if (injectedScript.hasNoValue()) {
449 *errorString = "Inspected frame has gone";
452 injectedScript.evaluateOnCallFrame(errorString, m_currentCallStack, callFrameId, expression, objectGroup ? *objectGroup : "", includeCommandLineAPI ? *includeCommandLineAPI : false, returnByValue ? *returnByValue : false, result, wasThrown);
455 PassRefPtr<InspectorArray> InspectorDebuggerAgent::currentCallFrames()
457 if (!m_pausedScriptState)
458 return InspectorArray::create();
459 InjectedScript injectedScript = m_injectedScriptManager->injectedScriptFor(m_pausedScriptState);
460 if (injectedScript.hasNoValue()) {
461 ASSERT_NOT_REACHED();
462 return InspectorArray::create();
464 return injectedScript.wrapCallFrames(m_currentCallStack);
467 // JavaScriptDebugListener functions
469 void InspectorDebuggerAgent::didParseSource(const String& scriptId, const Script& script)
471 // Don't send script content to the front end until it's really needed.
472 m_frontend->scriptParsed(scriptId, script.url, script.startLine, script.startColumn, script.endLine, script.endColumn, script.isContentScript ? &script.isContentScript : 0);
474 m_scripts.set(scriptId, script);
476 if (script.url.isEmpty())
479 RefPtr<InspectorObject> breakpointsCookie = m_state->getObject(DebuggerAgentState::javaScriptBreakpoints);
480 for (InspectorObject::iterator it = breakpointsCookie->begin(); it != breakpointsCookie->end(); ++it) {
481 RefPtr<InspectorObject> breakpointObject = it->second->asObject();
483 breakpointObject->getBoolean("isRegex", &isRegex);
485 breakpointObject->getString("url", &url);
486 if (!matches(script.url, url, isRegex))
488 ScriptBreakpoint breakpoint;
489 breakpointObject->getNumber("lineNumber", &breakpoint.lineNumber);
490 breakpointObject->getNumber("columnNumber", &breakpoint.columnNumber);
491 breakpointObject->getString("condition", &breakpoint.condition);
492 RefPtr<InspectorObject> location = resolveBreakpoint(it->first, scriptId, breakpoint);
494 m_frontend->breakpointResolved(it->first, location);
498 void InspectorDebuggerAgent::failedToParseSource(const String& url, const String& data, int firstLine, int errorLine, const String& errorMessage)
500 m_frontend->scriptFailedToParse(url, data, firstLine, errorLine, errorMessage);
503 void InspectorDebuggerAgent::didPause(ScriptState* scriptState, const ScriptValue& callFrames, const ScriptValue& exception)
505 ASSERT(scriptState && !m_pausedScriptState);
506 m_pausedScriptState = scriptState;
507 m_currentCallStack = callFrames;
509 if (!exception.hasNoValue()) {
510 InjectedScript injectedScript = m_injectedScriptManager->injectedScriptFor(scriptState);
511 if (!injectedScript.hasNoValue()) {
512 m_breakReason = "exception";
513 m_breakAuxData = injectedScript.wrapObject(exception, "backtrace");
517 m_frontend->paused(currentCallFrames(), m_breakReason, m_breakAuxData);
518 m_javaScriptPauseScheduled = false;
520 if (!m_continueToLocationBreakpointId.isEmpty()) {
521 scriptDebugServer().removeBreakpoint(m_continueToLocationBreakpointId);
522 m_continueToLocationBreakpointId = "";
526 void InspectorDebuggerAgent::didContinue()
528 m_pausedScriptState = 0;
529 m_currentCallStack = ScriptValue();
531 m_frontend->resumed();
534 void InspectorDebuggerAgent::breakProgram(const String& breakReason, PassRefPtr<InspectorObject> data)
536 m_breakReason = breakReason;
537 m_breakAuxData = data;
538 scriptDebugServer().breakProgram();
541 void InspectorDebuggerAgent::clear()
543 m_pausedScriptState = 0;
544 m_currentCallStack = ScriptValue();
546 m_breakpointIdToDebugServerBreakpointIds.clear();
547 m_continueToLocationBreakpointId = String();
549 m_javaScriptPauseScheduled = false;
552 bool InspectorDebuggerAgent::assertPaused(ErrorString* errorString)
554 if (!m_pausedScriptState) {
555 *errorString = "Can only perform operation while paused.";
561 void InspectorDebuggerAgent::clearBreakDetails()
563 m_breakReason = "other";
567 } // namespace WebCore
569 #endif // ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(INSPECTOR)