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
{