diff --git a/js/src/doc/Debugger/Debugger.Object.md b/js/src/doc/Debugger/Debugger.Object.md index 735036df53b..32f2741040a 100644 --- a/js/src/doc/Debugger/Debugger.Object.md +++ b/js/src/doc/Debugger/Debugger.Object.md @@ -427,9 +427,9 @@ code), the call throws a [`Debugger.DebuggeeWouldRun`][wouldrun] exception. `asEnvironment()` : If the referent is a global object, return the [`Debugger.Environment`][environment] - instance representing the referent as a variable environment for - evaluating code. If the referent is not a global object, throw a - `TypeError`. + instance representing the referent's global lexical scope. The global + lexical scope's enclosing scope is the global object. If the referent is + not a global object, throw a `TypeError`. setObjectWatchpoint(handler) (future plan) : Set a watchpoint on all the referent's own properties, reporting events diff --git a/js/src/jit-test/tests/debug/Object-asEnvironment-01.js b/js/src/jit-test/tests/debug/Object-asEnvironment-01.js new file mode 100644 index 00000000000..1f5e3be42ff --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-asEnvironment-01.js @@ -0,0 +1,15 @@ +// Tests D.O.asEnvironment() returning the global lexical scope. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +g.evaluate(` + var x = 42; + let y = "foo" +`); + +var globalLexical = gw.asEnvironment(); +assertEq(globalLexical.names().length, 1); +assertEq(globalLexical.getVariable("y"), "foo"); +assertEq(globalLexical.parent.getVariable("x"), 42); diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 405d66a1f65..f17f9101dda 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -7687,6 +7687,24 @@ DebuggerObject_executeInGlobalWithBindings(JSContext* cx, unsigned argc, Value* args.rval(), dbg, globalLexical, nullptr); } +static bool +DebuggerObject_asEnvironment(JSContext* cx, unsigned argc, Value* vp) +{ + THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "asEnvironment", args, dbg, referent); + if (!RequireGlobalObject(cx, args.thisv(), referent)) + return false; + + Rooted env(cx); + { + AutoCompartment ac(cx, referent); + env = GetDebugScopeForGlobalLexicalScope(cx); + if (!env) + return false; + } + + return dbg->wrapEnvironment(cx, env, args.rval()); +} + static bool DebuggerObject_unwrap(JSContext* cx, unsigned argc, Value* vp) { @@ -7764,6 +7782,7 @@ static const JSFunctionSpec DebuggerObject_methods[] = { JS_FN("makeDebuggeeValue", DebuggerObject_makeDebuggeeValue, 1, 0), JS_FN("executeInGlobal", DebuggerObject_executeInGlobal, 1, 0), JS_FN("executeInGlobalWithBindings", DebuggerObject_executeInGlobalWithBindings, 2, 0), + JS_FN("asEnvironment", DebuggerObject_asEnvironment, 0, 0), JS_FN("unwrap", DebuggerObject_unwrap, 0, 0), JS_FN("unsafeDereference", DebuggerObject_unsafeDereference, 0, 0), JS_FS_END diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index e926f23baeb..27296c4409e 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -2859,6 +2859,13 @@ js::GetDebugScopeForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc) return GetDebugScope(cx, si); } +JSObject* +js::GetDebugScopeForGlobalLexicalScope(JSContext* cx) +{ + ScopeIter si(cx, &cx->global()->lexicalScope(), &cx->global()->lexicalScope().staticBlock()); + return GetDebugScope(cx, si); +} + // See declaration and documentation in jsfriendapi.h JS_FRIEND_API(JSObject*) js::GetNearestEnclosingWithScopeObjectForFunction(JSFunction* fun) diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index b93c1535173..75af4708a23 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -1109,13 +1109,13 @@ class LiveScopeVal * now incomplete: it may not contain all, or any, of the ScopeObjects to * represent the current scope. * - * To resolve this, the debugger first calls GetDebugScopeFor(Function|Frame) - * to synthesize a "debug scope chain". A debug scope chain is just a chain of - * objects that fill in missing scopes and protect the engine from unexpected - * access. (The latter means that some debugger operations, like redefining a - * lexical binding, can fail when a true eval would succeed.) To do both of - * these things, GetDebugScopeFor* creates a new proxy DebugScopeObject to sit - * in front of every existing ScopeObject. + * To resolve this, the debugger first calls GetDebugScopeFor* to synthesize a + * "debug scope chain". A debug scope chain is just a chain of objects that + * fill in missing scopes and protect the engine from unexpected access. (The + * latter means that some debugger operations, like redefining a lexical + * binding, can fail when a true eval would succeed.) To do both of these + * things, GetDebugScopeFor* creates a new proxy DebugScopeObject to sit in + * front of every existing ScopeObject. * * GetDebugScopeFor* ensures the invariant that the same DebugScopeObject is * always produced for the same underlying scope (optimized or not!). This is @@ -1128,6 +1128,9 @@ GetDebugScopeForFunction(JSContext* cx, HandleFunction fun); extern JSObject* GetDebugScopeForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc); +extern JSObject* +GetDebugScopeForGlobalLexicalScope(JSContext* cx); + /* Provides debugger access to a scope. */ class DebugScopeObject : public ProxyObject {