diff --git a/InjectedScript/DebuggerScript.js b/InjectedScript/DebuggerScript.js index 0bdef39..22483ed 100644 --- a/InjectedScript/DebuggerScript.js +++ b/InjectedScript/DebuggerScript.js @@ -54,21 +54,6 @@ 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); @@ -89,6 +74,31 @@ DebuggerScript.getFunctionScopes = function(fun) return result; } +DebuggerScript.getGeneratorObjectDetails = function(object) +{ + var mirror = MakeMirror(object, true /* transient */); + if (!mirror.isGenerator()) + return null; + var funcMirror = mirror.func(); + if (!funcMirror.resolved()) + return null; + var result = { + "function": funcMirror.value(), + "functionName": DebuggerScript._displayFunctionName(funcMirror) || "", + "status": mirror.status() + }; + var script = funcMirror.script(); + var location = mirror.sourceLocation() || funcMirror.sourceLocation(); + if (script && location) { + result["location"] = { + "scriptId": String(script.id()), + "lineNumber": location.line, + "columnNumber": location.column + }; + } + return result; +} + DebuggerScript.getCollectionEntries = function(object) { var mirror = MakeMirror(object, true /* transient */); @@ -103,20 +113,6 @@ DebuggerScript.getCollectionEntries = function(object) } } -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); @@ -134,24 +130,24 @@ DebuggerScript._setScopeVariableValue = function(scopeHolder, scopeIndex, variab return undefined; } -DebuggerScript.getScripts = function(contextData) +DebuggerScript.getScripts = function(contextGroupId) { 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(); + var contextDataPrefix = null; + if (contextGroupId) + contextDataPrefix = contextGroupId + ","; 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)); + if (contextDataPrefix) { + if (!script.context_data) + continue; + // Context data is a string in the following format: + // ","("page"|"injected"|"worker") + if (script.context_data.indexOf(contextDataPrefix) !== 0) + continue; + } + result.push(DebuggerScript._formatScript(script)); } return result; } @@ -183,7 +179,8 @@ DebuggerScript._formatScript = function(script) startColumn: script.column_offset, endLine: endLine, endColumn: endColumn, - isContentScript: !!script.context_data && script.context_data.indexOf("injected") == 0 + isContentScript: !!script.context_data && script.context_data.endsWith(",injected"), + isInternalScript: script.is_debugger_script }; } @@ -276,6 +273,11 @@ DebuggerScript.stepOutOfFunction = function(execState, callFrame) execState.prepareStep(Debug.StepAction.StepOut, 1); } +DebuggerScript.clearStepping = function() +{ + Debug.clearStepping(); +} + // Returns array in form: // [ 0, ] in case of success // or [ 1, , , , ] in case of compile error, numbers are 1-based. @@ -296,7 +298,7 @@ DebuggerScript.liveEditScriptSource = function(scriptId, newSource, preview) var changeLog = []; try { var result = Debug.LiveEdit.SetScriptSource(scriptToEdit, newSource, preview, changeLog); - return [0, result]; + return [0, result.stack_modified]; } catch (e) { if (e instanceof Debug.LiveEdit.Failure && "details" in e) { var details = e.details; @@ -357,6 +359,17 @@ DebuggerScript.isEvalCompilation = function(eventData) return (script.compilationType() === Debug.ScriptCompilationType.Eval); } +DebuggerScript._displayFunctionName = function(funcMirror) +{ + if (!funcMirror.resolved()) + return undefined + var displayName; + var valueMirror = funcMirror.property("displayName").value(); + if (valueMirror && valueMirror.isString()) + displayName = valueMirror.value(); + return displayName || funcMirror.name() || funcMirror.inferredName(); +} + // 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. @@ -458,14 +471,19 @@ DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror, callerFrame, sc 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(); + return DebuggerScript._displayFunctionName(ensureFuncMirror()); + } + + function functionLine() + { + var location = ensureFuncMirror().sourceLocation(); + return location ? location.line : 0; + } + + function functionColumn() + { + var location = ensureFuncMirror().sourceLocation(); + return location ? location.column : 0; } function evaluate(expression, scopeExtension) @@ -475,7 +493,7 @@ DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror, callerFrame, sc function restart() { - return Debug.LiveEdit.RestartFrame(frameMirror); + return frameMirror.restart(); } function setVariableValue(scopeNumber, variableName, newValue) @@ -511,6 +529,8 @@ DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror, callerFrame, sc "column": column, "scriptName": scriptName, "functionName": functionName, + "functionLine": functionLine, + "functionColumn": functionColumn, "thisObject": thisObject, "scopeChain": lazyScopeChain, "scopeType": lazyScopeTypes, diff --git a/InjectedScript/InjectedScriptHost.js b/InjectedScript/InjectedScriptHost.js index 38d2810..793b2d0 100644 --- a/InjectedScript/InjectedScriptHost.js +++ b/InjectedScript/InjectedScriptHost.js @@ -1,15 +1,82 @@ "use strict"; (function (binding, DebuggerScript) { - function InjectedScriptHost() {} - + var lastBoundObjectId = 0; + var idToWrappedObject = new Map(); + var idToObjectGroupName = new Map(); + var nameToObjectGroup = new Map(); + + function InjectedScriptHost() { + } + InjectedScriptHost.prototype = binding.InjectedScriptHost; + InjectedScriptHost.prototype.bind = function(value, groupName) { + if (lastBoundObjectId <= 0) + lastBoundObjectId = 1; + + var id = lastBoundObjectId++; + idToWrappedObject.set(id, value); + + if (id < 0) return; + if (groupName == null) return id; + + idToObjectGroupName.set(id, groupName); + + if (!nameToObjectGroup.has(groupName)) + nameToObjectGroup.set(groupName, [id]); + else + nameToObjectGroup.get(groupName).push(id); + + return id; + }; + + InjectedScriptHost.prototype.unbind = function(id) { + idToWrappedObject.delete(id); + idToObjectGroupName.delete(id); + }; + + InjectedScriptHost.prototype.releaseObject = function(objectId) { + var parsedObjectId; + try { + parsedObjectId = JSON.parse(objectId); + } catch (e) { return; } + + this.unbind(parsedObjectId.id); + }; + + InjectedScriptHost.prototype.releaseObjectGroup = function(groupName) { + if (!groupName) return; + + var group = nameToObjectGroup.get(groupName); + if (!group) return; + + group.forEach(function(id) { + this.unbind(id); + }, this); + + nameToObjectGroup.delete(groupName); + }; + + InjectedScriptHost.prototype.objectForId = function(id) { + if (!Number(id)) return; + return idToWrappedObject.get(id); + }; + + InjectedScriptHost.prototype.idToObjectGroupName = function(id) { + if (!Number(id)) return; + return idToObjectGroupName.get(id) || ''; + } + InjectedScriptHost.prototype.isHTMLAllCollection = function(object) { //We don't have `all` collection in NodeJS return false; }; + InjectedScriptHost.prototype.isDOMWrapper = function(object) { + return false; + }; + InjectedScriptHost.prototype.suppressWarningsAndCallFunction = function(func, receiver, args) { return this.callFunction(func, receiver, args); }; @@ -17,17 +84,21 @@ InjectedScriptHost.prototype.functionDetails = function(fun) { var details = this.functionDetailsWithoutScopes(fun); var scopes = DebuggerScript.getFunctionScopes(fun); - + if (scopes && scopes.length) { details.rawScopes = scopes; } - + return details; }; - InjectedScriptHost.prototype.getInternalProperties = function(value) { - return DebuggerScript.getInternalProperties(value); + InjectedScriptHost.prototype.generatorObjectDetails = function(object) { + return DebuggerScript.getGeneratorObjectDetails(object); }; - + + InjectedScriptHost.prototype.collectionEntries = function(object) { + return DebuggerScript.getCollectionEntries(object); + }; + return new InjectedScriptHost(); }); diff --git a/InjectedScript/JavaScriptCallFrame.js b/InjectedScript/JavaScriptCallFrame.js new file mode 100644 index 0000000..38aa38a --- /dev/null +++ b/InjectedScript/JavaScriptCallFrame.js @@ -0,0 +1,125 @@ +"use strict"; + +(function(binding) { + function JavaScriptCallFrame(proto) { + Object.defineProperty(this, 'proto', { + get: function() { return proto; } + }); + } + + JavaScriptCallFrame.prototype = binding.JavaScriptCallFrame; + + Object.defineProperties(JavaScriptCallFrame.prototype, { + setVariableValue: { + value: function(scopeNumber, variableName, resolvedValue) { + return this.proto.setVariableValue(scopeNumber, variableName, resolvedValue); + }, + configurable: true + }, + + caller: { + get: function() { + var caller = this.proto.caller; + if (!caller) return; + + return new JavaScriptCallFrame(caller); + }, + configurable: true + }, + + sourceID: { + get: function() { + return Number(this.proto.sourceID()); + }, + configurable: true + }, + + line: { + get: function() { + return Number(this.proto.line()); + }, + configurable: true + }, + + column: { + get: function() { + return Number(this.proto.column()); + }, + configurable: true + }, + + scriptName: { + get: function() { + return String(this.proto.scriptName()); + }, + configurable: true + }, + + functionName: { + get: function() { + return String(this.proto.functionName()); + }, + configurable: true + }, + + functionLine: { + get: function() { + return Number(this.proto.functionLine()); + }, + configurable: true + }, + + functionColumn: { + get: function() { + return Number(this.proto.functionColumn()); + }, + configurable: true + }, + + scopeChain: { + get: function() { + var scopeChain = this.proto.scopeChain(); + return scopeChain.slice(); + }, + configurable: true + }, + + scopeType: { + value: function(index) { + var scopeType = this.proto.scopeType(); + return Number(scopeType[index]); + }, + configurable: true + }, + + thisObject: { + get: function() { + return this.proto.thisObject; + }, + configurable: true + }, + + stepInPositions: { + get: function() { + return String(this.proto.stepInPositions()); + }, + configurable: true + }, + + isAtReturn: { + get: function() { + return Boolean(this.proto.isAtReturn); + }, + configurable: true + }, + + returnValue: { + get: function() { + return this.proto.returnValue; + }, + configurable: true + } + }); + + return JavaScriptCallFrame; +}); diff --git a/appveyor.yml b/appveyor.yml index 86c9a91..e6fd390 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,14 +5,16 @@ environment: secure: jJxKoyWputzRz2EjAT9i/vBzYt2+lcjKZ5D6O5TBaS9+anpYHn2XWbOut5dkOgh0 node_pre_gyp_region: eu-central-1 matrix: + - NODE_VERSION: 6 + platform: x64 + - NODE_VERSION: 6 + platform: x86 - NODE_VERSION: 5 platform: x64 - NODE_VERSION: 5 platform: x86 - NODE_VERSION: 4 platform: x64 - - NODE_VERSION: 4 - platform: x86 - NODE_VERSION: 0.12 platform: x64 - NODE_VERSION: 0.12 diff --git a/binding.gyp b/binding.gyp index 14a0c20..b775ad7 100644 --- a/binding.gyp +++ b/binding.gyp @@ -15,7 +15,8 @@ 'conditions' : [ ['node_next=="true"', { 'sources': [ - 'src/InjectedScriptHost.cc' + 'src/InjectedScriptHost.cc', + 'src/JavaScriptCallFrame.cc' ], 'defines': ['NODE_NEXT=1'] }] diff --git a/package.json b/package.json index 21da994..cd59c3f 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ }, "main": "v8-debug", "dependencies": { - "nan": "^2.0.4", + "nan": "^2.3.2", "node-pre-gyp": "^0.6.5" }, "devDependencies": { diff --git a/readme.md b/readme.md index b9788f7..e92ff03 100644 --- a/readme.md +++ b/readme.md @@ -11,6 +11,12 @@ This is a part of [node-inspector](http://github.com/node-inspector/node-inspect ``` npm install v8-debug ``` + +## Usage +```js +var debug = require('v8-debug')(); +``` + ## API ### registerCommand(name, callback) @@ -109,7 +115,7 @@ Experimental method for registering WebKit protocol handlers Simple console.log checking ```js -var debug = require('v8-debug'); +var debug = require('v8-debug')(); debug.registerEvent('console.log'); diff --git a/src/InjectedScriptHost.cc b/src/InjectedScriptHost.cc index 82839ef..aa2ce3c 100644 --- a/src/InjectedScriptHost.cc +++ b/src/InjectedScriptHost.cc @@ -4,7 +4,6 @@ #include "tools.h" using v8::Isolate; -using v8::Handle; using v8::Local; using v8::Value; using v8::Boolean; @@ -31,7 +30,7 @@ using Nan::EmptyString; using Nan::Utf8String; namespace nodex { - void InjectedScriptHost::Initialize(Handle target) { + void InjectedScriptHost::Initialize(Local target) { Local injectedScriptHost = New(); SetMethod(injectedScriptHost, "eval", Eval); SetMethod(injectedScriptHost, "evaluateWithExceptionDetails", EvaluateWithExceptionDetails); @@ -40,11 +39,12 @@ namespace nodex { SetMethod(injectedScriptHost, "internalConstructorName", InternalConstructorName); SetMethod(injectedScriptHost, "functionDetailsWithoutScopes", FunctionDetailsWithoutScopes); SetMethod(injectedScriptHost, "callFunction", CallFunction); + SetMethod(injectedScriptHost, "getInternalProperties", GetInternalProperties); SET(target, "InjectedScriptHost", injectedScriptHost); } - Handle InjectedScriptHost::createExceptionDetails(Handle message) { + Local InjectedScriptHost::createExceptionDetails(Local message) { EscapableHandleScope scope; Local exceptionDetails = New(); @@ -133,51 +133,48 @@ namespace nodex { RETURN(Undefined()); }; - Local InjectedScriptHost::functionDisplayName(Handle function) { - EscapableHandleScope scope; + const char* InjectedScriptHost::toCoreStringWithUndefinedOrNullCheck(Local result) { + if (result.IsEmpty() || result->IsNull() || result->IsUndefined()) + return ""; + else + return *Utf8String(result); + }; - Local value = CHK(To(function->GetDisplayName())); - if (value->Length()) - return scope.Escape(value); + Local InjectedScriptHost::functionDisplayName(Local function) { + Local value = function->GetDisplayName(); + if (value->IsString() && Local::Cast(value)->Length()) + return Local::Cast(value); - value = CHK(To(function->GetName())); - if (value->Length()) - return scope.Escape(value); + value = function->GetName(); + if (value->IsString() && v8::Local::Cast(value)->Length()) + return v8::Local::Cast(value); - value = CHK(To(function->GetInferredName())); - if (value->Length()) - return scope.Escape(value); + value = function->GetInferredName(); + if (value->IsString() && v8::Local::Cast(value)->Length()) + return v8::Local::Cast(value); - return scope.Escape(EmptyString()); + return v8::Local(); }; NAN_METHOD(InjectedScriptHost::InternalConstructorName) { - if (info.Length() < 1) - return ThrowError("One argument expected."); - if (!info[0]->IsObject()) - return ThrowTypeError("The argument must be an object."); + if (info.Length() < 1 || !info[0]->IsObject()) + return; Local object = CHK(To(info[0])); Local result = object->GetConstructorName(); - const char* result_type; - if (result.IsEmpty() || result->IsNull() || result->IsUndefined()) - result_type = ""; - else - result_type = *Utf8String(info[0]); - - if (!result.IsEmpty() && strcmp(result_type, "Object") == 0) { + if (!result.IsEmpty() && strcmp(toCoreStringWithUndefinedOrNullCheck(result), "Object") == 0) { Local constructorSymbol = CHK(New("constructor")); if (object->HasRealNamedProperty(constructorSymbol) && !object->HasRealNamedCallbackProperty(constructorSymbol)) { TryCatch tryCatch; Local constructor = object->GetRealNamedProperty(constructorSymbol); if (!constructor.IsEmpty() && constructor->IsFunction()) { - Local constructorName = functionDisplayName(Handle::Cast(constructor)); + Local constructorName = functionDisplayName(Local::Cast(constructor)); if (!constructorName.IsEmpty() && !tryCatch.HasCaught()) result = constructorName; } } - if (strcmp(result_type, "Object") == 0 && object->IsFunction()) + if (strcmp(toCoreStringWithUndefinedOrNullCheck(result), "Object") == 0 && object->IsFunction()) result = CHK(New("Function")); } @@ -203,7 +200,7 @@ namespace nodex { Local result = New(); SET(result, "location", location); - Handle name = functionDisplayName(function); + Local name = functionDisplayName(function); SET(result, "functionName", name.IsEmpty() ? EmptyString() : name); SET(result, "isGenerator", New(function->IsGeneratorFunction())); @@ -217,8 +214,8 @@ namespace nodex { if (!info[0]->IsFunction()) return ThrowTypeError("Argument 0 must be a function."); - Handle function = Handle::Cast(info[0]); - Handle receiver = info[1]; + Local function = Local::Cast(info[0]); + Local receiver = info[1]; TryCatch tryCatch; MaybeLocal result; @@ -232,9 +229,9 @@ namespace nodex { if (!info[2]->IsArray()) return ThrowTypeError("Argument 2 must be an array."); - Handle arguments = Handle::Cast(info[2]); + Local arguments = Local::Cast(info[2]); int argc = arguments->Length(); - Handle *argv = new Handle[argc]; + Local *argv = new Local[argc]; for (int i = 0; i < argc; ++i) argv[i] = CHK(Get(arguments, i)); @@ -260,4 +257,13 @@ namespace nodex { RETURN(CHK(result)); }; + + NAN_METHOD(InjectedScriptHost::GetInternalProperties) { + if (info.Length() < 1 || !info[0]->IsObject()) + return; + + MaybeLocal result = v8::Debug::GetInternalProperties(info.GetIsolate(), info[0]); + + RETURN(CHK(result)); + } } diff --git a/src/InjectedScriptHost.h b/src/InjectedScriptHost.h index ccb1bd0..144dbd8 100644 --- a/src/InjectedScriptHost.h +++ b/src/InjectedScriptHost.h @@ -15,20 +15,11 @@ namespace nodex { static NAN_METHOD(InternalConstructorName); static NAN_METHOD(FunctionDetailsWithoutScopes); static NAN_METHOD(CallFunction); - /* - static v8::Local New(const v8::CpuProfile* node); - static Nan::Persistent profiles; - */ + static NAN_METHOD(GetInternalProperties); private: - static v8::Handle createExceptionDetails(v8::Handle message); - static v8::Local functionDisplayName(v8::Handle function); - - /* - static NAN_METHOD(Delete); - static void Initialize(); - static Nan::Persistent profile_template_; - static uint32_t uid_counter; - */ + static v8::Local createExceptionDetails(v8::Local message); + static v8::Local functionDisplayName(v8::Local function); + static const char* toCoreStringWithUndefinedOrNullCheck(v8::Local result); }; } //namespace nodex diff --git a/src/JavaScriptCallFrame.cc b/src/JavaScriptCallFrame.cc new file mode 100644 index 0000000..9f8f64b --- /dev/null +++ b/src/JavaScriptCallFrame.cc @@ -0,0 +1,95 @@ +#include + +#include "JavaScriptCallFrame.h" +#include "tools.h" + +using v8::Isolate; +using v8::Local; +using v8::Value; +using v8::Number; +using v8::Integer; +using v8::String; +using v8::Object; +using v8::Message; +using v8::Function; +using Nan::To; +using Nan::New; +using Nan::Get; +using Nan::SetMethod; +using Nan::EscapableHandleScope; +using Nan::Undefined; +using Nan::TryCatch; +using Nan::ThrowError; +using Nan::ThrowTypeError; +using Nan::MaybeLocal; + +namespace nodex { + void JavaScriptCallFrame::Initialize(Local target) { + Local javaScriptCallFrame = New(); + SetMethod(javaScriptCallFrame, "evaluateWithExceptionDetails", EvaluateWithExceptionDetails); + SetMethod(javaScriptCallFrame, "restart", Restart); + + SET(target, "JavaScriptCallFrame", javaScriptCallFrame); + } + + Local JavaScriptCallFrame::createExceptionDetails(Local message) { + EscapableHandleScope scope; + + Local exceptionDetails = New(); + SET(exceptionDetails, "text", message->Get()); + + SET(exceptionDetails, "url", message->GetScriptOrigin().ResourceName()); + SET(exceptionDetails, "scriptId", New((int32_t)message->GetScriptOrigin().ScriptID()->Value())); + SET(exceptionDetails, "line", New(message->GetLineNumber())); + SET(exceptionDetails, "column", New(message->GetStartColumn())); + + if (!message->GetStackTrace().IsEmpty()) + SET(exceptionDetails, "stackTrace", message->GetStackTrace()->AsArray()); + else + SET(exceptionDetails, "stackTrace", Undefined()); + + return scope.Escape(exceptionDetails); + }; + + NAN_METHOD(JavaScriptCallFrame::EvaluateWithExceptionDetails) { + Local callFrame = CHK(To(CHK(Get(info.Holder(), STR("proto"))))); + Local evalFunction = Local::Cast(CHK(Get(callFrame, STR("evaluate")))); + + Local expression = info[0]; + Local scopeExtension = info[1]; + v8::Local argv[] = { + expression, + scopeExtension + }; + + Local wrappedResult = New(); + + TryCatch tryCatch; + MaybeLocal result; + + result = evalFunction->Call(callFrame, 2, argv); + + if (tryCatch.HasCaught()) { + SET(wrappedResult, "result", tryCatch.Exception()); + SET(wrappedResult, "exceptionDetails", createExceptionDetails(tryCatch.Message())); + } else { + SET(wrappedResult, "result", CHK(result)); + SET(wrappedResult, "exceptionDetails", Undefined()); + } + + RETURN(wrappedResult); + }; + + NAN_METHOD(JavaScriptCallFrame::Restart) { + Local callFrame = CHK(To(CHK(Get(info.Holder(), STR("proto"))))); + Local restartFunction = Local::Cast(CHK(Get(callFrame, STR("restart")))); + + TryCatch tryCatch; + MaybeLocal result; + + result = restartFunction->Call(callFrame, 0, NULL); + + MAYBE_RETHROW(); + RETURN(CHK(result)); + }; +} diff --git a/src/JavaScriptCallFrame.h b/src/JavaScriptCallFrame.h new file mode 100644 index 0000000..f8e7c86 --- /dev/null +++ b/src/JavaScriptCallFrame.h @@ -0,0 +1,18 @@ +#ifndef X_JAVASCRIPT_CALL_FRAME_ +#define X_JAVASCRIPT_CALL_FRAME_ + +#include + +namespace nodex { + + class JavaScriptCallFrame { + public: + static void Initialize(v8::Local target); + static NAN_METHOD(EvaluateWithExceptionDetails); + static NAN_METHOD(Restart); + private: + static v8::Local createExceptionDetails(v8::Local message); + }; + +} //namespace nodex +#endif // X_JAVASCRIPT_CALL_FRAME_ diff --git a/src/debug.cc b/src/debug.cc index 43482e6..9426227 100644 --- a/src/debug.cc +++ b/src/debug.cc @@ -4,11 +4,13 @@ #include "tools.h" #if (NODE_NEXT) #include "InjectedScriptHost.h" +#include "JavaScriptCallFrame.h" #endif using v8::Isolate; using v8::Local; using v8::Value; +using v8::Boolean; using v8::String; using v8::Object; using v8::Context; @@ -58,15 +60,18 @@ namespace nodex { if (expression.IsEmpty()) RETURN(Undefined()); + Local current_context = Nan::GetCurrentContext(); Local debug_context = v8::Debug::GetDebugContext(); #if (NODE_MODULE_VERSION > 45) if (debug_context.IsEmpty()) { // Force-load the debug context. - v8::Debug::GetMirror(info.GetIsolate()->GetCurrentContext(), info[0]); + v8::Debug::GetMirror(current_context, New()); debug_context = v8::Debug::GetDebugContext(); } -#endif + // Share security token + debug_context->SetSecurityToken(current_context->GetSecurityToken()); +#endif Context::Scope context_scope(debug_context); TryCatch tryCatch; @@ -77,12 +82,57 @@ namespace nodex { }; static NAN_METHOD(AllowNatives) { + // TODO: deprecate this useless method const char allow_natives_syntax[] = "--allow_natives_syntax"; v8::V8::SetFlagsFromString(allow_natives_syntax, sizeof(allow_natives_syntax) - 1); RETURN(Undefined()); } + static NAN_METHOD(ShareSecurityToken) { + Local current_context = Nan::GetCurrentContext(); + Local debug_context = v8::Debug::GetDebugContext(); +#if (NODE_MODULE_VERSION > 45) + if (debug_context.IsEmpty()) { + // Force-load the debug context. + v8::Debug::GetMirror(current_context, New()); + debug_context = v8::Debug::GetDebugContext(); + } +#endif + // Share security token + debug_context->SetSecurityToken(current_context->GetSecurityToken()); + } + + static NAN_METHOD(UnshareSecurityToken) { + Local current_context = Nan::GetCurrentContext(); + Local debug_context = v8::Debug::GetDebugContext(); +#if (NODE_MODULE_VERSION > 45) + if (debug_context.IsEmpty()) { + // Force-load the debug context. + v8::Debug::GetMirror(current_context, New()); + debug_context = v8::Debug::GetDebugContext(); + } +#endif + debug_context->UseDefaultSecurityToken(); + } + + static NAN_METHOD(SetPauseOnNextStatement) { + Local _pause = CHK(To(info[0])); + bool pause = _pause->Value(); + if (pause) + v8::Debug::DebugBreak(info.GetIsolate()); + else + v8::Debug::CancelDebugBreak(info.GetIsolate()); + } + + static NAN_METHOD(SetLiveEditEnabled) { +#if (NODE_MODULE_VERSION > 45) + Local _enabled = CHK(To(info[0])); + bool enabled = _enabled->Value(); + v8::Debug::SetLiveEditEnabled(info.GetIsolate(), enabled); +#endif + } + private: Debug() {} ~Debug() {} @@ -91,12 +141,17 @@ namespace nodex { NAN_MODULE_INIT(Initialize) { #if (NODE_NEXT) InjectedScriptHost::Initialize(target); + JavaScriptCallFrame::Initialize(target); #endif SetMethod(target, "call", Debug::Call); SetMethod(target, "sendCommand", Debug::SendCommand); SetMethod(target, "runScript", Debug::RunScript); SetMethod(target, "allowNatives", Debug::AllowNatives); + SetMethod(target, "shareSecurityToken", Debug::ShareSecurityToken); + SetMethod(target, "unshareSecurityToken", Debug::UnshareSecurityToken); + SetMethod(target, "setPauseOnNextStatement", Debug::SetPauseOnNextStatement); + SetMethod(target, "setLiveEditEnabled", Debug::SetLiveEditEnabled); } NODE_MODULE(debug, Initialize) diff --git a/src/tools.h b/src/tools.h index ea65e27..b1f1b9f 100644 --- a/src/tools.h +++ b/src/tools.h @@ -4,6 +4,9 @@ #define CHK(VALUE) \ VALUE.ToLocalChecked() +#define STR(VALUE) \ + Nan::New(VALUE).ToLocalChecked() + #define RETURN(VALUE) { \ info.GetReturnValue().Set(VALUE); \ return; \ @@ -14,14 +17,14 @@ #define RUNSCRIPT(EXPRESSION, RESULT) while (true) { \ Nan::MaybeLocal script = Nan::CompileScript(EXPRESSION);\ - if (tryCatch.HasCaught()) break; \ + if (tryCatch.HasCaught()) break; \ RESULT = Nan::RunScript(CHK(script)); \ break; \ } #define MAYBE_RETHROW() \ - if (tryCatch.HasCaught()) { \ - tryCatch.ReThrow(); \ + if (tryCatch.HasCaught()) { \ + tryCatch.ReThrow(); \ return; \ } diff --git a/test/next/injected-script-source.js b/test/next/injected-script-source.js new file mode 100644 index 0000000..81145f9 --- /dev/null +++ b/test/next/injected-script-source.js @@ -0,0 +1,147 @@ +var expect = require('chai').expect; + +describe('InjectedScriptSource', function() { + var debug = null; + + before(function() { + debug = require('../../')(); + debug.enableWebkitProtocol(); + }); + + after(function() { debug = null; }); + + it('isPrimitiveValue', function(done) { + debug.registerAgentCommand('isPrimitiveValue', function(args, response, IScript, DScript) { + expect(IScript.isPrimitiveValue(42)).to.be.equal(true); + expect(IScript.isPrimitiveValue({})).to.be.equal(undefined); + done(); + }); + + debug.sendCommand('isPrimitiveValue'); + }); + + it('wrapObject', function(done) { + debug.registerAgentCommand('wrapObject', function(args, response, IScript, DScript) { + expect(IScript.wrapObject.bind(IScript, {a:1}, 'console', true, false)).to.not.throw(); + expect(IScript.wrapObject.bind(IScript, {a:1}, 'console', true, true)).to.not.throw(); + expect(IScript.wrapObject.bind(IScript, 'string', 'console', true, true)).to.not.throw(); + expect(IScript.wrapObject.bind(IScript, undefined, 'console', true, true)).to.not.throw(); + expect(IScript.wrapObject.bind(IScript, {a:1}, 'console', false, false)).to.not.throw(); + expect(IScript.wrapObject.bind(IScript, {a:1}, 'console', false, true)).to.not.throw(); + done(); + }); + + debug.sendCommand('wrapObject'); + }); + + it('wrapTable', function(done) { + debug.registerAgentCommand('wrapTable', function(args, response, IScript, DScript) { + expect(IScript.wrapTable.bind(IScript, true, {a:1}, ['a'])).to.not.throw(); + done(); + }); + + debug.sendCommand('wrapTable'); + }); + + it('getProperties', function(done) { + debug.registerAgentCommand('getProperties', function(args, response, IScript, DScript) { + var objectId = IScript.wrapObject({a:1, b: Symbol('b'), get c() {}}, 'console', true, false).objectId; + IScript.getProperties(objectId, true, true, false); + IScript.getProperties(objectId, true, false, false); + IScript.getProperties(objectId, false, false, false); + IScript.getProperties(objectId, false, true, false); + IScript.getProperties(objectId, true, false, true); + + done(); + }); + + debug.sendCommand('getProperties'); + }); + + it('getInternalProperties', function(done) { + debug.registerAgentCommand('getInternalProperties', function(args, response, IScript, DScript) { + var objectId = IScript.wrapObject({a:1, b: Symbol('b'), get c() {}}, 'console', true, false).objectId; + IScript.getInternalProperties(objectId); + + done(); + }); + + debug.sendCommand('getInternalProperties'); + }); + + it('getFunctionDetails', function(done) { + debug.registerAgentCommand('getFunctionDetails', function(args, response, IScript, DScript) { + var c = 1; + function d(a, b) { c = a; } + function * g(a, b) { yield c; return c = a; } + + var functionId = IScript.wrapObject(d, 'console', true, false).objectId; + var generatorId = IScript.wrapObject(g, 'console', true, false).objectId; + IScript.getFunctionDetails(functionId); + IScript.getFunctionDetails(generatorId); + + done(); + }); + + debug.sendCommand('getFunctionDetails'); + }); + + it('getGeneratorObjectDetails', function(done) { + debug.registerAgentCommand('getGeneratorObjectDetails', function(args, response, IScript, DScript) { + var c = 1; + function * g(a, b) { yield c; return c = b; } + var gen = g(1, 2); + + var genId = IScript.wrapObject(gen, 'console', true, false).objectId; + IScript.getGeneratorObjectDetails(genId); + + done(); + }); + + debug.sendCommand('getGeneratorObjectDetails'); + }); + + it('getCollectionEntries', function(done) { + debug.registerAgentCommand('getCollectionEntries', function(args, response, IScript, DScript) { + var map = new Map().set('1', 1); + + var mapId = IScript.wrapObject(map, 'console', true, false).objectId; + IScript.getCollectionEntries(mapId); + + done(); + }); + + debug.sendCommand('getCollectionEntries'); + }); + + it('evaluate', function(done) { + debug.registerAgentCommand('evaluate', function(args, response, IScript, DScript) { + IScript.evaluate('{a: 1}', 'console'); + IScript.evaluate('{a: 1}', 'console', false, true); + IScript.evaluate('{a: 1}', 'console', false, false, true); + IScript.evaluate('{a: 1}', 'console', true); + + done(); + }); + + debug.sendCommand('evaluate'); + }); + + it('callFunctionOn', function(done) { + debug.registerAgentCommand('callFunctionOn', function(args, response, IScript, DScript) { + var contextId = IScript.wrapObject({a: 1, b: 2}, 'console', true, false).objectId; + var args = [ + {value: 3}, + {objectId: IScript.wrapObject({b: 4}, 'console', true, false).objectId} + ]; + var expression = 'function(a, b) { return this.a + this.b + a + b.b; }'; + + var result = IScript.callFunctionOn(contextId, expression, JSON.stringify(args)).result; + expect(result.value).to.be.equal(10); + + done(); + }); + + debug.sendCommand('callFunctionOn'); + }); +}); diff --git a/test/injected-script.js b/test/next/injected-script.js similarity index 95% rename from test/injected-script.js rename to test/next/injected-script.js index 39421a6..f6def63 100644 --- a/test/injected-script.js +++ b/test/next/injected-script.js @@ -1,10 +1,7 @@ var expect = require('chai').expect; var binary = require('node-pre-gyp'); var path = require('path'); -var binding_path = binary.find(path.resolve(path.join(__dirname,'../package.json'))); -var NODE_NEXT = require('../tools/NODE_NEXT.js'); - -if (!NODE_NEXT) return; +var binding_path = binary.find(path.resolve(path.join(__dirname,'../../package.json'))); describe('binding', function() { var binding = require(binding_path); @@ -21,6 +18,7 @@ describe('binding', function() { checksTypeValid(new RegExp(), 'regexp'); checksTypeValid(new Error(), 'error'); checksTypeValid(new String(), undefined); + checksTypeValid(new Promise(function() {}), undefined); function checksTypeValid(value, type) { it('checks ' + type + ' subtype', function() { @@ -65,6 +63,9 @@ describe('binding', function() { describe('function `internalConstructorName`', function() { checksNameValid(new Number(), 'Number'); checksNameValid(new Object(), 'Object'); + checksNameValid(new Promise(function() {}), 'Promise'); + checksNameValid(1, undefined); + checksNameValid(null, undefined); function checksNameValid(value, name) { it('checks new ' + name + '() constructor name', function() { @@ -72,10 +73,6 @@ describe('binding', function() { }); } - throwsOnArgs([]); - throwsOnArgs([1]); - throwsOnArgs([null]); - function throwsOnArgs(argvList) { it('should throw on wrong arguments ' + JSON.stringify(argvList), function() { expect(host.internalConstructorName.bind.apply( diff --git a/test/v8-debug.js b/test/v8-debug.js index 86671f6..fb0cf14 100644 --- a/test/v8-debug.js +++ b/test/v8-debug.js @@ -1,5 +1,4 @@ -var expect = require('chai').expect, - v8debug = require('../'); +var expect = require('chai').expect; var NODE_NEXT = require('../tools/NODE_NEXT'); @@ -10,13 +9,17 @@ _debugger.stderr.on('data', function(data) { }); describe('v8-debug', function() { + var v8debug = null; + before(function(done) { - _debugger.stdout.on('data', function(data) { - console.log(' ' + data); + v8debug = require('../')() + _debugger.stdout.once('data', function(data) { done(); }); }); + after(function() { v8debug = null; }); + describe('function `runInDebugContext`', function() { it('returns Debug object', function() { var Debug = v8debug.runInDebugContext('Debug'); @@ -55,8 +58,13 @@ describe('v8-debug', function() { describe('events.', function() { it('Emits `close` on disconnect command', function(done) { - v8debug.on('close', done); + v8debug.once('close', done); v8debug.sendCommand('disconnect'); }); }); }); + +if (NODE_NEXT) { + require('./next/injected-script.js'); + require('./next/injected-script-source.js'); +} diff --git a/tools/prepublish-to-npm.js b/tools/prepublish-to-npm.js index 55f145d..5abc566 100644 --- a/tools/prepublish-to-npm.js +++ b/tools/prepublish-to-npm.js @@ -7,13 +7,17 @@ rimraf.sync('./build'); var versions = ['0.10.0', '0.12.0', '4.0.0', '5.0.0']; var matrix = { x64: ['win32', 'linux', 'darwin'], - ia32: ['win32'] + ia32: ['win32'], + except: { '4.0.0-win32-ia32': true } }; var targets = []; Object.keys(matrix).forEach(function(arch) { matrix[arch].forEach(function(platform) { versions.forEach(function(version) { + var key = version + '-' + platform + '-' + arch; + if (matrix.except[key]) return; + targets.push({ target: version, target_platform: platform, diff --git a/v8-debug.js b/v8-debug.js index 0d94df8..2ae69bc 100644 --- a/v8-debug.js +++ b/v8-debug.js @@ -1,16 +1,21 @@ -var binary = require('node-pre-gyp'); +//var binary = require('node-pre-gyp'); var fs = require('fs'); var path = require('path'); -var binding_path = binary.find(path.resolve(path.join(__dirname,'./package.json'))); -var binding = require(binding_path); +var pack = require('./package.json'); var EventEmitter = require('events').EventEmitter; var inherits = require('util').inherits; var extend = require('util')._extend; -var NODE_NEXT = require('./tools/NODE_NEXT'); +var binding = require('./' + [ + 'build', + 'debug', + 'v' + pack.version, + ['node', 'v' + process.versions.modules, process.platform, process.arch].join('-'), + 'debug.node' +].join('/')); -// Don't cache debugger module -delete require.cache[module.id]; +var NODE_NEXT = require('./tools/NODE_NEXT'); +var nextTmpEventId = 1; function InjectedScriptDir(link) { return require.resolve(__dirname + '/InjectedScript/' + link); @@ -18,6 +23,7 @@ function InjectedScriptDir(link) { var DebuggerScriptLink = InjectedScriptDir('DebuggerScript.js'); var InjectedScriptLink = InjectedScriptDir('InjectedScriptSource.js'); var InjectedScriptHostLink = InjectedScriptDir('InjectedScriptHost.js'); +var JavaScriptCallFrameLink = InjectedScriptDir('JavaScriptCallFrame.js'); var overrides = { extendedProcessDebugJSONRequestHandles_: {}, @@ -68,7 +74,7 @@ var overrides = { response = this.createResponse(); } response.success = false; - response.message = e.toString(); + response.message = e.stack; } // Return the response as a JSON encoded string. @@ -85,11 +91,11 @@ var overrides = { '"request_seq":' + request.seq + ',' + '"type":"response",' + '"success":false,' + - '"message":"Internal error: ' + e.toString() + '"}'; + '"message":"Internal error: ' + e.stack + '"}'; } } catch (e) { // Failed in one of the catch blocks above - most generic error. - return '{"seq":0,"type":"response","success":false,"message":"Internal error"}'; + return '{"seq":0,"type":"response","success":false,"message":"' + e.stack + '"}'; } }, processDebugRequest: function WRAPPED_BY_NODE_INSPECTOR(request) { @@ -101,77 +107,131 @@ var overrides = { inherits(V8Debug, EventEmitter); function V8Debug() { + if (!(this instanceof V8Debug)) return new V8Debug(); + + this._Debug = this.get('Debug'); + this._webkitProtocolEnabled = false; // NOTE: Call `_setDebugEventListener` before all other changes in Debug Context. // After node 0.12.0 this function serves to allocate Debug Context // like a persistent value, that saves all our changes. this._setDebugEventListener(); + // We need to share security token between current and debug context to + // get access to evaluation functions + this._shareSecurityToken(); this._wrapDebugCommandProcessor(); this.once('close', function() { + this._disableWebkitProtocol(); this._unwrapDebugCommandProcessor(); + this._unshareSecurityToken(); this._unsetDebugEventListener(); - process.nextTick(function() { - this.removeAllListeners(); - }.bind(this)); }); } V8Debug.prototype._setDebugEventListener = function() { - var Debug = this.get('Debug'); - Debug.setListener(function(_, execState, event) { + this._Debug.setListener(function(_, execState, event) { // TODO(3y3): Handle events here }); }; V8Debug.prototype._unsetDebugEventListener = function() { - var Debug = this.get('Debug'); - Debug.setListener(null); + this._Debug.setListener(null); +}; + +V8Debug.prototype._shareSecurityToken = function() { + binding.shareSecurityToken(); +}; + +V8Debug.prototype._unshareSecurityToken = function() { + binding.unshareSecurityToken(); }; V8Debug.prototype._wrapDebugCommandProcessor = function() { var proto = this.get('DebugCommandProcessor.prototype'); - overrides.processDebugRequest_ = proto.processDebugRequest; - extend(proto, overrides); - overrides.extendedProcessDebugJSONRequestHandles_['disconnect'] = function(request, response) { + this._DebugCommandProcessor = proto; + + if (!proto.processDebugRequest_) { + proto.processDebugRequest_ = proto.processDebugRequest; + extend(proto, overrides); + } + + proto.extendedProcessDebugJSONRequestHandles_['disconnect'] = function(request, response) { this.emit('close'); - this.processDebugJSONRequest(request); + proto._DebugCommandProcessor; + //proto.processDebugJSONRequest(request, response); + //return true; }.bind(this); }; V8Debug.prototype._unwrapDebugCommandProcessor = function() { var proto = this.get('DebugCommandProcessor.prototype'); - proto.processDebugRequest = proto.processDebugRequest_; - delete proto.processDebugRequest_; - delete proto.extendedProcessDebugJSONRequest_; - delete proto.extendedProcessDebugJSONRequestHandles_; - delete proto.extendedProcessDebugJSONRequestAsyncHandles_; + delete this._DebugCommandProcessor; + + proto.extendedProcessDebugJSONRequestHandles_ = {}; + proto.extendedProcessDebugJSONRequestAsyncHandles_ = {}; +}; + +V8Debug.prototype.setPauseOnNextStatement = function(pause) { + binding.setPauseOnNextStatement(pause === true); +}; + +V8Debug.prototype.setLiveEditEnabled = function(enabled) { + binding.setLiveEditEnabled(enabled === true); +}; + +V8Debug.prototype.scripts = function() { + return this._Debug.scripts(); }; V8Debug.prototype.register = V8Debug.prototype.registerCommand = function(name, func) { - overrides.extendedProcessDebugJSONRequestHandles_[name] = func; + var proto = this._DebugCommandProcessor; + if (!proto) return; + + proto.extendedProcessDebugJSONRequestHandles_[name] = func; +}; + +V8Debug.prototype.unregister = +V8Debug.prototype.unregisterCommand = function(name, func) { + var proto = this._DebugCommandProcessor; + if (!proto) return; + + delete proto.extendedProcessDebugJSONRequestHandles_[name]; }; V8Debug.prototype.registerAsync = V8Debug.prototype.registerAsyncCommand = function(name, func) { - overrides.extendedProcessDebugJSONRequestAsyncHandles_[name] = func; + var proto = this._DebugCommandProcessor; + if (!proto) return; + + proto.extendedProcessDebugJSONRequestAsyncHandles_[name] = func; }; V8Debug.prototype.command = -V8Debug.prototype.sendCommand = -V8Debug.prototype.emitEvent = sendCommand; +V8Debug.prototype.sendCommand = sendCommand; function sendCommand(name, attributes, userdata) { var message = { seq: 0, type: 'request', command: name, - arguments: attributes || {} + arguments: attributes }; binding.sendCommand(JSON.stringify(message)); }; +V8Debug.prototype.emitEvent = emitEvent; +function emitEvent(name, attributes, userdata) { + var handlerName = 'tmpEvent-' + nextTmpEventId++; + this.registerCommand(handlerName, function(request, response) { + this.commandToEvent(request, response); + response.event = name; + this.unregisterCommand(handlerName); + }.bind(this)); + this.sendCommand(handlerName, attributes, userdata); +} + V8Debug.prototype.commandToEvent = function(request, response) { response.type = 'event'; response.event = response.command; @@ -180,15 +240,9 @@ V8Debug.prototype.commandToEvent = function(request, response) { delete response.request_seq; }; -V8Debug.prototype.registerEvent = function(name) { - overrides.extendedProcessDebugJSONRequestHandles_[name] = this.commandToEvent; -}; - V8Debug.prototype.get = V8Debug.prototype.runInDebugContext = function(script) { - if (typeof script == 'function') script = script.toString() + '()'; - - script = /\);$/.test(script) ? script : '(' + script + ');'; + if (typeof script == 'function') script = '(' + script.toString() + ')()'; return binding.runScript(script); }; @@ -230,31 +284,65 @@ V8Debug.prototype.enableWebkitProtocol = function() { InjectedScriptHostSource, InjectedScriptHost; - function prepareSource(source) { - return 'var ToggleMirrorCache = ToggleMirrorCache || function() {};\n' + - '(function() {' + - ('' + source).replace(/^.*?"use strict";(\r?\n.*?)*\(/m, '\r\n"use strict";\nreturn (') + - '}());'; - } + this.runInDebugContext('ToggleMirrorCache = ToggleMirrorCache || function() {}'); - DebuggerScriptSource = prepareSource(fs.readFileSync(DebuggerScriptLink, 'utf8')); + DebuggerScriptSource = fs.readFileSync(DebuggerScriptLink, 'utf8'); DebuggerScript = this.runInDebugContext(DebuggerScriptSource); - InjectedScriptSource = prepareSource(fs.readFileSync(InjectedScriptLink, 'utf8')); + InjectedScriptSource = fs.readFileSync(InjectedScriptLink, 'utf8'); InjectedScript = this.runInDebugContext(InjectedScriptSource); - InjectedScriptHostSource = prepareSource(fs.readFileSync(InjectedScriptHostLink, 'utf8')); + InjectedScriptHostSource = fs.readFileSync(InjectedScriptHostLink, 'utf8'); InjectedScriptHost = this.runInDebugContext(InjectedScriptHostSource)(binding, DebuggerScript); - var injectedScript = InjectedScript(InjectedScriptHost, global, 1); + JavaScriptCallFrameSource = fs.readFileSync(JavaScriptCallFrameLink, 'utf8'); + JavaScriptCallFrame = this.runInDebugContext(JavaScriptCallFrameSource)(binding); + + var injectedScript = InjectedScript(InjectedScriptHost, global, process.pid); this.registerAgentCommand = function(command, parameters, callback) { - this.registerCommand(command, new WebkitProtocolCallback(parameters, callback)); + if (typeof parameters === 'function') { + callback = parameters; + parameters = []; + } + + this.registerCommand(command, new WebkitCommandCallback(parameters, callback)); + }; + + this.emitAgentEvent = function(command, callback) { + var handlerName = 'tmpEvent-' + nextTmpEventId++; + this.registerCommand(handlerName, function(request, response) { + this.commandToEvent(request, response); + response.event = command; + + new WebkitEventCallback(callback)(request, response); + this.unregisterCommand(handlerName); + }.bind(this)); + this.sendCommand(handlerName); + }; + + this.wrapCallFrames = function(execState, maximumLimit, scopeDetails) { + var scopeBits = 2; + + if (maximumLimit < 0) throw new Error('Incorrect stack trace limit.'); + var data = (maximumLimit << scopeBits) | scopeDetails; + var currentCallFrame = DebuggerScript.currentCallFrame(execState, data); + if (!currentCallFrame) return; + + return new JavaScriptCallFrame(currentCallFrame); + }; + + this.releaseObject = function(name) { + return InjectedScriptHost.releaseObject(name); + }; + + this.releaseObjectGroup = function(name) { + return InjectedScriptHost.releaseObjectGroup(name); }; this._webkitProtocolEnabled = true; - function WebkitProtocolCallback(argsList, callback) { + function WebkitCommandCallback(argsList, callback) { return function(request, response) { InjectedScriptHost.execState = this.exec_state_; @@ -267,10 +355,29 @@ V8Debug.prototype.enableWebkitProtocol = function() { InjectedScriptHost.execState = null; } } + + function WebkitEventCallback(callback) { + return function(request, response) { + InjectedScriptHost.execState = this.exec_state_; + + callback.call(this, response, injectedScript, DebuggerScript); + + InjectedScriptHost.execState = null; + } + } +}; + +V8Debug.prototype._disableWebkitProtocol = function() { + if (!this._webkitProtocolEnabled) return; + this._webkitProtocolEnabled = false; + this.runInDebugContext('ToggleMirrorCache(true)'); }; +V8Debug.prototype.releaseObject = +V8Debug.prototype.releaseObjectGroup = +V8Debug.prototype.wrapCallFrames = V8Debug.prototype.registerAgentCommand = function(command, parameters, callback) { throw new Error('Use "enableWebkitProtocol" before using this method'); }; -module.exports = new V8Debug(); +module.exports = V8Debug;