Backed out changesets b61921a307e9 and e9e74f6bd12a (bug 1054759) for breaking web compat by implementing Symbol.unscopables without Array.prototype[@@unscopables].
authorShu-yu Guo <shu@rfrn.org>
Sat, 19 Mar 2016 19:18:12 -0700
changeset 289582 8461e2ffc339629b431d93be4c8bc59db9327de6
parent 289581 6d3b1698925ec0a72ebba55a34e874b21decc58b
child 289583 de7baace275ad30bb1eae8e5fe85a9a8f5d1e779
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1054759
milestone48.0a1
backs outb61921a307e9ed7d94e94290dda4672ad2779bd0
e9e74f6bd12a8c45bb3e20a0ca573db972ed6345
Backed out changesets b61921a307e9 and e9e74f6bd12a (bug 1054759) for breaking web compat by implementing Symbol.unscopables without Array.prototype[@@unscopables].
js/src/jit-test/tests/debug/Environment-unscopables.js
js/src/jsapi.h
js/src/tests/ecma_6/LexicalEnvironment/unscopables-basics.js
js/src/tests/ecma_6/LexicalEnvironment/unscopables-closures.js
js/src/tests/ecma_6/LexicalEnvironment/unscopables-const.js
js/src/tests/ecma_6/LexicalEnvironment/unscopables-delete.js
js/src/tests/ecma_6/LexicalEnvironment/unscopables-getters.js
js/src/tests/ecma_6/LexicalEnvironment/unscopables-global.js
js/src/tests/ecma_6/LexicalEnvironment/unscopables-ignored.js
js/src/tests/ecma_6/LexicalEnvironment/unscopables-miss.js
js/src/tests/ecma_6/LexicalEnvironment/unscopables-mutation-frozen.js
js/src/tests/ecma_6/LexicalEnvironment/unscopables-mutation.js
js/src/tests/ecma_6/LexicalEnvironment/unscopables-proto.js
js/src/tests/ecma_6/LexicalEnvironment/unscopables-proxy.js
js/src/tests/ecma_6/LexicalEnvironment/unscopables-strict.js
js/src/tests/ecma_6/LexicalEnvironment/unscopables-tdz.js
js/src/tests/ecma_6/Symbol/well-known.js
js/src/vm/CommonPropertyNames.h
js/src/vm/Debugger.cpp
js/src/vm/Runtime.h
js/src/vm/ScopeObject.cpp
deleted file mode 100644
--- a/js/src/jit-test/tests/debug/Environment-unscopables.js
+++ /dev/null
@@ -1,37 +0,0 @@
-// An Environment for a `with` statement does not observe bindings ruled out by @@unscopables.
-
-load(libdir + "asserts.js");
-
-let g = newGlobal();
-g.eval(`
-    let x = 'global';
-    function f() {
-        let obj = {
-            x: 'obj',
-            y: 'obj',
-            [Symbol.unscopables]: {x: 1},
-        };
-        with (obj)
-            debugger;
-    }
-`);
-let dbg = Debugger(g);
-let hits = 0;
-dbg.onDebuggerStatement = function (frame) {
-    let env = frame.environment;
-
-    assertEq(env.find("x") !== env, true);
-    assertEq(env.names().indexOf("x"), -1);
-    assertEq(env.getVariable("x"), undefined);
-    assertThrowsInstanceOf(() => env.setVariable("x", 7), TypeError);
-
-    assertEq(env.find("y") === env, true);
-    assertEq(env.getVariable("y"), "obj");
-    env.setVariable("y", 8);
-
-    assertEq(frame.eval("x").return, "global");
-    assertEq(frame.eval("y").return, 8);
-    hits++;
-};
-g.f();
-assertEq(hits, 1);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4710,35 +4710,27 @@ GetSymbolFor(JSContext* cx, HandleString
  *
  * This function is infallible. If it returns null, that means the symbol's
  * [[Description]] is undefined.
  */
 JS_PUBLIC_API(JSString*)
 GetSymbolDescription(HandleSymbol symbol);
 
 /* Well-known symbols. */
-#define JS_FOR_EACH_WELL_KNOWN_SYMBOL(macro) \
-    macro(iterator) \
-    macro(match) \
-    macro(species) \
-    macro(toPrimitive) \
-    macro(unscopables)
-
 enum class SymbolCode : uint32_t {
-    // There is one SymbolCode for each well-known symbol.
-#define JS_DEFINE_SYMBOL_ENUM(name) name,
-    JS_FOR_EACH_WELL_KNOWN_SYMBOL(JS_DEFINE_SYMBOL_ENUM)  // SymbolCode::iterator, etc.
-#undef JS_DEFINE_SYMBOL_ENUM
-    Limit,
+    iterator,                       // well-known symbols
+    match,
+    species,
+    toPrimitive,
     InSymbolRegistry = 0xfffffffe,  // created by Symbol.for() or JS::GetSymbolFor()
     UniqueSymbol = 0xffffffff       // created by Symbol() or JS::NewSymbol()
 };
 
 /* For use in loops that iterate over the well-known symbols. */
-const size_t WellKnownSymbolLimit = size_t(SymbolCode::Limit);
+const size_t WellKnownSymbolLimit = 4;
 
 /**
  * Return the SymbolCode telling what sort of symbol `symbol` is.
  *
  * A symbol's SymbolCode never changes once it is created.
  */
 JS_PUBLIC_API(SymbolCode)
 GetSymbolCode(Handle<Symbol*> symbol);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/unscopables-basics.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// Basics of @@unscopables support.
-
-// In with(obj), if obj[@@unscopables][id] is truthy, then the identifier id
-// is not present as a binding in the with-block's scope.
-var x = "global";
-with ({x: "with", [Symbol.unscopables]: {x: true}})
-    assertEq(x, "global");
-
-// But if obj[@@unscopables][id] is false or not present, there is a binding.
-with ({y: "with", z: "with", [Symbol.unscopables]: {y: false}}) {
-    assertEq(y, "with");
-    assertEq(z, "with");
-}
-
-// ToBoolean(obj[@@unscopables][id]) determines whether there's a binding.
-let someValues = [0, -0, NaN, "", undefined, null, "x", {}, []];
-for (let v of someValues) {
-    with ({x: "with", [Symbol.unscopables]: {x: v}})
-        assertEq(x, v ? "global" : "with");
-}
-
-reportCompare(0, 0);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/unscopables-closures.js
+++ /dev/null
@@ -1,23 +0,0 @@
-// @@unscopables continues to work after exiting the relevant `with` block,
-// if the environment is captured by a closure.
-
-let env = {
-    x: 9000,
-    [Symbol.unscopables]: {x: true}
-};
-
-function make_adder(x) {
-    with (env)
-        return function (y) { return x + y; };
-}
-assertEq(make_adder(3)(10), 13);
-
-// Same test, but with a bunch of different parts for bad luck
-let x = 500;
-function make_adder_with_eval() {
-    with (env)
-        return eval('y => eval("x + y")');
-}
-assertEq(make_adder_with_eval()(10), 510);
-
-reportCompare(0, 0);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/unscopables-const.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// @@unscopables prevents a property from having any effect on assigning to a
-// const binding (which is an error).
-
-const x = 1;
-with ({x: 1, [Symbol.unscopables]: {x: true}})
-    assertThrowsInstanceOf(() => {x = 2;}, TypeError);
-
-reportCompare(0, 0);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/unscopables-delete.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// If obj[@@unscopables][id], then `delete id` works across `with (obj)` scope.
-
-this.niche = 7;
-let obj = { niche: 8, [Symbol.unscopables]: { niche: true } };
-with (obj) {
-    delete niche;
-}
-
-assertEq(obj.niche, 8);
-assertEq("niche" in this, false);
-
-// Same thing, but delete a variable introduced by sloppy direct eval.
-this.niche = 9;
-function f() {
-    eval("var niche = 10;");
-    with (obj) {
-        assertEq(niche, 10);
-        delete niche;
-    }
-    assertEq(niche, 9);
-}
-
-// Of course none of this affects a qualified delete.
-assertEq(delete this.niche, true);
-assertEq("niche" in this, false);
-
-reportCompare(0, 0);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/unscopables-getters.js
+++ /dev/null
@@ -1,41 +0,0 @@
-// @@unscopables checks can call getters.
-
-// The @@unscopables property itself can be a getter.
-let hit1 = 0;
-let x = "global x";
-let env1 = {
-    x: "env1.x",
-    get [Symbol.unscopables]() {
-        hit1++;
-        return {x: true};
-    }
-};
-with (env1)
-    assertEq(x, "global x");
-assertEq(hit1, 1);
-
-// It can throw; the exception is propagated out.
-function Fit() {}
-with ({x: 0, get [Symbol.unscopables]() { throw new Fit; }})
-    assertThrowsInstanceOf(() => x, Fit);
-
-// Individual properties on the @@unscopables object can have getters.
-let hit2 = 0;
-let env2 = {
-    x: "env2.x",
-    [Symbol.unscopables]: {
-        get x() {
-            hit2++;
-            return true;
-        }
-    }
-};
-with (env2)
-    assertEq(x, "global x");
-assertEq(hit2, 1);
-
-// And they can throw.
-with ({x: 0, [Symbol.unscopables]: {get x() { throw new Fit; }}})
-    assertThrowsInstanceOf(() => x, Fit);
-
-reportCompare(0, 0);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/unscopables-global.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// @@unscopables does not affect the global environment.
-
-this.x = "global property x";
-let y = "global lexical y";
-this[Symbol.unscopables] = {x: true, y: true};
-assertEq(x, "global property x");
-assertEq(y, "global lexical y");
-assertEq(eval("x"), "global property x");
-assertEq(eval("y"), "global lexical y");
-
-// But it does affect `with` statements targeting the global object.
-{
-    let x = "local x";
-    with (this)
-        assertEq(x, "local x");
-}
-
-reportCompare(0, 0);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/unscopables-ignored.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// In these cases, @@unscopables should not be consulted.
-
-// Because obj has no properties `assertEq` or `x`,
-// obj[@@unscopables] is not checked here:
-var obj = {
-    get [Symbol.unscopables]() {
-        throw "tried to read @@unscopables";
-    }
-};
-var x = 3;
-with (obj)
-    assertEq(x, 3);
-
-// If @@unscopables is present but not an object, it is ignored:
-for (let nonObject of [undefined, null, "nothing", Symbol.for("moon")]) {
-    let y = 4;
-    let obj2 = {[Symbol.unscopables]: nonObject, y: 5};
-    with (obj2)
-        assertEq(y, 5);
-}
-
-reportCompare(0, 0);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/unscopables-miss.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// Trying to access a binding that doesn't exist due to @@unscopables
-// is a ReferenceError.
-
-with ({x: 1, [Symbol.unscopables]: {x: true}})
-    assertThrowsInstanceOf(() => x, ReferenceError);
-
-reportCompare(0, 0);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/unscopables-mutation-frozen.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// When env[@@unscopables].x changes, bindings can appear even if env is inextensible.
-
-let x = "global";
-let unscopables = {x: true};
-let env = Object.create(null);
-env[Symbol.unscopables] = unscopables;
-env.x = "object";
-Object.freeze(env);
-
-for (let i = 0; i < 1004; i++) {
-    if (i === 1000)
-        unscopables.x = false;
-    with (env) {
-        assertEq(x, i < 1000 ? "global" : "object");
-    }
-}
-
-reportCompare(0, 0);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/unscopables-mutation.js
+++ /dev/null
@@ -1,44 +0,0 @@
-// When obj[@@unscopables].x changes, bindings appear and disappear accordingly.
-
-let x = "global";
-function getX() { return x; }
-
-let unscopables = {x: true};
-let obj = {x: "obj", [Symbol.unscopables]: unscopables};
-
-with (obj) {
-    assertEq(x, "global");
-    x = "global-1";
-    assertEq(x, "global-1");
-    assertEq(obj.x, "obj");
-
-    unscopables.x = false;  // suddenly x appears in the with-environment
-
-    assertEq(x, "obj");
-    x = "obj-1";
-    assertEq(getX(), "global-1");  // unchanged
-    assertEq(obj.x, "obj-1");
-
-    unscopables.x = true;  // *poof*
-
-    assertEq(x, "global-1");
-    x = "global-2";
-    assertEq(getX(), "global-2");
-    assertEq(obj.x, "obj-1");  // unchanged
-
-    // The determination of which binding is assigned happens when the LHS of
-    // assignment is evaluated, before the RHS. This is observable if we make
-    // the binding appear or disappear during evaluation of the RHS, before
-    // assigning.
-    x = (unscopables.x = false, "global-3");
-    assertEq(getX(), "global-3");
-    assertEq(obj.x, "obj-1");
-
-    x = (unscopables.x = true, "obj-2");
-    assertEq(getX(), "global-3");
-    assertEq(obj.x, "obj-2");
-}
-
-assertEq(x, "global-3");
-
-reportCompare(0, 0);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/unscopables-proto.js
+++ /dev/null
@@ -1,39 +0,0 @@
-// @@unscopables treats properties found on prototype chains the same as other
-// properties.
-
-const x = "global x";
-const y = "global y";
-
-// obj[@@unscopables].x works when obj.x is inherited via the prototype chain.
-let proto = {x: "object x", y: "object y"};
-let env = Object.create(proto);
-env[Symbol.unscopables] = {x: true, y: false};
-with (env) {
-    assertEq(x, "global x");
-    assertEq(delete x, false);
-    assertEq(y, "object y");
-}
-assertEq(env.x, "object x");
-
-// @@unscopables works if is inherited via the prototype chain.
-env = {
-    x: "object",
-    [Symbol.unscopables]: {x: true, y: true}
-};
-for (let i = 0; i < 50; i++)
-    env = Object.create(env);
-env.y = 1;
-with (env) {
-    assertEq(x, "global x");
-    assertEq(y, "global y");
-}
-
-// @@unscopables works if the obj[@@unscopables][id] property is inherited.
-env = {
-    x: "object",
-    [Symbol.unscopables]: Object.create({x: true})
-};
-with (env)
-    assertEq(x, "global x");
-
-reportCompare(0, 0);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/unscopables-proxy.js
+++ /dev/null
@@ -1,46 +0,0 @@
-// Object operations are performed in the right order, as observed by proxies.
-
-let log = [];
-function LoggingProxyHandlerWrapper(name, handler={}) {
-    return new Proxy(handler, {
-        get(t, id) {
-            let method = handler[id];
-            return function (...args) {
-                log.push([name + "." + id, ...args.filter(v => typeof v !== "object")]);
-                if (method === undefined)
-                    return Reflect[id].apply(null, args);
-                return method.apply(this, args);
-            };
-        }
-    });
-}
-
-function LoggingProxy(name, target) {
-    return new Proxy(target, new LoggingProxyHandlerWrapper(name));
-}
-
-let proto = {x: 44};
-let proto_proxy = new LoggingProxy("proto", proto);
-let unscopables = {x: true};
-let unscopables_proxy = new LoggingProxy("unscopables", {x: true});
-let env = Object.create(proto_proxy, {
-    [Symbol.unscopables]: { value: unscopables_proxy }
-});
-let env_proxy = new LoggingProxy("env", env);
-
-let x = 11;
-function f() {
-    with (env_proxy)
-        return x;
-}
-
-assertEq(f(), 11);
-
-assertDeepEq(log, [
-    ["env.has", "x"],
-    ["proto.has", "x"],
-    ["env.get", Symbol.unscopables],
-    ["unscopables.get", "x"]
-]);
-
-reportCompare(0, 0);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/unscopables-strict.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// Strict assignment to the name of a property that's masked by @@unscopables
-// throws a ReferenceError.
-
-let env = {k: 1};
-let f;
-with (env) {
-    f = function () {
-        "use strict";
-        k = 2;
-    };
-}
-
-f();
-assertEq(env.k, 2);
-
-env[Symbol.unscopables] = {k: true};
-assertThrowsInstanceOf(f, ReferenceError);
-
-// @@unscopables is tested when the LHS of assignment is evaluated, so there is
-// no effect on the assignment if it is changed while evaluating the RHS.
-let g;
-with (env) {
-    g = function () {
-        "use strict";
-        k = (env[Symbol.unscopables].k = true, 3);
-    }
-}
-env[Symbol.unscopables].k = false;
-g();
-assertEq(env.k, 3);
-
-reportCompare(0, 0);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/unscopables-tdz.js
+++ /dev/null
@@ -1,9 +0,0 @@
-// Accessing an uninitialized variable due to @@unscopables is still a ReferenceError.
-
-with ({x: 1, [Symbol.unscopables]: {x: true}})
-    assertThrowsInstanceOf(() => x, ReferenceError);
-
-let x;
-
-reportCompare(0, 0);
-
--- a/js/src/tests/ecma_6/Symbol/well-known.js
+++ b/js/src/tests/ecma_6/Symbol/well-known.js
@@ -1,17 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/ */
 
 var names = [
     "iterator",
     "match",
     "species",
-    "toPrimitive",
-    "unscopables"
 ];
 
 for (var name of names) {
     // Well-known symbols exist.
     assertEq(typeof Symbol[name], "symbol");
 
     // They are never in the registry.
     assertEq(Symbol[name] !== Symbol.for("Symbol." + name), true);
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -288,21 +288,21 @@
     macro(null, null, "null") \
     macro(symbol, symbol, "symbol") \
     /* Well-known atom names must be continuous and ordered, matching \
      * enum JS::SymbolCode in jsapi.h. */ \
     macro(iterator, iterator, "iterator") \
     macro(match, match, "match") \
     macro(species, species, "species") \
     macro(toPrimitive, toPrimitive, "toPrimitive") \
-    macro(unscopables, unscopables, "unscopables") \
     /* Same goes for the descriptions of the well-known symbols. */ \
     macro(Symbol_hasInstance, Symbol_hasInstance, "Symbol.hasInstance") \
     macro(Symbol_isConcatSpreadable, Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") \
     macro(Symbol_iterator, Symbol_iterator, "Symbol.iterator") \
     macro(Symbol_match,    Symbol_match,    "Symbol.match") \
     macro(Symbol_species,  Symbol_species,  "Symbol.species") \
     macro(Symbol_toPrimitive, Symbol_toPrimitive, "Symbol.toPrimitive") \
+    macro(Symbol_toStringTag, Symbol_toStringTag, "Symbol.toStringTag") \
     macro(Symbol_unscopables, Symbol_unscopables, "Symbol.unscopables") \
     /* Function names for properties named by symbols. */ \
     macro(Symbol_iterator_fun, Symbol_iterator_fun, "[Symbol.iterator]") \
 
 #endif /* vm_CommonPropertyNames_h */
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -8861,24 +8861,16 @@ DebuggerEnv_getVariable(JSContext* cx, u
     RootedValue v(cx);
     {
         Maybe<AutoCompartment> ac;
         ac.emplace(cx, env);
 
         /* This can trigger getters. */
         ErrorCopier ec(ac);
 
-        bool found;
-        if (!HasProperty(cx, env, id, &found))
-            return false;
-        if (!found) {
-            args.rval().setUndefined();
-            return true;
-        }
-
         // For DebugScopeObjects, we get sentinel values for optimized out
         // slots and arguments instead of throwing (the default behavior).
         //
         // See wrapDebuggeeValue for how the sentinel values are wrapped.
         if (env->is<DebugScopeObject>()) {
             if (!env->as<DebugScopeObject>().getMaybeSentinelValue(cx, id, &v))
                 return false;
         } else {
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -442,19 +442,20 @@ namespace js {
  * field is a smart pointer that's immutable once initialized.
  * `rt->wellKnownSymbols->iterator` is convertible to Handle<Symbol*>.
  *
  * Well-known symbols are never GC'd. The description() of each well-known
  * symbol is a permanent atom.
  */
 struct WellKnownSymbols
 {
-#define DECLARE_SYMBOL(name) js::ImmutableSymbolPtr name;
-    JS_FOR_EACH_WELL_KNOWN_SYMBOL(DECLARE_SYMBOL)
-#undef DECLARE_SYMBOL
+    js::ImmutableSymbolPtr iterator;
+    js::ImmutableSymbolPtr match;
+    js::ImmutableSymbolPtr species;
+    js::ImmutableSymbolPtr toPrimitive;
 
     const ImmutableSymbolPtr& get(size_t u) const {
         MOZ_ASSERT(u < JS::WellKnownSymbolLimit);
         const ImmutableSymbolPtr* symbols = reinterpret_cast<const ImmutableSymbolPtr*>(this);
         return symbols[u];
     }
 
     const ImmutableSymbolPtr& get(JS::SymbolCode code) const {
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -750,84 +750,44 @@ DynamicWithObject::create(JSContext* cx,
     obj->setEnclosingScope(enclosing);
     obj->setFixedSlot(OBJECT_SLOT, ObjectValue(*object));
     obj->setFixedSlot(THIS_SLOT, thisv);
     obj->setFixedSlot(KIND_SLOT, Int32Value(kind));
 
     return obj;
 }
 
-/* Implements ES6 8.1.1.2.1 HasBinding steps 7-9. */
-static bool
-CheckUnscopables(JSContext *cx, HandleObject obj, HandleId id, bool *scopable)
-{
-    RootedId unscopablesId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols()
-                                                .get(JS::SymbolCode::unscopables)));
-    RootedValue v(cx);
-    if (!GetProperty(cx, obj, obj, unscopablesId, &v))
-        return false;
-    if (v.isObject()) {
-        RootedObject unscopablesObj(cx, &v.toObject());
-        if (!GetProperty(cx, unscopablesObj, unscopablesObj, id, &v))
-            return false;
-        *scopable = !ToBoolean(v);
-    } else {
-        *scopable = true;
-    }
-    return true;
-}
-
 static bool
 with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
                     MutableHandleObject objp, MutableHandleShape propp)
 {
     if (JSID_IS_ATOM(id, cx->names().dotThis)) {
         objp.set(nullptr);
         propp.set(nullptr);
         return true;
     }
     RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
-    if (!LookupProperty(cx, actual, id, objp, propp))
-        return false;
-
-    if (propp) {
-        bool scopable;
-        if (!CheckUnscopables(cx, actual, id, &scopable))
-            return false;
-        if (!scopable) {
-            objp.set(nullptr);
-            propp.set(nullptr);
-        }
-    }
-    return true;
+    return LookupProperty(cx, actual, id, objp, propp);
 }
 
 static bool
 with_DefineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle<PropertyDescriptor> desc,
                     ObjectOpResult& result)
 {
     MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
     RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
     return DefineProperty(cx, actual, id, desc, result);
 }
 
 static bool
 with_HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
 {
     MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
     RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
-
-    // ES 8.1.1.2.1 step 3-5.
-    if (!HasProperty(cx, actual, id, foundp))
-        return false;
-    if (!*foundp)
-        return true;
-
-    // Steps 7-10. (Step 6 is a no-op.)
-    return CheckUnscopables(cx, actual, id, foundp);
+    return HasProperty(cx, actual, id, foundp);
 }
 
 static bool
 with_GetProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id,
                  MutableHandleValue vp)
 {
     MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
     RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
@@ -2310,35 +2270,22 @@ class DebugScopeProxy : public BaseProxy
                 return false;
         }
 
         // DynamicWithObject isn't a very good proxy.  It doesn't have a
         // JSNewEnumerateOp implementation, because if it just delegated to the
         // target object, the object would indicate that native enumeration is
         // the thing to do, but native enumeration over the DynamicWithObject
         // wrapper yields no properties.  So instead here we hack around the
-        // issue: punch a hole through to the with object target, then manually
-        // examine @@unscopables.
-        bool isWith = scope->is<DynamicWithObject>();
-        Rooted<JSObject*> target(cx, (isWith ? &scope->as<DynamicWithObject>().object() : scope));
+        // issue, and punch a hole through to the with object target.
+        Rooted<JSObject*> target(cx, (scope->is<DynamicWithObject>()
+                                      ? &scope->as<DynamicWithObject>().object() : scope));
         if (!GetPropertyKeys(cx, target, JSITER_OWNONLY, &props))
             return false;
 
-        if (isWith) {
-            size_t j = 0;
-            for (size_t i = 0; i < props.length(); i++) {
-                bool inScope;
-                if (!CheckUnscopables(cx, scope, props[i], &inScope))
-                    return false;
-                if (inScope)
-                    props[j++].set(props[i]);
-            }
-            props.resize(j);
-        }
-
         /*
          * Function scopes are optimized to not contain unaliased variables so
          * they must be manually appended here.
          */
         if (isFunctionScope(*scope)) {
             RootedScript script(cx, scope->as<CallObject>().callee().nonLazyScript());
             for (BindingIter bi(script); bi; bi++) {
                 if (!bi->aliased() && !props.append(NameToId(bi->name())))