From f187b2dfeb04ac2c168e4afe0eaae8cc534dddd0 Mon Sep 17 00:00:00 2001 From: Cameron Kaiser Date: Sun, 14 Jun 2020 09:57:59 -0700 Subject: [PATCH] closes #610: M1317422 + tests --- js/public/Class.h | 2 +- js/src/jsapi.cpp | 6 + .../test262/built-ins/global/global-object.js | 28 ++++ .../built-ins/global/property-descriptor.js | 16 ++ .../tests/test262/built-ins/global/shell.js | 1 + js/src/tests/test262/built-ins/shell.js | 1 + js/src/tests/test262/shell.js | 145 ++++++++++++++++++ js/src/vm/CommonPropertyNames.h | 1 + js/src/vm/GlobalObject.cpp | 34 ++++ js/src/vm/GlobalObject.h | 2 + 10 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 js/src/tests/test262/built-ins/global/global-object.js create mode 100644 js/src/tests/test262/built-ins/global/property-descriptor.js create mode 100644 js/src/tests/test262/built-ins/global/shell.js diff --git a/js/public/Class.h b/js/public/Class.h index 29bf814d3..b1b39081d 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -719,7 +719,7 @@ struct JSClass { // application. #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5 #define JSCLASS_GLOBAL_SLOT_COUNT \ - (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 3 + 36) + (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 3 + 37) #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \ (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n))) #define JSCLASS_GLOBAL_FLAGS \ diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index a59dac231..14a6e207a 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -1079,6 +1079,11 @@ JS_ResolveStandardClass(JSContext* cx, HandleObject obj, HandleId id, bool* reso JSPROP_PERMANENT | JSPROP_READONLY | JSPROP_RESOLVING); } + // Resolve a "globalThis" self-referential property if necessary. + if (idAtom == cx->names().globalThis) { + return GlobalObject::maybeResolveGlobalThis(cx, global, resolved); + } + /* Try for class constructors/prototypes named by well-known atoms. */ stdnm = LookupStdName(cx->names(), idAtom, standard_class_names); @@ -1128,6 +1133,7 @@ JS_MayResolveStandardClass(const JSAtomState& names, jsid id, JSObject* maybeObj JSAtom* atom = JSID_TO_ATOM(id); return atom == names.undefined || + atom == names.globalThis || LookupStdName(names, atom, standard_class_names) || LookupStdName(names, atom, builtin_property_names); } diff --git a/js/src/tests/test262/built-ins/global/global-object.js b/js/src/tests/test262/built-ins/global/global-object.js new file mode 100644 index 000000000..7e757e8f4 --- /dev/null +++ b/js/src/tests/test262/built-ins/global/global-object.js @@ -0,0 +1,28 @@ +// Copyright (C) 2016 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-other-properties-of-the-global-object-globalThis +description: "'globalThis' should be the global object" +author: Jordan Harband +features: [globalThis] +---*/ + +assert.sameValue(this, globalThis); +assert.sameValue(globalThis.globalThis, globalThis); + +assert.sameValue(Array, globalThis.Array); +assert.sameValue(Boolean, globalThis.Boolean); +assert.sameValue(Date, globalThis.Date); +assert.sameValue(Error, globalThis.Error); +assert.sameValue(Function, globalThis.Function); +assert.sameValue(JSON, globalThis.JSON); +assert.sameValue(Math, globalThis.Math); +assert.sameValue(Number, globalThis.Number); +assert.sameValue(RegExp, globalThis.RegExp); +assert.sameValue(String, globalThis.String); + +var globalVariable = {}; +assert.sameValue(globalVariable, globalThis.globalVariable); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/global/property-descriptor.js b/js/src/tests/test262/built-ins/global/property-descriptor.js new file mode 100644 index 000000000..0c34c97c6 --- /dev/null +++ b/js/src/tests/test262/built-ins/global/property-descriptor.js @@ -0,0 +1,16 @@ +// Copyright (C) 2016 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-other-properties-of-the-global-object-global +description: "'globalThis' should be writable, non-enumerable, and configurable" +author: Jordan Harband +includes: [propertyHelper.js] +features: [globalThis] +---*/ + +verifyNotEnumerable(this, 'globalThis'); +verifyWritable(this, 'globalThis'); +verifyConfigurable(this, 'globalThis'); + +reportCompare(0, 0); diff --git a/js/src/tests/test262/built-ins/global/shell.js b/js/src/tests/test262/built-ins/global/shell.js new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/js/src/tests/test262/built-ins/global/shell.js @@ -0,0 +1 @@ + diff --git a/js/src/tests/test262/built-ins/shell.js b/js/src/tests/test262/built-ins/shell.js index e69de29bb..8b1378917 100644 --- a/js/src/tests/test262/built-ins/shell.js +++ b/js/src/tests/test262/built-ins/shell.js @@ -0,0 +1 @@ + diff --git a/js/src/tests/test262/shell.js b/js/src/tests/test262/shell.js index b70bb5dbb..3832a23f1 100644 --- a/js/src/tests/test262/shell.js +++ b/js/src/tests/test262/shell.js @@ -938,3 +938,148 @@ var fnGlobalObject = (function() var global = Function("return this")(); return function fnGlobalObject() { return global; }; })(); + +/* hack */ +function _assert(c, s) +{ + if (!c) $ERROR(s); +} +var assert = { + sameValue : function(x, y) { + assertEq(x,y); + } +}; + +function isConfigurable(obj, name) { + var hasOwnProperty = Object.prototype.hasOwnProperty; + try { + delete obj[name]; + } catch (e) { + if (!(e instanceof TypeError)) { + $ERROR("Expected TypeError, got " + e); + } + } + return !hasOwnProperty.call(obj, name); +} + +function isEnumerable(obj, name) { + var stringCheck = false; + + if (typeof name === "string") { + for (var x in obj) { + if (x === name) { + stringCheck = true; + break; + } + } + } else { + // skip it if name is not string, works for Symbol names. + stringCheck = true; + } + + return stringCheck && + Object.prototype.hasOwnProperty.call(obj, name) && + Object.prototype.propertyIsEnumerable.call(obj, name); +} + +function isSameValue(a, b) { + if (a === 0 && b === 0) return 1 / a === 1 / b; + if (a !== a && b !== b) return true; + + return a === b; +} + +var __isArray = Array.isArray; +function isWritable(obj, name, verifyProp, value) { + var unlikelyValue = __isArray(obj) && name === "length" ? + Math.pow(2, 32) - 1 : + "unlikelyValue"; + var newValue = value || unlikelyValue; + var hadValue = Object.prototype.hasOwnProperty.call(obj, name); + var oldValue = obj[name]; + var writeSucceeded; + + try { + obj[name] = newValue; + } catch (e) { + if (!(e instanceof TypeError)) { + $ERROR("Expected TypeError, got " + e); + } + } + + writeSucceeded = isSameValue(obj[verifyProp || name], newValue); + + // Revert the change only if it was successful (in other cases, reverting + // is unnecessary and may trigger exceptions for certain property + // configurations) + if (writeSucceeded) { + if (hadValue) { + obj[name] = oldValue; + } else { + delete obj[name]; + } + } + + return writeSucceeded; +} + +function verifyEqualTo(obj, name, value) { + if (!isSameValue(obj[name], value)) { + $ERROR("Expected obj[" + String(name) + "] to equal " + value + + ", actually " + obj[name]); + } +} + +function verifyWritable(obj, name, verifyProp, value) { + if (!verifyProp) { + _assert(Object.getOwnPropertyDescriptor(obj, name).writable, + "Expected obj[" + String(name) + "] to have writable:true."); + } + if (!isWritable(obj, name, verifyProp, value)) { + $ERROR("Expected obj[" + String(name) + "] to be writable, but was not."); + } +} + +function verifyNotWritable(obj, name, verifyProp, value) { + if (!verifyProp) { + _assert(!Object.getOwnPropertyDescriptor(obj, name).writable, + "Expected obj[" + String(name) + "] to have writable:false."); + } + if (isWritable(obj, name, verifyProp)) { + $ERROR("Expected obj[" + String(name) + "] NOT to be writable, but was."); + } +} + +function verifyEnumerable(obj, name) { + _assert(Object.getOwnPropertyDescriptor(obj, name).enumerable, + "Expected obj[" + String(name) + "] to have enumerable:true."); + if (!isEnumerable(obj, name)) { + $ERROR("Expected obj[" + String(name) + "] to be enumerable, but was not."); + } +} + +function verifyNotEnumerable(obj, name) { + _assert(!Object.getOwnPropertyDescriptor(obj, name).enumerable, + "Expected obj[" + String(name) + "] to have enumerable:false."); + if (isEnumerable(obj, name)) { + $ERROR("Expected obj[" + String(name) + "] NOT to be enumerable, but was."); + } +} + +function verifyConfigurable(obj, name) { + _assert(Object.getOwnPropertyDescriptor(obj, name).configurable, + "Expected obj[" + String(name) + "] to have configurable:true."); + if (!isConfigurable(obj, name)) { + $ERROR("Expected obj[" + String(name) + "] to be configurable, but was not." +); + } +} + +function verifyNotConfigurable(obj, name) { + _assert(!Object.getOwnPropertyDescriptor(obj, name).configurable, + "Expected obj[" + String(name) + "] to have configurable:false."); + if (isConfigurable(obj, name)) { + $ERROR("Expected obj[" + String(name) + "] NOT to be configurable, but was." +); + } +} diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index c38283d4b..7a7cf9f24 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -109,6 +109,7 @@ macro(getOwnPropertyNames, getOwnPropertyNames, "getOwnPropertyNames") \ macro(getPropertyDescriptor, getPropertyDescriptor, "getPropertyDescriptor") \ macro(global, global, "global") \ + macro(globalThis, globalThis, "globalThis") \ macro(Handle, Handle, "Handle") \ macro(has, has, "has") \ macro(hasOwn, hasOwn, "hasOwn") \ diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index 734629bb4..2f30c1382 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -238,6 +238,34 @@ GlobalObject::initBuiltinConstructor(JSContext* cx, Handle global return true; } + +// Resolve a "globalThis" self-referential property if necessary, +// per a stage-3 proposal. https://github.com/tc39/ecma262/pull/702 +// +// We could also do this in |FinishObjectClassInit| to trim the global +// resolve hook. Unfortunately, |ToWindowProxyIfWindow| doesn't work then: +// the browser's |nsGlobalWindow::SetNewDocument| invokes Object init +// *before* it sets the global's WindowProxy using |js::SetWindowProxy|. +// +// Refactoring global object creation code to support this approach is a +// challenge for another day. +/* static */ bool +GlobalObject::maybeResolveGlobalThis(JSContext* cx, Handle global, bool* resolved) +{ + if (global->getSlot(GLOBAL_THIS_RESOLVED).isUndefined()) { + RootedValue v(cx, ObjectValue(*ToWindowProxyIfWindow(global))); + if (!DefineProperty(cx, global, cx->names().globalThis, v, nullptr, nullptr, +JSPROP_RESOLVING)) { + return false; + } + + *resolved = true; + global->setSlot(GLOBAL_THIS_RESOLVED, BooleanValue(true)); + } + + return true; +} + GlobalObject* GlobalObject::createInternal(JSContext* cx, const Class* clasp) { @@ -346,6 +374,12 @@ GlobalObject::initStandardClasses(JSContext* cx, Handle global) return false; } + // Resolve a "globalThis" self-referential property if necessary. + bool resolved; + if (!GlobalObject::maybeResolveGlobalThis(cx, global, &resolved)) { + return false; + } + for (size_t k = 0; k < JSProto_LIMIT; ++k) { if (!ensureConstructor(cx, global, static_cast(k))) return false; diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index 345791202..42cd19258 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -119,6 +119,7 @@ class GlobalObject : public NativeObject FOR_OF_PIC_CHAIN, MODULE_RESOLVE_HOOK, WINDOW_PROXY, + GLOBAL_THIS_RESOLVED, /* Total reserved-slot count for global objects. */ RESERVED_SLOTS @@ -167,6 +168,7 @@ class GlobalObject : public NativeObject static bool resolveConstructor(JSContext* cx, Handle global, JSProtoKey key); static bool initBuiltinConstructor(JSContext* cx, Handle global, JSProtoKey key, HandleObject ctor, HandleObject proto); + static bool maybeResolveGlobalThis(JSContext* cx, Handle global, bool* resolved); void setConstructor(JSProtoKey key, const Value& v) { MOZ_ASSERT(key <= JSProto_LIMIT);