diff --git a/engine/bindings/core/v8/DebuggerScript.js b/engine/bindings/core/v8/DebuggerScript.js new file mode 100644 index 00000000000..695586874c2 --- /dev/null +++ b/engine/bindings/core/v8/DebuggerScript.js @@ -0,0 +1,548 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +(function () { + +var DebuggerScript = {}; + +DebuggerScript.PauseOnExceptionsState = { + DontPauseOnExceptions: 0, + PauseOnAllExceptions: 1, + PauseOnUncaughtExceptions: 2 +}; + +DebuggerScript.ScopeInfoDetails = { + AllScopes: 0, + FastAsyncScopes: 1, + NoScopes: 2 +}; + +DebuggerScript._pauseOnExceptionsState = DebuggerScript.PauseOnExceptionsState.DontPauseOnExceptions; +Debug.clearBreakOnException(); +Debug.clearBreakOnUncaughtException(); + +DebuggerScript.getAfterCompileScript = function(eventData) +{ + return DebuggerScript._formatScript(eventData.script_.script_); +} + +DebuggerScript.getWorkerScripts = function() +{ + var result = []; + var scripts = Debug.scripts(); + for (var i = 0; i < scripts.length; ++i) { + var script = scripts[i]; + // Workers don't share same V8 heap now so there is no need to complicate stuff with + // the context id like we do to discriminate between scripts from different pages. + // However we need to filter out v8 native scripts. + if (script.context_data && script.context_data === "worker") + result.push(DebuggerScript._formatScript(script)); + } + return result; +} + +DebuggerScript.getFunctionScopes = function(fun) +{ + var mirror = MakeMirror(fun); + var count = mirror.scopeCount(); + if (count == 0) + return null; + var result = []; + for (var i = 0; i < count; i++) { + var scopeDetails = mirror.scope(i).details(); + result[i] = { + type: scopeDetails.type(), + object: DebuggerScript._buildScopeObject(scopeDetails.type(), scopeDetails.object()) + }; + } + return result; +} + +DebuggerScript.getCollectionEntries = function(object) +{ + var mirror = MakeMirror(object, true /* transient */); + if (mirror.isMap()) + return mirror.entries(); + if (mirror.isSet()) { + var result = []; + var values = mirror.values(); + for (var i = 0; i < values.length; ++i) + result.push({ value: values[i] }); + return result; + } +} + +DebuggerScript.getInternalProperties = function(value) +{ + var properties = ObjectMirror.GetInternalProperties(value); + var result = []; + for (var i = 0; i < properties.length; i++) { + var mirror = properties[i]; + result.push({ + name: mirror.name(), + value: mirror.value().value() + }); + } + return result; +} + +DebuggerScript.setFunctionVariableValue = function(functionValue, scopeIndex, variableName, newValue) +{ + var mirror = MakeMirror(functionValue); + if (!mirror.isFunction()) + throw new Error("Function value has incorrect type"); + return DebuggerScript._setScopeVariableValue(mirror, scopeIndex, variableName, newValue); +} + +DebuggerScript._setScopeVariableValue = function(scopeHolder, scopeIndex, variableName, newValue) +{ + var scopeMirror = scopeHolder.scope(scopeIndex); + if (!scopeMirror) + throw new Error("Incorrect scope index"); + scopeMirror.setVariableValue(variableName, newValue); + return undefined; +} + +DebuggerScript.getScripts = function(contextData) +{ + var result = []; + + if (!contextData) + return result; + var comma = contextData.indexOf(","); + if (comma === -1) + return result; + // Context data is a string in the following format: + // ("page"|"injected")"," + var idSuffix = contextData.substring(comma); // including the comma + + var scripts = Debug.scripts(); + for (var i = 0; i < scripts.length; ++i) { + var script = scripts[i]; + if (script.context_data && script.context_data.lastIndexOf(idSuffix) != -1) + result.push(DebuggerScript._formatScript(script)); + } + return result; +} + +DebuggerScript._formatScript = function(script) +{ + var lineEnds = script.line_ends; + var lineCount = lineEnds.length; + var endLine = script.line_offset + lineCount - 1; + var endColumn; + // V8 will not count last line if script source ends with \n. + if (script.source[script.source.length - 1] === '\n') { + endLine += 1; + endColumn = 0; + } else { + if (lineCount === 1) + endColumn = script.source.length + script.column_offset; + else + endColumn = script.source.length - (lineEnds[lineCount - 2] + 1); + } + + return { + id: script.id, + name: script.nameOrSourceURL(), + sourceURL: script.source_url, + sourceMappingURL: script.source_mapping_url, + source: script.source, + startLine: script.line_offset, + startColumn: script.column_offset, + endLine: endLine, + endColumn: endColumn, + isContentScript: !!script.context_data && script.context_data.indexOf("injected") == 0 + }; +} + +DebuggerScript.setBreakpoint = function(execState, info) +{ + var positionAlignment = info.interstatementLocation ? Debug.BreakPositionAlignment.BreakPosition : Debug.BreakPositionAlignment.Statement; + var breakId = Debug.setScriptBreakPointById(info.sourceID, info.lineNumber, info.columnNumber, info.condition, undefined, positionAlignment); + + var locations = Debug.findBreakPointActualLocations(breakId); + if (!locations.length) + return undefined; + info.lineNumber = locations[0].line; + info.columnNumber = locations[0].column; + return breakId.toString(); +} + +DebuggerScript.removeBreakpoint = function(execState, info) +{ + Debug.findBreakPoint(info.breakpointId, true); +} + +DebuggerScript.pauseOnExceptionsState = function() +{ + return DebuggerScript._pauseOnExceptionsState; +} + +DebuggerScript.setPauseOnExceptionsState = function(newState) +{ + DebuggerScript._pauseOnExceptionsState = newState; + + if (DebuggerScript.PauseOnExceptionsState.PauseOnAllExceptions === newState) + Debug.setBreakOnException(); + else + Debug.clearBreakOnException(); + + if (DebuggerScript.PauseOnExceptionsState.PauseOnUncaughtExceptions === newState) + Debug.setBreakOnUncaughtException(); + else + Debug.clearBreakOnUncaughtException(); +} + +DebuggerScript.frameCount = function(execState) +{ + return execState.frameCount(); +} + +DebuggerScript.currentCallFrame = function(execState, data) +{ + var maximumLimit = data >> 2; + var scopeDetailsLevel = data & 3; + + var frameCount = execState.frameCount(); + if (maximumLimit && maximumLimit < frameCount) + frameCount = maximumLimit; + var topFrame = undefined; + for (var i = frameCount - 1; i >= 0; i--) { + var frameMirror = execState.frame(i); + topFrame = DebuggerScript._frameMirrorToJSCallFrame(frameMirror, topFrame, scopeDetailsLevel); + } + return topFrame; +} + +DebuggerScript.currentCallFrameByIndex = function(execState, index) +{ + if (index < 0) + return undefined; + var frameCount = execState.frameCount(); + if (index >= frameCount) + return undefined; + return DebuggerScript._frameMirrorToJSCallFrame(execState.frame(index), undefined, DebuggerScript.ScopeInfoDetails.NoScopes); +} + +DebuggerScript.stepIntoStatement = function(execState) +{ + execState.prepareStep(Debug.StepAction.StepIn, 1); +} + +DebuggerScript.stepOverStatement = function(execState, callFrame) +{ + execState.prepareStep(Debug.StepAction.StepNext, 1); +} + +DebuggerScript.stepOutOfFunction = function(execState, callFrame) +{ + execState.prepareStep(Debug.StepAction.StepOut, 1); +} + +// Returns array in form: +// [ 0, ] in case of success +// or [ 1, , , , ] in case of compile error, numbers are 1-based. +// or throws exception with message. +DebuggerScript.liveEditScriptSource = function(scriptId, newSource, preview) +{ + var scripts = Debug.scripts(); + var scriptToEdit = null; + for (var i = 0; i < scripts.length; i++) { + if (scripts[i].id == scriptId) { + scriptToEdit = scripts[i]; + break; + } + } + if (!scriptToEdit) + throw("Script not found"); + + var changeLog = []; + try { + var result = Debug.LiveEdit.SetScriptSource(scriptToEdit, newSource, preview, changeLog); + return [0, result]; + } catch (e) { + if (e instanceof Debug.LiveEdit.Failure && "details" in e) { + var details = e.details; + if (details.type === "liveedit_compile_error") { + var startPosition = details.position.start; + return [1, String(e), String(details.syntaxErrorMessage), Number(startPosition.line), Number(startPosition.column)]; + } + } + throw e; + } +} + +DebuggerScript.clearBreakpoints = function(execState, info) +{ + Debug.clearAllBreakPoints(); +} + +DebuggerScript.setBreakpointsActivated = function(execState, info) +{ + Debug.debuggerFlags().breakPointsActive.setValue(info.enabled); +} + +DebuggerScript.getScriptSource = function(eventData) +{ + return eventData.script().source(); +} + +DebuggerScript.setScriptSource = function(eventData, source) +{ + if (eventData.script().data() === "injected-script") + return; + eventData.script().setSource(source); +} + +DebuggerScript.getScriptName = function(eventData) +{ + return eventData.script().script_.nameOrSourceURL(); +} + +DebuggerScript.getBreakpointNumbers = function(eventData) +{ + var breakpoints = eventData.breakPointsHit(); + var numbers = []; + if (!breakpoints) + return numbers; + + for (var i = 0; i < breakpoints.length; i++) { + var breakpoint = breakpoints[i]; + var scriptBreakPoint = breakpoint.script_break_point(); + numbers.push(scriptBreakPoint ? scriptBreakPoint.number() : breakpoint.number()); + } + return numbers; +} + +DebuggerScript.isEvalCompilation = function(eventData) +{ + var script = eventData.script(); + return (script.compilationType() === Debug.ScriptCompilationType.Eval); +} + +// NOTE: This function is performance critical, as it can be run on every +// statement that generates an async event (like addEventListener) to support +// asynchronous call stacks. Thus, when possible, initialize the data lazily. +DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror, callerFrame, scopeDetailsLevel) +{ + // Stuff that can not be initialized lazily (i.e. valid while paused with a valid break_id). + // The frameMirror and scopeMirror can be accessed only while paused on the debugger. + var frameDetails = frameMirror.details(); + + var funcObject = frameDetails.func(); + var sourcePosition = frameDetails.sourcePosition(); + var thisObject = frameDetails.receiver(); + + var isAtReturn = !!frameDetails.isAtReturn(); + var returnValue = isAtReturn ? frameDetails.returnValue() : undefined; + + var scopeMirrors = (scopeDetailsLevel === DebuggerScript.ScopeInfoDetails.NoScopes ? [] : frameMirror.allScopes(scopeDetailsLevel === DebuggerScript.ScopeInfoDetails.FastAsyncScopes)); + var scopeTypes = new Array(scopeMirrors.length); + var scopeObjects = new Array(scopeMirrors.length); + for (var i = 0; i < scopeMirrors.length; ++i) { + var scopeDetails = scopeMirrors[i].details(); + scopeTypes[i] = scopeDetails.type(); + scopeObjects[i] = scopeDetails.object(); + } + + // Calculated lazily. + var scopeChain; + var funcMirror; + var location; + + function lazyScopeChain() + { + if (!scopeChain) { + scopeChain = []; + for (var i = 0; i < scopeObjects.length; ++i) + scopeChain.push(DebuggerScript._buildScopeObject(scopeTypes[i], scopeObjects[i])); + scopeObjects = null; // Free for GC. + } + return scopeChain; + } + + function ensureFuncMirror() + { + if (!funcMirror) { + funcMirror = MakeMirror(funcObject); + if (!funcMirror.isFunction()) + funcMirror = new UnresolvedFunctionMirror(funcObject); + } + return funcMirror; + } + + function ensureLocation() + { + if (!location) { + var script = ensureFuncMirror().script(); + if (script) + location = script.locationFromPosition(sourcePosition, true); + if (!location) + location = { line: 0, column: 0 }; + } + return location; + } + + function line() + { + return ensureLocation().line; + } + + function column() + { + return ensureLocation().column; + } + + function sourceID() + { + var script = ensureFuncMirror().script(); + return script && script.id(); + } + + function scriptName() + { + var script = ensureFuncMirror().script(); + return script && script.name(); + } + + function functionName() + { + var func = ensureFuncMirror(); + if (!func.resolved()) + return undefined; + var displayName; + var valueMirror = func.property("displayName").value(); + if (valueMirror && valueMirror.isString()) + displayName = valueMirror.value(); + return displayName || func.name() || func.inferredName(); + } + + function evaluate(expression) + { + return frameMirror.evaluate(expression, false).value(); + } + + function restart() + { + return Debug.LiveEdit.RestartFrame(frameMirror); + } + + function setVariableValue(scopeNumber, variableName, newValue) + { + return DebuggerScript._setScopeVariableValue(frameMirror, scopeNumber, variableName, newValue); + } + + function stepInPositions() + { + var stepInPositionsV8 = frameMirror.stepInPositions(); + var stepInPositionsProtocol; + if (stepInPositionsV8) { + stepInPositionsProtocol = []; + var script = ensureFuncMirror().script(); + if (script) { + var scriptId = String(script.id()); + for (var i = 0; i < stepInPositionsV8.length; i++) { + var item = { + scriptId: scriptId, + lineNumber: stepInPositionsV8[i].position.line, + columnNumber: stepInPositionsV8[i].position.column + }; + stepInPositionsProtocol.push(item); + } + } + } + return JSON.stringify(stepInPositionsProtocol); + } + + return { + "sourceID": sourceID, + "line": line, + "column": column, + "scriptName": scriptName, + "functionName": functionName, + "thisObject": thisObject, + "scopeChain": lazyScopeChain, + "scopeType": scopeTypes, + "evaluate": evaluate, + "caller": callerFrame, + "restart": restart, + "setVariableValue": setVariableValue, + "stepInPositions": stepInPositions, + "isAtReturn": isAtReturn, + "returnValue": returnValue + }; +} + +DebuggerScript._buildScopeObject = function(scopeType, scopeObject) +{ + var result; + switch (scopeType) { + case ScopeType.Local: + case ScopeType.Closure: + case ScopeType.Catch: + // For transient objects we create a "persistent" copy that contains + // the same properties. + // Reset scope object prototype to null so that the proto properties + // don't appear in the local scope section. + result = { __proto__: null }; + var properties = MakeMirror(scopeObject, true /* transient */).properties(); + for (var j = 0; j < properties.length; j++) { + var name = properties[j].name(); + if (name.charAt(0) === ".") + continue; // Skip internal variables like ".arguments" + result[name] = properties[j].value_; + } + break; + case ScopeType.Global: + case ScopeType.With: + result = scopeObject; + break; + case ScopeType.Block: + // Unsupported yet. Mustn't be reachable. + break; + } + return result; +} + +DebuggerScript.getPromiseDetails = function(eventData) +{ + return { + "promise": eventData.promise().value(), + "parentPromise": eventData.parentPromise().value(), + "status": eventData.status() + }; +} + +// We never resolve Mirror by its handle so to avoid memory leaks caused by Mirrors in the cache we disable it. +ToggleMirrorCache(false); + +return DebuggerScript; +})(); diff --git a/engine/bindings/core/v8/PageScriptDebugServer.cpp b/engine/bindings/core/v8/PageScriptDebugServer.cpp new file mode 100644 index 00000000000..f5e5937ebf3 --- /dev/null +++ b/engine/bindings/core/v8/PageScriptDebugServer.cpp @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "bindings/core/v8/PageScriptDebugServer.h" + +#include "bindings/core/v8/DOMWrapperWorld.h" +#include "bindings/core/v8/ScriptController.h" +#include "bindings/core/v8/ScriptSourceCode.h" +#include "bindings/core/v8/V8Binding.h" +#include "bindings/core/v8/V8ScriptRunner.h" +#include "bindings/core/v8/V8Window.h" +#include "bindings/core/v8/WindowProxy.h" +#include "core/dom/ExecutionContext.h" +#include "core/frame/FrameConsole.h" +#include "core/frame/FrameHost.h" +#include "core/frame/LocalFrame.h" +#include "core/frame/UseCounter.h" +#include "core/inspector/InspectorTraceEvents.h" +#include "core/inspector/ScriptDebugListener.h" +#include "core/page/Page.h" +#include "wtf/OwnPtr.h" +#include "wtf/PassOwnPtr.h" +#include "wtf/StdLibExtras.h" +#include "wtf/TemporaryChange.h" +#include "wtf/text/StringBuilder.h" +#include "gin/modules/console.h" +#include "gin/converter.h" + +namespace blink { + +static LocalFrame* retrieveFrameWithGlobalObjectCheck(v8::Handle context) +{ + if (context.IsEmpty()) + return 0; + + // FIXME: This is a temporary hack for crbug.com/345014. + // Currently it's possible that V8 can trigger Debugger::ProcessDebugEvent for a context + // that is being initialized (i.e., inside Context::New() of the context). + // We should fix the V8 side so that it won't trigger the event for a half-baked context + // because there is no way in the embedder side to check if the context is half-baked or not. + if (isMainThread() && DOMWrapperWorld::windowIsBeingInitialized()) + return 0; + + v8::Handle global = V8Window::findInstanceInPrototypeChain(context->Global(), context->GetIsolate()); + if (global.IsEmpty()) + return 0; + + return toFrameIfNotDetached(context); +} + +void PageScriptDebugServer::setPreprocessorSource(const String& preprocessorSource) +{ + if (preprocessorSource.isEmpty()) + m_preprocessorSourceCode.clear(); + else + m_preprocessorSourceCode = adoptPtr(new ScriptSourceCode(preprocessorSource)); + m_scriptPreprocessor.clear(); +} + +PageScriptDebugServer& PageScriptDebugServer::shared() +{ + DEFINE_STATIC_LOCAL(PageScriptDebugServer, server, ()); + return server; +} + +v8::Isolate* PageScriptDebugServer::s_mainThreadIsolate = 0; + +void PageScriptDebugServer::setMainThreadIsolate(v8::Isolate* isolate) +{ + s_mainThreadIsolate = isolate; +} + +PageScriptDebugServer::PageScriptDebugServer() + : ScriptDebugServer(s_mainThreadIsolate) + , m_pausedPage(0) +{ +} + +PageScriptDebugServer::~PageScriptDebugServer() +{ +} + +void PageScriptDebugServer::addListener(ScriptDebugListener* listener, Page* page) +{ + ScriptController& scriptController = page->mainFrame()->script(); + + v8::HandleScope scope(m_isolate); + + if (!m_listenersMap.size()) { + v8::Debug::SetDebugEventListener(&PageScriptDebugServer::v8DebugEventCallback, v8::External::New(m_isolate, this)); + ensureDebuggerScriptCompiled(); + } + + v8::Local debuggerContext = v8::Debug::GetDebugContext(); + v8::Context::Scope contextScope(debuggerContext); + + v8::Local console = gin::Console::GetModule(m_isolate); + debuggerContext->Global()->Set(gin::StringToV8(m_isolate, "console"), console); + + v8::Local debuggerScript = m_debuggerScript.newLocal(m_isolate); + ASSERT(!debuggerScript->IsUndefined()); + m_listenersMap.set(page, listener); + + WindowProxy* windowProxy = scriptController.existingWindowProxy(DOMWrapperWorld::mainWorld()); + if (!windowProxy || !windowProxy->isContextInitialized()) + return; + v8::Local context = windowProxy->context(); + v8::Handle getScriptsFunction = v8::Local::Cast(debuggerScript->Get(v8AtomicString(m_isolate, "getScripts"))); + v8::Handle argv[] = { context->GetEmbedderData(0) }; + v8::Handle value = V8ScriptRunner::callInternalFunction(getScriptsFunction, debuggerScript, WTF_ARRAY_LENGTH(argv), argv, m_isolate); + if (value.IsEmpty()) + return; + ASSERT(!value->IsUndefined() && value->IsArray()); + v8::Handle scriptsArray = v8::Handle::Cast(value); + for (unsigned i = 0; i < scriptsArray->Length(); ++i) + dispatchDidParseSource(listener, v8::Handle::Cast(scriptsArray->Get(v8::Integer::New(m_isolate, i))), CompileSuccess); +} + +void PageScriptDebugServer::removeListener(ScriptDebugListener* listener, Page* page) +{ + if (!m_listenersMap.contains(page)) + return; + + if (m_pausedPage == page) + continueProgram(); + + m_listenersMap.remove(page); + + if (m_listenersMap.isEmpty()) { + discardDebuggerScript(); + v8::Debug::SetDebugEventListener(0); + // FIXME: Remove all breakpoints set by the agent. + } +} + +void PageScriptDebugServer::interruptAndRun(PassOwnPtr task) +{ + ScriptDebugServer::interruptAndRun(task, s_mainThreadIsolate); +} + +void PageScriptDebugServer::setClientMessageLoop(PassOwnPtr clientMessageLoop) +{ + m_clientMessageLoop = clientMessageLoop; +} + +void PageScriptDebugServer::compileScript(ScriptState* scriptState, const String& expression, const String& sourceURL, String* scriptId, String* exceptionDetailsText, int* lineNumber, int* columnNumber, RefPtr* stackTrace) +{ + ExecutionContext* executionContext = scriptState->executionContext(); + RefPtr protect = executionContext->executingWindow()->frame(); + ScriptDebugServer::compileScript(scriptState, expression, sourceURL, scriptId, exceptionDetailsText, lineNumber, columnNumber, stackTrace); + if (!scriptId->isNull()) + m_compiledScriptURLs.set(*scriptId, sourceURL); +} + +void PageScriptDebugServer::clearCompiledScripts() +{ + ScriptDebugServer::clearCompiledScripts(); + m_compiledScriptURLs.clear(); +} + +void PageScriptDebugServer::runScript(ScriptState* scriptState, const String& scriptId, ScriptValue* result, bool* wasThrown, String* exceptionDetailsText, int* lineNumber, int* columnNumber, RefPtr* stackTrace) +{ + String sourceURL = m_compiledScriptURLs.take(scriptId); + + ExecutionContext* executionContext = scriptState->executionContext(); + LocalFrame* frame = executionContext->executingWindow()->frame(); + TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "EvaluateScript", "data", InspectorEvaluateScriptEvent::data(frame, sourceURL, TextPosition::minimumPosition().m_line.oneBasedInt())); + TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"), "CallStack", TRACE_EVENT_SCOPE_PROCESS, "stack", InspectorCallStackEvent::currentCallStack()); + + RefPtr protect = frame; + ScriptDebugServer::runScript(scriptState, scriptId, result, wasThrown, exceptionDetailsText, lineNumber, columnNumber, stackTrace); + + TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "UpdateCounters", TRACE_EVENT_SCOPE_PROCESS, "data", InspectorUpdateCountersEvent::data()); +} + +ScriptDebugListener* PageScriptDebugServer::getDebugListenerForContext(v8::Handle context) +{ + v8::HandleScope scope(m_isolate); + LocalFrame* frame = retrieveFrameWithGlobalObjectCheck(context); + if (!frame) + return 0; + return m_listenersMap.get(frame->page()); +} + +void PageScriptDebugServer::runMessageLoopOnPause(v8::Handle context) +{ + v8::HandleScope scope(m_isolate); + LocalFrame* frame = retrieveFrameWithGlobalObjectCheck(context); + m_pausedPage = frame->page(); + + // Wait for continue or step command. + m_clientMessageLoop->run(m_pausedPage); + + // The listener may have been removed in the nested loop. + if (ScriptDebugListener* listener = m_listenersMap.get(m_pausedPage)) + listener->didContinue(); + + m_pausedPage = 0; +} + +void PageScriptDebugServer::quitMessageLoopOnPause() +{ + m_clientMessageLoop->quitNow(); +} + +void PageScriptDebugServer::preprocessBeforeCompile(const v8::Debug::EventDetails& eventDetails) +{ + v8::Handle eventContext = eventDetails.GetEventContext(); + LocalFrame* frame = retrieveFrameWithGlobalObjectCheck(eventContext); + if (!frame) + return; + + if (!canPreprocess(frame)) + return; + + v8::Handle eventData = eventDetails.GetEventData(); + v8::Local debugContext = v8::Debug::GetDebugContext(); + v8::Context::Scope contextScope(debugContext); + v8::TryCatch tryCatch; + // diff --git a/tests/inspector/page-agent-get-resource-expected.txt b/tests/inspector/page-agent-get-resource-expected.txt new file mode 100644 index 00000000000..f96104a9406 --- /dev/null +++ b/tests/inspector/page-agent-get-resource-expected.txt @@ -0,0 +1 @@ +Got message! diff --git a/viewer/document_view.cc b/viewer/document_view.cc index 0bfbeca61cc..49982990210 100644 --- a/viewer/document_view.cc +++ b/viewer/document_view.cc @@ -64,6 +64,8 @@ mojo::Target WebNavigationPolicyToNavigationTarget( } // namespace +static int s_next_debugger_id = 1; + DocumentView::DocumentView( mojo::URLResponsePtr response, mojo::ShellPtr shell, @@ -75,6 +77,7 @@ DocumentView::DocumentView( view_manager_client_factory_(shell_.get(), this), inspector_service_factory_(this), compositor_thread_(compositor_thread), + debugger_id_(s_next_debugger_id++), weak_factory_(this) { shell_.set_client(this); } @@ -191,8 +194,6 @@ void DocumentView::didCreateScriptContext(blink::WebLocalFrame* frame, v8::Handle context, int extensionGroup, int worldId) { - if (frame != web_view_->mainFrame()) - return; script_runner_.reset(new ScriptRunner(frame, context)); v8::Isolate* isolate = context->GetIsolate(); @@ -209,6 +210,10 @@ mojo::NavigatorHost* DocumentView::NavigatorHost() { return navigator_host_.get(); } +mojo::Shell* DocumentView::Shell() { + return shell_.get(); +} + void DocumentView::OnViewBoundsChanged(mojo::View* view, const mojo::Rect& old_bounds, const mojo::Rect& new_bounds) { diff --git a/viewer/document_view.h b/viewer/document_view.h index d52ce7913a3..1ef54835248 100644 --- a/viewer/document_view.h +++ b/viewer/document_view.h @@ -89,6 +89,7 @@ class DocumentView : public mojo::InterfaceImpl, // Services methods: mojo::NavigatorHost* NavigatorHost() override; + mojo::Shell* Shell() override; // ViewManagerDelegate methods: void OnEmbed( @@ -121,6 +122,7 @@ class DocumentView : public mojo::InterfaceImpl, scoped_ptr web_layer_tree_view_impl_; scoped_refptr compositor_thread_; scoped_ptr script_runner_; + int debugger_id_; base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(DocumentView); diff --git a/viewer/platform/platform_impl.cc b/viewer/platform/platform_impl.cc index 2315c4560d3..b9131677812 100644 --- a/viewer/platform/platform_impl.cc +++ b/viewer/platform/platform_impl.cc @@ -7,6 +7,9 @@ #include #include "base/debug/trace_event.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/path_service.h" #include "base/rand_util.h" #include "base/stl_util.h" #include "base/synchronization/waitable_event.h" @@ -235,4 +238,28 @@ void PlatformImpl::updateTraceEventDuration( category_group_enabled, name, traceEventHandle); } +// FIXME(sky): This is a horrible hack and makes sky only work when run +// inside its source tree! crbug.com/434513 +blink::WebData PlatformImpl::loadResource(const char* name) +{ + base::FilePath root_dir; + PathService::Get(base::DIR_SOURCE_ROOT, &root_dir); + base::FilePath engine_dir = root_dir.AppendASCII("sky").AppendASCII("engine"); + base::FilePath v8_dir = engine_dir.AppendASCII("bindings").AppendASCII("core").AppendASCII("v8"); + base::FilePath inspector_dir = engine_dir.AppendASCII("core").AppendASCII("inspector"); + + base::FilePath path; + if (std::string("InjectedScriptSource.js") == name) + path = inspector_dir.AppendASCII(name); + else if (std::string("DebuggerScript.js") == name) + path = v8_dir.AppendASCII(name); + else + CHECK(false); + + std::string buffer; + base::ReadFileToString(path, &buffer); + CHECK(!buffer.empty()); + return blink::WebData(buffer.data(), buffer.size()); +} + } // namespace sky diff --git a/viewer/platform/platform_impl.h b/viewer/platform/platform_impl.h index 9495ab477aa..78895b18d68 100644 --- a/viewer/platform/platform_impl.h +++ b/viewer/platform/platform_impl.h @@ -70,6 +70,8 @@ class PlatformImpl : public blink::Platform { const char* name, TraceEventHandle); + virtual blink::WebData loadResource(const char* name); + private: void SuspendSharedTimer(); void ResumeSharedTimer(); diff --git a/viewer/services/inspector_impl.cc b/viewer/services/inspector_impl.cc index 4c8d086f3ed..ca8059c7722 100644 --- a/viewer/services/inspector_impl.cc +++ b/viewer/services/inspector_impl.cc @@ -27,16 +27,17 @@ void InspectorServiceImpl::Inject() { if (!view_) return; - mojo::ServiceProviderPtr inpector_service_provider; + mojo::ServiceProviderPtr inspector_service_provider; view_->shell()->ConnectToApplication("mojo:sky_inspector_server", - GetProxy(&inpector_service_provider)); + GetProxy(&inspector_service_provider)); InspectorServerPtr inspector; - ConnectToService(inpector_service_provider.get(), &inspector); + mojo::ConnectToService(inspector_service_provider.get(), &inspector); inspector->Listen(9898, base::Bind(&Ignored)); // Listen drops existing agents/backends, wait before registering new ones. inspector.WaitForIncomingMethodCall(); view_->web_view()->injectModule("/sky/framework/inspector/inspector.sky"); + view_->web_view()->connectInspectorBackend(); } } // namespace sky