diff --git a/dom/base/nsFrameMessageManager.cpp b/dom/base/nsFrameMessageManager.cpp index a14b0e058336..d7ff43d8c086 100644 --- a/dom/base/nsFrameMessageManager.cpp +++ b/dom/base/nsFrameMessageManager.cpp @@ -21,6 +21,7 @@ #include "js/CallAndConstruct.h" // JS::IsCallable, JS_CallFunctionValue #include "js/CompilationAndEvaluation.h" #include "js/CompileOptions.h" +#include "js/EnvironmentChain.h" // JS::EnvironmentChain #include "js/experimental/JSStencil.h" #include "js/GCVector.h" #include "js/JSON.h" @@ -1212,7 +1213,7 @@ void nsMessageManagerScriptExecutor::LoadScriptInternal( } } else { JS::Rooted rval(cx); - JS::RootedVector envChain(cx); + JS::EnvironmentChain envChain(cx, JS::SupportUnscopables::No); if (!envChain.append(aMessageManager)) { return; } diff --git a/dom/base/nsJSUtils.cpp b/dom/base/nsJSUtils.cpp index 48df3ae2d30b..bf7eb34da03c 100644 --- a/dom/base/nsJSUtils.cpp +++ b/dom/base/nsJSUtils.cpp @@ -19,6 +19,7 @@ #include "js/CompilationAndEvaluation.h" #include "js/CompileOptions.h" #include "js/Date.h" +#include "js/EnvironmentChain.h" #include "js/GCVector.h" #include "js/HeapAPI.h" #include "js/Modules.h" @@ -80,7 +81,7 @@ nsresult nsJSUtils::UpdateFunctionDebugMetadata( } nsresult nsJSUtils::CompileFunction(AutoJSAPI& jsapi, - JS::HandleVector aScopeChain, + const JS::EnvironmentChain& aEnvChain, JS::CompileOptions& aOptions, const nsACString& aName, uint32_t aArgCount, const char** aArgArray, @@ -88,12 +89,12 @@ nsresult nsJSUtils::CompileFunction(AutoJSAPI& jsapi, JSObject** aFunctionObject) { JSContext* cx = jsapi.cx(); MOZ_ASSERT(js::GetContextRealm(cx)); - MOZ_ASSERT_IF(aScopeChain.length() != 0, - js::IsObjectInContextCompartment(aScopeChain[0], cx)); + MOZ_ASSERT_IF(aEnvChain.length() != 0, + js::IsObjectInContextCompartment(aEnvChain.chain()[0], cx)); // Do the junk Gecko is supposed to do before calling into JSAPI. - for (size_t i = 0; i < aScopeChain.length(); ++i) { - JS::ExposeObjectToActiveJS(aScopeChain[i]); + for (size_t i = 0; i < aEnvChain.length(); ++i) { + JS::ExposeObjectToActiveJS(aEnvChain.chain()[i]); } // Compile. @@ -106,7 +107,7 @@ nsresult nsJSUtils::CompileFunction(AutoJSAPI& jsapi, } JS::Rooted fun( - cx, JS::CompileFunction(cx, aScopeChain, aOptions, + cx, JS::CompileFunction(cx, aEnvChain, aOptions, PromiseFlatCString(aName).get(), aArgCount, aArgArray, source)); if (!fun) { @@ -122,14 +123,14 @@ bool nsJSUtils::IsScriptable(JS::Handle aEvaluationGlobal) { return xpc::Scriptability::AllowedIfExists(aEvaluationGlobal); } -static bool AddScopeChainItem(JSContext* aCx, nsINode* aNode, - JS::MutableHandleVector aScopeChain) { +static bool AddEnvChainItem(JSContext* aCx, nsINode* aNode, + JS::EnvironmentChain& aEnvChain) { JS::Rooted val(aCx); if (!GetOrCreateDOMReflector(aCx, aNode, &val)) { return false; } - if (!aScopeChain.append(&val.toObject())) { + if (!aEnvChain.append(&val.toObject())) { return false; } @@ -137,11 +138,10 @@ static bool AddScopeChainItem(JSContext* aCx, nsINode* aNode, } /* static */ -bool nsJSUtils::GetScopeChainForElement( - JSContext* aCx, Element* aElement, - JS::MutableHandleVector aScopeChain) { +bool nsJSUtils::GetEnvironmentChainForElement(JSContext* aCx, Element* aElement, + JS::EnvironmentChain& aEnvChain) { for (nsINode* cur = aElement; cur; cur = cur->GetScopeChainParent()) { - if (!AddScopeChainItem(aCx, cur, aScopeChain)) { + if (!AddEnvChainItem(aCx, cur, aEnvChain)) { return false; } } diff --git a/dom/base/nsJSUtils.h b/dom/base/nsJSUtils.h index 8b4c1492c648..f32e21752d50 100644 --- a/dom/base/nsJSUtils.h +++ b/dom/base/nsJSUtils.h @@ -29,6 +29,10 @@ class nsIScriptElement; class nsIScriptGlobalObject; class nsXBLPrototypeBinding; +namespace JS { +class JS_PUBLIC_API EnvironmentChain; +}; + namespace mozilla { union Utf8Unit; @@ -51,7 +55,7 @@ class nsJSUtils { static uint64_t GetCurrentlyRunningCodeInnerWindowID(JSContext* aContext); static nsresult CompileFunction(mozilla::dom::AutoJSAPI& jsapi, - JS::HandleVector aScopeChain, + const JS::EnvironmentChain& aEnvChain, JS::CompileOptions& aOptions, const nsACString& aName, uint32_t aArgCount, const char** aArgArray, @@ -66,10 +70,10 @@ class nsJSUtils { static bool IsScriptable(JS::Handle aEvaluationGlobal); // Returns false if an exception got thrown on aCx. Passing a null - // aElement is allowed; that wil produce an empty aScopeChain. - static bool GetScopeChainForElement( - JSContext* aCx, mozilla::dom::Element* aElement, - JS::MutableHandleVector aScopeChain); + // aElement is allowed; that wil produce an empty aEnvChain. + static bool GetEnvironmentChainForElement(JSContext* aCx, + mozilla::dom::Element* aElement, + JS::EnvironmentChain& aEnvChain); static void ResetTimeZone(); diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp index 19d7e58af177..57ff22b76e5c 100644 --- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -7,7 +7,8 @@ // Microsoft's API Name hackery sucks #undef CreateEvent -#include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin +#include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin +#include "js/EnvironmentChain.h" // JS::EnvironmentChain #include "js/loader/LoadedScript.h" #include "js/loader/ScriptFetchOptions.h" #include "mozilla/Assertions.h" @@ -1219,12 +1220,12 @@ nsresult EventListenerManager::CompileEventHandlerInternal( JSAutoRealm ar(cx, target); // Now that we've entered the realm we actually care about, create our - // scope chain. Note that we start with |element|, not aElement, because - // mTarget is different from aElement in the case, where mTarget is a - // Window, and in that case we do not want the scope chain to include the body - // or the document. - JS::RootedVector scopeChain(cx); - if (!nsJSUtils::GetScopeChainForElement(cx, element, &scopeChain)) { + // environment chain. Note that we start with |element|, not aElement, + // because mTarget is different from aElement in the case, where + // mTarget is a Window, and in that case we do not want the environment chain + // to include the body or the document. + JS::EnvironmentChain envChain(cx, JS::SupportUnscopables::Yes); + if (!nsJSUtils::GetEnvironmentChainForElement(cx, element, envChain)) { return NS_ERROR_OUT_OF_MEMORY; } @@ -1257,7 +1258,7 @@ nsresult EventListenerManager::CompileEventHandlerInternal( .setDeferDebugMetadata(true); JS::Rooted handler(cx); - result = nsJSUtils::CompileFunction(jsapi, scopeChain, options, + result = nsJSUtils::CompileFunction(jsapi, envChain, options, nsAtomCString(aTypeAtom), argCount, argNames, *body, handler.address()); NS_ENSURE_SUCCESS(result, result); diff --git a/js/public/CompilationAndEvaluation.h b/js/public/CompilationAndEvaluation.h index fee0aecbb09d..c8e64b007d22 100644 --- a/js/public/CompilationAndEvaluation.h +++ b/js/public/CompilationAndEvaluation.h @@ -27,6 +27,7 @@ union Utf8Unit; namespace JS { +class JS_PUBLIC_API EnvironmentChain; class JS_PUBLIC_API InstantiateOptions; class JS_PUBLIC_API ReadOnlyCompileOptions; @@ -84,12 +85,12 @@ extern JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx, * objects that should end up on the script's scope chain. */ extern JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx, - JS::HandleObjectVector envChain, + const JS::EnvironmentChain& envChain, JS::Handle script, JS::MutableHandle rval); extern JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx, - JS::HandleObjectVector envChain, + const JS::EnvironmentChain& envChain, JS::Handle script); namespace JS { @@ -108,7 +109,8 @@ extern JS_PUBLIC_API bool Evaluate(JSContext* cx, * the global object on it; that's implicit. It needs to contain the other * objects that should end up on the script's scope chain. */ -extern JS_PUBLIC_API bool Evaluate(JSContext* cx, HandleObjectVector envChain, +extern JS_PUBLIC_API bool Evaluate(JSContext* cx, + const JS::EnvironmentChain& envChain, const ReadOnlyCompileOptions& options, SourceText& srcBuf, MutableHandle rval); @@ -173,7 +175,7 @@ extern JS_PUBLIC_API JSScript* CompileUtf8Path( * global must not be explicitly included in the scope chain. */ extern JS_PUBLIC_API JSFunction* CompileFunction( - JSContext* cx, HandleObjectVector envChain, + JSContext* cx, const JS::EnvironmentChain& envChain, const ReadOnlyCompileOptions& options, const char* name, unsigned nargs, const char* const* argnames, SourceText& srcBuf); @@ -185,7 +187,7 @@ extern JS_PUBLIC_API JSFunction* CompileFunction( * global must not be explicitly included in the scope chain. */ extern JS_PUBLIC_API JSFunction* CompileFunction( - JSContext* cx, HandleObjectVector envChain, + JSContext* cx, const JS::EnvironmentChain& envChain, const ReadOnlyCompileOptions& options, const char* name, unsigned nargs, const char* const* argnames, SourceText& srcBuf); @@ -194,7 +196,7 @@ extern JS_PUBLIC_API JSFunction* CompileFunction( * Rust-friendly ergonomics. */ extern JS_PUBLIC_API JSFunction* CompileFunctionUtf8( - JSContext* cx, HandleObjectVector envChain, + JSContext* cx, const JS::EnvironmentChain& envChain, const ReadOnlyCompileOptions& options, const char* name, unsigned nargs, const char* const* argnames, const char* utf8, size_t length); diff --git a/js/public/EnvironmentChain.h b/js/public/EnvironmentChain.h new file mode 100644 index 000000000000..7d37184b521b --- /dev/null +++ b/js/public/EnvironmentChain.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef js_EnvironmentChain_h +#define js_EnvironmentChain_h + +#include "mozilla/Attributes.h" // MOZ_RAII + +#include // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/GCVector.h" // JS::RootedVector + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSObject; + +namespace JS { + +enum class SupportUnscopables : bool { No = false, Yes = true }; + +/** + * JS::EnvironmentChain stores a list of objects to put on the environment + * chain. + * + * Internally the engine will create a non-syntactic 'with' environment for each + * of these objects. Note that 'with' environments aren't optimized well so you + * should use this class only if you really have to. + * + * The SupportUnscopables enum class controls whether these non-syntactic 'with' + * environments support Symbol.unscopables similar to syntactic 'with' + * statements in JS. + * + * Passing SupportUnscopables::No is better for performance because it lets us + * skip the Symbol.unscopables property lookup. Some Web APIs require supporting + * Symbol.unscopables though. In Firefox, SupportUnscopables::Yes is used for + * event handlers. + */ +class MOZ_RAII JS_PUBLIC_API EnvironmentChain { + JS::RootedObjectVector chain_; + SupportUnscopables supportUnscopables_; + + public: + EnvironmentChain(JSContext* cx, SupportUnscopables supportUnscopables) + : chain_(cx), supportUnscopables_(supportUnscopables) {} + + EnvironmentChain(const EnvironmentChain&) = delete; + void operator=(const EnvironmentChain&) = delete; + + [[nodiscard]] bool append(JSObject* obj) { return chain_.append(obj); } + bool empty() const { return chain_.empty(); } + size_t length() const { return chain_.length(); } + + RootedObjectVector& chain() { return chain_; } + const RootedObjectVector& chain() const { return chain_; } + + void setSupportUnscopables(SupportUnscopables supportUnscopables) { + supportUnscopables_ = supportUnscopables; + } + SupportUnscopables supportUnscopables() const { return supportUnscopables_; } +}; + +} // namespace JS + +#endif /* js_EnvironmentChain_h */ diff --git a/js/public/friend/JSMEnvironment.h b/js/public/friend/JSMEnvironment.h index ad16f2ba6260..36d3337c971b 100644 --- a/js/public/friend/JSMEnvironment.h +++ b/js/public/friend/JSMEnvironment.h @@ -45,6 +45,8 @@ namespace JS { +class JS_PUBLIC_API EnvironmentChain; + /** * Allocate a new environment in the current compartment that is compatible with * JSM shared loading. @@ -68,7 +70,7 @@ extern JS_PUBLIC_API bool ExecuteInJSMEnvironment(JSContext* cx, // temporarily placed on the environment chain. extern JS_PUBLIC_API bool ExecuteInJSMEnvironment( JSContext* cx, Handle script, Handle jsmEnv, - Handle> targetObj); + const EnvironmentChain& targetObj); // Used by native methods to determine the JSMEnvironment of caller if possible // by looking at stack frames. Returns nullptr if top frame isn't a scripted diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp index 2100bf3648a3..04ebbd023d54 100644 --- a/js/src/builtin/Eval.cpp +++ b/js/src/builtin/Eval.cpp @@ -12,6 +12,7 @@ #include "frontend/BytecodeCompiler.h" // frontend::CompileEvalScript #include "gc/HashUtil.h" #include "js/CompilationAndEvaluation.h" +#include "js/EnvironmentChain.h" // JS::EnvironmentChain #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/friend/JSMEnvironment.h" // JS::NewJSMEnvironment, JS::ExecuteInJSMEnvironment, JS::GetJSMEnvironmentOfScriptedCaller, JS::IsJSMEnvironment #include "js/friend/WindowProxy.h" // js::IsWindowProxy @@ -410,7 +411,7 @@ JS_PUBLIC_API bool js::ExecuteInFrameScriptEnvironment( return false; } - RootedObjectVector envChain(cx); + JS::EnvironmentChain envChain(cx, JS::SupportUnscopables::No); if (!envChain.append(objArg)) { return false; } @@ -461,14 +462,13 @@ JS_PUBLIC_API JSObject* JS::NewJSMEnvironment(JSContext* cx) { JS_PUBLIC_API bool JS::ExecuteInJSMEnvironment(JSContext* cx, HandleScript scriptArg, HandleObject varEnv) { - RootedObjectVector emptyChain(cx); + JS::EnvironmentChain emptyChain(cx, JS::SupportUnscopables::No); return ExecuteInJSMEnvironment(cx, scriptArg, varEnv, emptyChain); } -JS_PUBLIC_API bool JS::ExecuteInJSMEnvironment(JSContext* cx, - HandleScript scriptArg, - HandleObject varEnv, - HandleObjectVector targetObj) { +JS_PUBLIC_API bool JS::ExecuteInJSMEnvironment( + JSContext* cx, HandleScript scriptArg, HandleObject varEnv, + const EnvironmentChain& targetObj) { cx->check(varEnv); MOZ_ASSERT( ObjectRealm::get(varEnv).getNonSyntacticLexicalEnvironment(varEnv)); diff --git a/js/src/debugger/Frame.cpp b/js/src/debugger/Frame.cpp index e4f555750e77..c5c15e2be8d2 100644 --- a/js/src/debugger/Frame.cpp +++ b/js/src/debugger/Frame.cpp @@ -41,6 +41,7 @@ #include "jit/JSJitFrameIter.h" // for InlineFrameIterator #include "jit/RematerializedFrame.h" // for RematerializedFrame #include "js/CallArgs.h" // for CallArgs +#include "js/EnvironmentChain.h" // JS::EnvironmentChain #include "js/friend/ErrorMessages.h" // for GetErrorMessage, JSMSG_* #include "js/GCVector.h" // for JS::StackGCVector #include "js/Object.h" // for SetReservedSlot @@ -954,7 +955,7 @@ static WithEnvironmentObject* CreateBindingsEnv( } } - RootedObjectVector envChain(cx); + JS::EnvironmentChain envChain(cx, JS::SupportUnscopables::No); if (!envChain.append(bindingsObj)) { return nullptr; } diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index 453a3107cc5f..ffebc0eba928 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -28,6 +28,7 @@ #include "js/AllocPolicy.h" // js::SystemAllocPolicy, ReportOutOfMemory #include "js/CharacterEncoding.h" // JS_EncodeStringToUTF8 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin +#include "js/EnvironmentChain.h" // JS::SupportUnscopables #include "js/ErrorReport.h" // JS_ReportErrorASCII #include "js/experimental/JSStencil.h" #include "js/GCVector.h" // JS::StackGCVector @@ -612,8 +613,8 @@ static WithEnvironmentObject* CreateExtraBindingsEnvironment( } JS::Rooted globalLexical(cx, &cx->global()->lexicalEnvironment()); - return WithEnvironmentObject::createNonSyntactic(cx, extraBindingsObj, - globalLexical); + return WithEnvironmentObject::createNonSyntactic( + cx, extraBindingsObj, globalLexical, JS::SupportUnscopables::No); } JSScript* frontend::CompileGlobalScriptWithExtraBindings( diff --git a/js/src/jit-test/tests/basic/non-syntactic-with-unscopables.js b/js/src/jit-test/tests/basic/non-syntactic-with-unscopables.js new file mode 100644 index 000000000000..826c7a909cf9 --- /dev/null +++ b/js/src/jit-test/tests/basic/non-syntactic-with-unscopables.js @@ -0,0 +1,15 @@ +// Tests evaluate's supportUnscopables option. +function test(supportUnscopables) { + var env = {x: 1, y: 2}; + Object.defineProperty(env, Symbol.unscopables, {get: function() { + assertEq(supportUnscopables, true); + return {x: false, y: true}; + }}); + + evaluate(`this.gotX = x; try { this.gotY = y; } catch {}`, + {envChainObject: env, supportUnscopables}); + assertEq(env.gotX, 1); + assertEq(env.gotY, supportUnscopables ? undefined : 2); +} +test(false); +test(true); diff --git a/js/src/jsapi-tests/testChromeBuffer.cpp b/js/src/jsapi-tests/testChromeBuffer.cpp index 7ffe2a8ce0d0..97631b7ec236 100644 --- a/js/src/jsapi-tests/testChromeBuffer.cpp +++ b/js/src/jsapi-tests/testChromeBuffer.cpp @@ -9,6 +9,7 @@ #include "js/CallAndConstruct.h" // JS_CallFunctionValue #include "js/CompilationAndEvaluation.h" // JS::CompileFunction #include "js/ContextOptions.h" +#include "js/EnvironmentChain.h" // JS::EnvironmentChain #include "js/GlobalObject.h" // JS_NewGlobalObject #include "js/PropertyAndElement.h" // JS_DefineProperty #include "js/SourceText.h" // JS::Source{Ownership,Text} @@ -87,8 +88,8 @@ BEGIN_TEST(testChromeBuffer) { JS::CompileOptions options(cx); options.setFileAndLine("", 0); - JS::RootedObjectVector emptyScopeChain(cx); - fun = JS::CompileFunction(cx, emptyScopeChain, options, "trusted", 1, + JS::EnvironmentChain emptyEnvChain(cx, JS::SupportUnscopables::No); + fun = JS::CompileFunction(cx, emptyEnvChain, options, "trusted", 1, ¶mName, srcBuf); CHECK(fun); CHECK(JS_DefineProperty(cx, trusted_glob, "trusted", fun, @@ -118,8 +119,8 @@ BEGIN_TEST(testChromeBuffer) { JS::CompileOptions options(cx); options.setFileAndLine("", 0); - JS::RootedObjectVector emptyScopeChain(cx); - fun = JS::CompileFunction(cx, emptyScopeChain, options, "untrusted", 1, + JS::EnvironmentChain emptyEnvChain(cx, JS::SupportUnscopables::No); + fun = JS::CompileFunction(cx, emptyEnvChain, options, "untrusted", 1, ¶mName, srcBuf); CHECK(fun); CHECK(JS_DefineProperty(cx, global, "untrusted", fun, JSPROP_ENUMERATE)); @@ -165,8 +166,8 @@ BEGIN_TEST(testChromeBuffer) { JS::CompileOptions options(cx); options.setFileAndLine("", 0); - JS::RootedObjectVector emptyScopeChain(cx); - fun = JS::CompileFunction(cx, emptyScopeChain, options, "trusted", 1, + JS::EnvironmentChain emptyEnvChain(cx, JS::SupportUnscopables::No); + fun = JS::CompileFunction(cx, emptyEnvChain, options, "trusted", 1, ¶mName, srcBuf); CHECK(fun); CHECK(JS_DefineProperty(cx, trusted_glob, "trusted", fun, @@ -192,8 +193,8 @@ BEGIN_TEST(testChromeBuffer) { JS::CompileOptions options(cx); options.setFileAndLine("", 0); - JS::RootedObjectVector emptyScopeChain(cx); - fun = JS::CompileFunction(cx, emptyScopeChain, options, "untrusted", 1, + JS::EnvironmentChain emptyEnvChain(cx, JS::SupportUnscopables::No); + fun = JS::CompileFunction(cx, emptyEnvChain, options, "untrusted", 1, ¶mName, srcBuf); CHECK(fun); CHECK(JS_DefineProperty(cx, global, "untrusted", fun, JSPROP_ENUMERATE)); @@ -226,8 +227,8 @@ BEGIN_TEST(testChromeBuffer) { JS::CompileOptions options(cx); options.setFileAndLine("", 0); - JS::RootedObjectVector emptyScopeChain(cx); - fun = JS::CompileFunction(cx, emptyScopeChain, options, "trusted", 0, + JS::EnvironmentChain emptyEnvChain(cx, JS::SupportUnscopables::No); + fun = JS::CompileFunction(cx, emptyEnvChain, options, "trusted", 0, nullptr, srcBuf); CHECK(fun); CHECK(JS_DefineProperty(cx, trusted_glob, "trusted", fun, @@ -254,8 +255,8 @@ BEGIN_TEST(testChromeBuffer) { JS::CompileOptions options(cx); options.setFileAndLine("", 0); - JS::RootedObjectVector emptyScopeChain(cx); - fun = JS::CompileFunction(cx, emptyScopeChain, options, "untrusted", 1, + JS::EnvironmentChain emptyEnvChain(cx, JS::SupportUnscopables::No); + fun = JS::CompileFunction(cx, emptyEnvChain, options, "untrusted", 1, ¶mName, srcBuf); CHECK(fun); CHECK(JS_DefineProperty(cx, global, "untrusted", fun, JSPROP_ENUMERATE)); diff --git a/js/src/jsapi-tests/testFunctionBinding.cpp b/js/src/jsapi-tests/testFunctionBinding.cpp index 6a69e3234914..7f5924a0a223 100644 --- a/js/src/jsapi-tests/testFunctionBinding.cpp +++ b/js/src/jsapi-tests/testFunctionBinding.cpp @@ -11,6 +11,7 @@ #include "js/CallAndConstruct.h" #include "js/CompilationAndEvaluation.h" // JS::CompileFunction +#include "js/EnvironmentChain.h" // JS::EnvironmentChain #include "js/SourceText.h" // JS::Source{Ownership,Text} #include "jsapi-tests/tests.h" #include "util/Text.h" @@ -23,7 +24,7 @@ BEGIN_TEST(test_functionBinding) { JS::CompileOptions options(cx); options.setFileAndLine(__FILE__, __LINE__); - JS::RootedObjectVector emptyScopeChain(cx); + JS::EnvironmentChain emptyEnvChain(cx, JS::SupportUnscopables::No); // Named function shouldn't have it's binding. { @@ -33,7 +34,7 @@ BEGIN_TEST(test_functionBinding) { CHECK(srcBuf.init(cx, s1chars, js_strlen(s1chars), JS::SourceOwnership::Borrowed)); - fun = JS::CompileFunction(cx, emptyScopeChain, options, "s1", 0, nullptr, + fun = JS::CompileFunction(cx, emptyEnvChain, options, "s1", 0, nullptr, srcBuf); CHECK(fun); } @@ -52,7 +53,7 @@ BEGIN_TEST(test_functionBinding) { CHECK(srcBuf.init(cx, s2chars, js_strlen(s2chars), JS::SourceOwnership::Borrowed)); - fun = JS::CompileFunction(cx, emptyScopeChain, options, "s2", 0, nullptr, + fun = JS::CompileFunction(cx, emptyEnvChain, options, "s2", 0, nullptr, srcBuf); CHECK(fun); } @@ -69,7 +70,7 @@ BEGIN_TEST(test_functionBinding) { CHECK(srcBuf.init(cx, s3chars, js_strlen(s3chars), JS::SourceOwnership::Borrowed)); - fun = JS::CompileFunction(cx, emptyScopeChain, options, nullptr, 0, nullptr, + fun = JS::CompileFunction(cx, emptyEnvChain, options, nullptr, 0, nullptr, srcBuf); CHECK(fun); } diff --git a/js/src/jsapi-tests/testFunctionNonSyntactic.cpp b/js/src/jsapi-tests/testFunctionNonSyntactic.cpp index b58db0889793..5b481d90ea2d 100644 --- a/js/src/jsapi-tests/testFunctionNonSyntactic.cpp +++ b/js/src/jsapi-tests/testFunctionNonSyntactic.cpp @@ -11,6 +11,7 @@ #include "js/CallAndConstruct.h" #include "js/CompilationAndEvaluation.h" // JS::CompileFunction +#include "js/EnvironmentChain.h" // JS::EnvironmentChain #include "js/PropertyAndElement.h" // JS_DefineProperty #include "js/SourceText.h" // JS::Source{Ownership,Text} #include "jsapi-tests/tests.h" @@ -22,7 +23,7 @@ using namespace js; BEGIN_TEST(testFunctionNonSyntactic) { - JS::RootedObjectVector scopeChain(cx); + JS::EnvironmentChain envChain(cx, JS::SupportUnscopables::No); { JS::RootedObject scopeObj(cx, JS_NewPlainObject(cx)); @@ -30,7 +31,7 @@ BEGIN_TEST(testFunctionNonSyntactic) { JS::RootedValue val(cx); val.setNumber(1); CHECK(JS_DefineProperty(cx, scopeObj, "foo", val, JSPROP_ENUMERATE)); - CHECK(scopeChain.append(scopeObj)); + CHECK(envChain.append(scopeObj)); } { @@ -39,7 +40,7 @@ BEGIN_TEST(testFunctionNonSyntactic) { JS::RootedValue val(cx); val.setNumber(20); CHECK(JS_DefineProperty(cx, scopeObj, "bar", val, JSPROP_ENUMERATE)); - CHECK(scopeChain.append(scopeObj)); + CHECK(envChain.append(scopeObj)); } { @@ -50,8 +51,8 @@ BEGIN_TEST(testFunctionNonSyntactic) { JS::CompileOptions options(cx); options.setFileAndLine(__FILE__, __LINE__); - RootedFunction fun(cx, JS::CompileFunction(cx, scopeChain, options, "test", - 0, nullptr, srcBuf)); + RootedFunction fun(cx, JS::CompileFunction(cx, envChain, options, "test", 0, + nullptr, srcBuf)); CHECK(fun); CHECK(fun->enclosingScope()->kind() == ScopeKind::NonSyntactic); @@ -76,8 +77,8 @@ BEGIN_TEST(testFunctionNonSyntactic) { JS::CompileOptions options(cx); options.setFileAndLine(__FILE__, __LINE__); - RootedFunction fun(cx, JS::CompileFunction(cx, scopeChain, options, "test", - 1, args, srcBuf)); + RootedFunction fun(cx, JS::CompileFunction(cx, envChain, options, "test", 1, + args, srcBuf)); CHECK(fun); CHECK(fun->enclosingScope()->kind() == ScopeKind::NonSyntactic); diff --git a/js/src/jsapi-tests/testJSEvaluateScript.cpp b/js/src/jsapi-tests/testJSEvaluateScript.cpp index 1dfc9ea3cb5f..72c1aa6eacc7 100644 --- a/js/src/jsapi-tests/testJSEvaluateScript.cpp +++ b/js/src/jsapi-tests/testJSEvaluateScript.cpp @@ -3,6 +3,7 @@ */ #include "js/CompilationAndEvaluation.h" +#include "js/EnvironmentChain.h" // JS::EnvironmentChain #include "js/PropertyAndElement.h" // JS_AlreadyHasOwnProperty, JS_HasProperty #include "js/SourceText.h" #include "jsapi-tests/tests.h" @@ -16,13 +17,13 @@ BEGIN_TEST(testJSEvaluateScript) { JS::RootedValue retval(cx); JS::CompileOptions opts(cx); - JS::RootedObjectVector scopeChain(cx); - CHECK(scopeChain.append(obj)); + JS::EnvironmentChain envChain(cx, JS::SupportUnscopables::No); + CHECK(envChain.append(obj)); JS::SourceText srcBuf; CHECK(srcBuf.init(cx, src, js_strlen(src), JS::SourceOwnership::Borrowed)); - CHECK(JS::Evaluate(cx, scopeChain, opts.setFileAndLine(__FILE__, __LINE__), + CHECK(JS::Evaluate(cx, envChain, opts.setFileAndLine(__FILE__, __LINE__), srcBuf, &retval)); bool hasProp = true; diff --git a/js/src/jsapi-tests/testPreserveJitCode.cpp b/js/src/jsapi-tests/testPreserveJitCode.cpp index e27e63f2d1cd..994f5bec2152 100644 --- a/js/src/jsapi-tests/testPreserveJitCode.cpp +++ b/js/src/jsapi-tests/testPreserveJitCode.cpp @@ -7,6 +7,7 @@ #include "jit/Ion.h" // js::jit::IsIonEnabled #include "js/CallAndConstruct.h" // JS::CallFunction #include "js/CompilationAndEvaluation.h" // JS::CompileFunction +#include "js/EnvironmentChain.h" // JS::EnvironmentChain #include "js/GlobalObject.h" // JS_NewGlobalObject #include "js/SourceText.h" // JS::Source{Ownership,Text} #include "jsapi-tests/tests.h" @@ -69,9 +70,9 @@ bool testPreserveJitCode(bool preserveJitCode, unsigned remainingIonScripts) { options.setFileAndLine(__FILE__, 1); JS::RootedFunction fun(cx); - JS::RootedObjectVector emptyScopeChain(cx); - fun = JS::CompileFunction(cx, emptyScopeChain, options, "f", 0, nullptr, - srcBuf); + JS::EnvironmentChain emptyEnvChain(cx, JS::SupportUnscopables::No); + fun = + JS::CompileFunction(cx, emptyEnvChain, options, "f", 0, nullptr, srcBuf); CHECK(fun); RootedValue value(cx); diff --git a/js/src/jsapi-tests/testSourcePolicy.cpp b/js/src/jsapi-tests/testSourcePolicy.cpp index b6f9d4a8d78d..e2e2796ef12e 100644 --- a/js/src/jsapi-tests/testSourcePolicy.cpp +++ b/js/src/jsapi-tests/testSourcePolicy.cpp @@ -5,6 +5,7 @@ #include "mozilla/Utf8.h" // mozilla::Utf8Unit #include "js/CompilationAndEvaluation.h" // JS::CompileFunction, JS::Evaluate +#include "js/EnvironmentChain.h" // JS::EnvironmentChain #include "js/GlobalObject.h" // JS_NewGlobalObject #include "js/MemoryFunctions.h" #include "js/SourceText.h" // JS::Source{Ownership,Text} @@ -41,13 +42,13 @@ BEGIN_TEST(testBug795104) { CHECK(JS::Evaluate(cx, opts, srcBuf, &unused)); JS::RootedFunction fun(cx); - JS::RootedObjectVector emptyScopeChain(cx); + JS::EnvironmentChain emptyEnvChain(cx, JS::SupportUnscopables::No); // But when compiling a function we don't want to use no-rval // mode, since it's not supported for functions. opts.setNoScriptRval(false); - fun = JS::CompileFunction(cx, emptyScopeChain, opts, "f", 0, nullptr, srcBuf); + fun = JS::CompileFunction(cx, emptyEnvChain, opts, "f", 0, nullptr, srcBuf); CHECK(fun); JS_free(cx, s); diff --git a/js/src/jsapi-tests/testStencil.cpp b/js/src/jsapi-tests/testStencil.cpp index 7d6b78d7d8cd..8a71d273b9e0 100644 --- a/js/src/jsapi-tests/testStencil.cpp +++ b/js/src/jsapi-tests/testStencil.cpp @@ -11,6 +11,7 @@ #include "frontend/CompilationStencil.h" #include "js/CompilationAndEvaluation.h" +#include "js/EnvironmentChain.h" // JS::EnvironmentChain #include "js/experimental/CompileScript.h" #include "js/experimental/JSStencil.h" #include "js/Modules.h" @@ -136,11 +137,11 @@ BEGIN_TEST(testStencil_NonSyntactic) { CHECK(obj); CHECK(JS_SetProperty(cx, obj, "x", val)); - JS::RootedObjectVector chain(cx); - CHECK(chain.append(obj)); + JS::EnvironmentChain envChain(cx, JS::SupportUnscopables::No); + CHECK(envChain.append(obj)); JS::RootedValue rval(cx); - CHECK(JS_ExecuteScript(cx, chain, script, &rval)); + CHECK(JS_ExecuteScript(cx, envChain, script, &rval)); CHECK(rval.isNumber() && rval.toNumber() == 42); return true; diff --git a/js/src/moz.build b/js/src/moz.build index f586cbed1740..edbf2db6bd58 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -136,6 +136,7 @@ EXPORTS.js += [ "../public/Conversions.h", "../public/Date.h", "../public/Debug.h", + "../public/EnvironmentChain.h", "../public/Equality.h", "../public/ErrorInterceptor.h", "../public/ErrorReport.h", diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index a8afc08cf806..063ffc5c81f0 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -124,8 +124,9 @@ #include "js/CompilationAndEvaluation.h" #include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions, JS::CompileOptions, JS::OwningCompileOptions, JS::DecodeOptions, JS::InstantiateOptions #include "js/ContextOptions.h" // JS::ContextOptions{,Ref} -#include "js/Debug.h" // JS::dbg::ShouldAvoidSideEffects, JS::ExecutionTrace -#include "js/Equality.h" // JS::SameValue +#include "js/Debug.h" // JS::dbg::ShouldAvoidSideEffects, JS::ExecutionTrace +#include "js/EnvironmentChain.h" // JS::EnvironmentChain +#include "js/Equality.h" // JS::SameValue #include "js/ErrorReport.h" // JS::PrintError #include "js/Exception.h" // JS::StealPendingExceptionStack #include "js/experimental/BindingAllocs.h" // JS_NewObjectWithGivenProtoAndUseAllocSite @@ -2693,7 +2694,7 @@ static bool Evaluate(JSContext* cx, unsigned argc, Value* vp) { bool saveIncrementalBytecode = false; bool execute = true; bool assertEqBytecode = false; - JS::RootedObjectVector envChain(cx); + JS::EnvironmentChain envChain(cx, JS::SupportUnscopables::No); RootedObject callerGlobal(cx, cx->global()); options.setIntroductionType("js shell evaluate") @@ -2781,6 +2782,13 @@ static bool Evaluate(JSContext* cx, unsigned argc, Value* vp) { } } + if (!JS_GetProperty(cx, opts, "supportUnscopables", &v)) { + return false; + } + if (!v.isUndefined()) { + envChain.setSupportUnscopables(JS::SupportUnscopables(ToBoolean(v))); + } + // We cannot load or save the bytecode if we have no object where the // bytecode cache is stored. if (loadBytecode || saveIncrementalBytecode) { @@ -2796,7 +2804,7 @@ static bool Evaluate(JSContext* cx, unsigned argc, Value* vp) { // Wrap the envChainObject list into target realm. JSAutoRealm ar(cx, global); for (size_t i = 0; i < envChain.length(); ++i) { - if (!JS_WrapObject(cx, envChain[i])) { + if (!JS_WrapObject(cx, envChain.chain()[i])) { return false; } } @@ -9746,6 +9754,8 @@ static const JSFunctionSpecWithHelp shell_functions[] = { " envChainObject: object to put on the scope chain, with its fields added\n" " as var bindings, akin to how elements are added to the environment in\n" " event handlers in Gecko.\n" +" supportUnscopables: if true, support Symbol.unscopables lookups for\n" +" envChainObject, similar to (syntactic) with-statements.\n" ), JS_FN_HELP("run", Run, 1, 0, diff --git a/js/src/vm/CompilationAndEvaluation.cpp b/js/src/vm/CompilationAndEvaluation.cpp index 6ef1248b614f..24823539d084 100644 --- a/js/src/vm/CompilationAndEvaluation.cpp +++ b/js/src/vm/CompilationAndEvaluation.cpp @@ -23,6 +23,7 @@ #include "frontend/Parser.h" // frontend::Parser, frontend::ParseGoal #include "js/CharacterEncoding.h" // JS::UTF8Chars, JS::ConstUTF8CharsZ, JS::UTF8CharsToNewTwoByteCharsZ #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin +#include "js/EnvironmentChain.h" // JS::EnvironmentChain #include "js/experimental/JSStencil.h" // JS::Stencil #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/RootingAPI.h" // JS::Rooted @@ -299,7 +300,7 @@ class FunctionCompiler { return funStr_.append(srcBuf.get(), srcBuf.length()); } - JSFunction* finish(HandleObjectVector envChain, + JSFunction* finish(const JS::EnvironmentChain& envChain, const ReadOnlyCompileOptions& optionsArg) { using js::frontend::FunctionSyntaxKind; @@ -381,7 +382,7 @@ class FunctionCompiler { }; JS_PUBLIC_API JSFunction* JS::CompileFunction( - JSContext* cx, HandleObjectVector envChain, + JSContext* cx, const EnvironmentChain& envChain, const ReadOnlyCompileOptions& options, const char* name, unsigned nargs, const char* const* argnames, SourceText& srcBuf) { ManualReportFrontendContext fc(cx); @@ -397,7 +398,7 @@ JS_PUBLIC_API JSFunction* JS::CompileFunction( } JS_PUBLIC_API JSFunction* JS::CompileFunction( - JSContext* cx, HandleObjectVector envChain, + JSContext* cx, const EnvironmentChain& envChain, const ReadOnlyCompileOptions& options, const char* name, unsigned nargs, const char* const* argnames, SourceText& srcBuf) { ManualReportFrontendContext fc(cx); @@ -413,7 +414,7 @@ JS_PUBLIC_API JSFunction* JS::CompileFunction( } JS_PUBLIC_API JSFunction* JS::CompileFunctionUtf8( - JSContext* cx, HandleObjectVector envChain, + JSContext* cx, const EnvironmentChain& envChain, const ReadOnlyCompileOptions& options, const char* name, unsigned nargs, const char* const* argnames, const char* bytes, size_t length) { SourceText srcBuf; @@ -495,7 +496,7 @@ MOZ_NEVER_INLINE static bool ExecuteScript(JSContext* cx, HandleObject envChain, return Execute(cx, script, envChain, rval); } -static bool ExecuteScript(JSContext* cx, HandleObjectVector envChain, +static bool ExecuteScript(JSContext* cx, const JS::EnvironmentChain& envChain, HandleScript script, MutableHandleValue rval) { RootedObject env(cx, CreateNonSyntacticEnvironmentChain(cx, envChain)); if (!env) { @@ -520,13 +521,14 @@ MOZ_NEVER_INLINE JS_PUBLIC_API bool JS_ExecuteScript(JSContext* cx, } MOZ_NEVER_INLINE JS_PUBLIC_API bool JS_ExecuteScript( - JSContext* cx, HandleObjectVector envChain, HandleScript scriptArg, + JSContext* cx, const JS::EnvironmentChain& envChain, HandleScript scriptArg, MutableHandleValue rval) { return ExecuteScript(cx, envChain, scriptArg, rval); } MOZ_NEVER_INLINE JS_PUBLIC_API bool JS_ExecuteScript( - JSContext* cx, HandleObjectVector envChain, HandleScript scriptArg) { + JSContext* cx, const JS::EnvironmentChain& envChain, + HandleScript scriptArg) { RootedValue rval(cx); return ExecuteScript(cx, envChain, scriptArg, &rval); } @@ -576,7 +578,7 @@ JS_PUBLIC_API bool JS::Evaluate(JSContext* cx, srcBuf, rval); } -JS_PUBLIC_API bool JS::Evaluate(JSContext* cx, HandleObjectVector envChain, +JS_PUBLIC_API bool JS::Evaluate(JSContext* cx, const EnvironmentChain& envChain, const ReadOnlyCompileOptions& options, SourceText& srcBuf, MutableHandleValue rval) { diff --git a/js/src/vm/EnvironmentObject.cpp b/js/src/vm/EnvironmentObject.cpp index d5865ff6ffd6..f7d824b84cb8 100644 --- a/js/src/vm/EnvironmentObject.cpp +++ b/js/src/vm/EnvironmentObject.cpp @@ -10,6 +10,7 @@ #include "builtin/Array.h" #include "builtin/ModuleObject.h" +#include "js/EnvironmentChain.h" // JS::EnvironmentChain #include "js/Exception.h" #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit @@ -717,10 +718,9 @@ JSObject* js::GetThisObject(JSObject* obj) { return obj; } -WithEnvironmentObject* WithEnvironmentObject::create(JSContext* cx, - HandleObject object, - HandleObject enclosing, - Handle scope) { +WithEnvironmentObject* WithEnvironmentObject::create( + JSContext* cx, HandleObject object, HandleObject enclosing, + Handle scope, JS::SupportUnscopables supportUnscopables) { Rooted shape(cx, EmptyEnvironmentShape(cx)); if (!shape) { @@ -738,17 +738,22 @@ WithEnvironmentObject* WithEnvironmentObject::create(JSContext* cx, obj->initReservedSlot(OBJECT_SLOT, ObjectValue(*object)); obj->initReservedSlot(THIS_SLOT, ObjectValue(*thisObj)); if (scope) { - obj->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope)); + MOZ_ASSERT(supportUnscopables == JS::SupportUnscopables::Yes, + "with-statements must support Symbol.unscopables"); + obj->initReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT, + PrivateGCThingValue(scope)); } else { - obj->initReservedSlot(SCOPE_SLOT, NullValue()); + Value v = BooleanValue(supportUnscopables == JS::SupportUnscopables::Yes); + obj->initReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT, v); } return obj; } WithEnvironmentObject* WithEnvironmentObject::createNonSyntactic( - JSContext* cx, HandleObject object, HandleObject enclosing) { - return create(cx, object, enclosing, nullptr); + JSContext* cx, HandleObject object, HandleObject enclosing, + JS::SupportUnscopables supportUnscopables) { + return create(cx, object, enclosing, nullptr, supportUnscopables); } static inline bool IsUnscopableDotName(JSContext* cx, HandleId id) { @@ -811,8 +816,9 @@ static bool with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id, } if (propp->isFound()) { - bool scopable; - if (!CheckUnscopables(cx, actual, id, &scopable)) { + bool scopable = true; + if (obj->as().supportUnscopables() && + !CheckUnscopables(cx, actual, id, &scopable)) { return false; } if (!scopable) { @@ -840,7 +846,7 @@ static bool with_HasProperty(JSContext* cx, HandleObject obj, HandleId id, if (!HasProperty(cx, actual, id, foundp)) { return false; } - if (!*foundp) { + if (!*foundp || !obj->as().supportUnscopables()) { return true; } @@ -938,7 +944,7 @@ const JSClass NonSyntacticVariablesObject::class_ = { }; NonSyntacticLexicalEnvironmentObject* js::CreateNonSyntacticEnvironmentChain( - JSContext* cx, HandleObjectVector envChain) { + JSContext* cx, const JS::EnvironmentChain& envChain) { // Callers are responsible for segregating the NonSyntactic case from simple // compilation cases. MOZ_RELEASE_ASSERT(!envChain.empty()); @@ -2437,9 +2443,12 @@ class DebugEnvironmentProxyHandler : public NurseryAllocableProxyHandler { if (isWith) { size_t j = 0; + bool supportUnscopables = + env->as().supportUnscopables(); for (size_t i = 0; i < props.length(); i++) { - bool inScope; - if (!CheckUnscopables(cx, env, props[i], &inScope)) { + bool inScope = true; + if (supportUnscopables && + !CheckUnscopables(cx, env, props[i], &inScope)) { return false; } if (inScope) { @@ -3429,13 +3438,14 @@ JSObject* js::GetDebugEnvironmentForGlobalLexicalEnvironment(JSContext* cx) { } WithEnvironmentObject* js::CreateObjectsForEnvironmentChain( - JSContext* cx, HandleObjectVector chain, HandleObject terminatingEnv) { + JSContext* cx, const JS::EnvironmentChain& chain, + HandleObject terminatingEnv) { MOZ_ASSERT(!chain.empty()); #ifdef DEBUG for (size_t i = 0; i < chain.length(); ++i) { - cx->check(chain[i]); - MOZ_ASSERT(!chain[i]->isUnqualifiedVarObj()); + cx->check(chain.chain()[i]); + MOZ_ASSERT(!chain.chain()[i]->isUnqualifiedVarObj()); } #endif @@ -3444,8 +3454,8 @@ WithEnvironmentObject* js::CreateObjectsForEnvironmentChain( Rooted withEnv(cx); RootedObject enclosingEnv(cx, terminatingEnv); for (size_t i = chain.length(); i > 0;) { - withEnv = - WithEnvironmentObject::createNonSyntactic(cx, chain[--i], enclosingEnv); + withEnv = WithEnvironmentObject::createNonSyntactic( + cx, chain.chain()[--i], enclosingEnv, chain.supportUnscopables()); if (!withEnv) { return nullptr; } @@ -3470,14 +3480,24 @@ JSObject* WithEnvironmentObject::withThis() const { } bool WithEnvironmentObject::isSyntactic() const { - Value v = getReservedSlot(SCOPE_SLOT); - MOZ_ASSERT(v.isPrivateGCThing() || v.isNull()); + Value v = getReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT); + MOZ_ASSERT(v.isPrivateGCThing() || v.isBoolean()); return v.isPrivateGCThing(); } +bool WithEnvironmentObject::supportUnscopables() const { + if (isSyntactic()) { + return true; + } + Value v = getReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT); + MOZ_ASSERT(v.isBoolean()); + return v.isTrue(); +} + WithScope& WithEnvironmentObject::scope() const { MOZ_ASSERT(isSyntactic()); - return *static_cast(getReservedSlot(SCOPE_SLOT).toGCThing()); + Value v = getReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT); + return *static_cast(v.toGCThing()); } ModuleEnvironmentObject* js::GetModuleEnvironmentForScript(JSScript* script) { diff --git a/js/src/vm/EnvironmentObject.h b/js/src/vm/EnvironmentObject.h index 2e43569a43fc..d72749cbe3ad 100644 --- a/js/src/vm/EnvironmentObject.h +++ b/js/src/vm/EnvironmentObject.h @@ -20,6 +20,11 @@ #include "vm/Scope.h" #include "vm/ScopeKind.h" // ScopeKind +namespace JS { +class JS_PUBLIC_API EnvironmentChain; +enum class SupportUnscopables : bool; +}; // namespace JS + namespace js { class AbstractGeneratorObject; @@ -175,6 +180,11 @@ extern PropertyName* EnvironmentCoordinateNameSlow(JSScript* script, * * Does not hold 'let' or 'const' bindings. * + * The embedding can specify whether these non-syntactic WithEnvironment + * objects support Symbol.unscopables similar to syntactic 'with' statements + * in JS. In Firefox, we support Symbol.unscopables only for DOM event + * handlers because this is required by the spec. + * * 2. NonSyntacticVariablesObject * * When the embedding wants qualified 'var' bindings and unqualified @@ -234,7 +244,8 @@ extern PropertyName* EnvironmentCoordinateNameSlow(JSScript* script, * | * GlobalLexicalEnvironmentObject[this=global] * | - * WithEnvironmentObject wrapping target (qualified 'var's) + * WithEnvironmentObject [SupportUnscopables=No] wrapping target + * (qualified 'var's) * | * NonSyntacticLexicalEnvironmentObject[this=target] (lexical vars) * @@ -263,7 +274,8 @@ extern PropertyName* EnvironmentCoordinateNameSlow(JSScript* script, * | * NonSyntacticLexicalEnvironmentObject[this=nsvo] * | - * WithEnvironmentObject wrapping target (qualified 'var's) + * WithEnvironmentObject [SupportUnscopables=No] wrapping target + * (qualified 'var's) * | * NonSyntacticLexicalEnvironmentObject[this=target] (lexical vars) * @@ -298,7 +310,7 @@ extern PropertyName* EnvironmentCoordinateNameSlow(JSScript* script, * | * NonSyntacticVariablesObject (qualified 'var's and unqualified names) * | - * WithEnvironmentObject wrapping messageManager + * WithEnvironmentObject [SupportUnscopables=No] wrapping messageManager * | * NonSyntacticLexicalEnvironmentObject[this=messageManager] (lexical vars) * @@ -315,7 +327,8 @@ extern PropertyName* EnvironmentCoordinateNameSlow(JSScript* script, * | * GlobalLexicalEnvironmentObject[this=global] * | - * WithEnvironmentObject wrapping messageManager (qualified 'var's) + * WithEnvironmentObject [SupportUnscopables=No] wrapping messageManager + * (qualified 'var's) * | * NonSyntacticLexicalEnvironmentObject[this=messageManager] (lexical vars) * @@ -334,13 +347,13 @@ extern PropertyName* EnvironmentCoordinateNameSlow(JSScript* script, * | * GlobalLexicalEnvironmentObject[this=global] * | - * WithEnvironmentObject wrapping eN + * WithEnvironmentObject [SupportUnscopables=Yes] wrapping eN * | * ... * | - * WithEnvironmentObject wrapping e1 + * WithEnvironmentObject [SupportUnscopables=Yes] wrapping e1 * | - * WithEnvironmentObject wrapping e0 + * WithEnvironmentObject [SupportUnscopables=Yes] wrapping e0 * | * NonSyntacticLexicalEnvironmentObject [this=*unused*] * @@ -355,9 +368,9 @@ extern PropertyName* EnvironmentCoordinateNameSlow(JSScript* script, * | * ... * | - * WithEnvironmentObject wrapping e1 + * WithEnvironmentObject [SupportUnscopables=Yes] wrapping e1 * | - * WithEnvironmentObject wrapping e0 + * WithEnvironmentObject [SupportUnscopables=Yes] wrapping e0 * | * NonSyntacticLexicalEnvironmentObject [this=*unused*] * | @@ -383,7 +396,8 @@ extern PropertyName* EnvironmentCoordinateNameSlow(JSScript* script, * | * [DebugProxy] CallObject (qualified 'var's) * | - * WithEnvironmentObject wrapping bindings (conflicting 'var's and names) + * WithEnvironmentObject [SupportUnscopables=No] wrapping bindings + * (conflicting 'var's and names) * * If the script has direct eval, BlockLexicalEnvironmentObject is created for * it: @@ -394,7 +408,8 @@ extern PropertyName* EnvironmentCoordinateNameSlow(JSScript* script, * | * [DebugProxy] CallObject (qualified 'var's) * | - * WithEnvironmentObject wrapping bindings (conflicting 'var's and names) + * WithEnvironmentObject [SupportUnscopables=No] wrapping bindings + * (conflicting 'var's and names) * | * BlockLexicalEnvironmentObject (lexical vars, and conflicting lexical vars) * @@ -415,7 +430,8 @@ extern PropertyName* EnvironmentCoordinateNameSlow(JSScript* script, * | * GlobalLexicalEnvironmentObject[this=global] (lexical vars) * | - * WithEnvironmentObject wrapping object with not-conflicting bindings + * WithEnvironmentObject [SupportUnscopables=No] wrapping object with + * not-conflicting bindings * * If `options.useInnerBindings` is true, all bindings are stored into the * bindings object wrapped by WithEnvironmentObject, and they shadow globals @@ -424,7 +440,8 @@ extern PropertyName* EnvironmentCoordinateNameSlow(JSScript* script, * | * GlobalLexicalEnvironmentObject[this=global] (lexical vars) * | - * WithEnvironmentObject wrapping object with all bindings + * WithEnvironmentObject [SupportUnscopables=No] wrapping object with all + * bindings * * NOTE: If `options.useInnerBindings` is true, and if lexical variable names * conflict with the bindings object's properties, the write on them @@ -982,13 +999,16 @@ class NonSyntacticVariablesObject : public EnvironmentObject { }; NonSyntacticLexicalEnvironmentObject* CreateNonSyntacticEnvironmentChain( - JSContext* cx, JS::HandleObjectVector envChain); + JSContext* cx, const JS::EnvironmentChain& envChain); // With environment objects on the run-time environment chain. class WithEnvironmentObject : public EnvironmentObject { static constexpr uint32_t OBJECT_SLOT = 1; static constexpr uint32_t THIS_SLOT = 2; - static constexpr uint32_t SCOPE_SLOT = 3; + // For syntactic with-environments this slot stores the js::Scope*. + // For non-syntactic with-environments it stores a boolean indicating whether + // we need to look up and use Symbol.unscopables. + static constexpr uint32_t SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT = 3; public: static const JSClass class_; @@ -996,12 +1016,12 @@ class WithEnvironmentObject : public EnvironmentObject { static constexpr uint32_t RESERVED_SLOTS = 4; static constexpr ObjectFlags OBJECT_FLAGS = {}; - static WithEnvironmentObject* create(JSContext* cx, HandleObject object, - HandleObject enclosing, - Handle scope); - static WithEnvironmentObject* createNonSyntactic(JSContext* cx, - HandleObject object, - HandleObject enclosing); + static WithEnvironmentObject* create( + JSContext* cx, HandleObject object, HandleObject enclosing, + Handle scope, JS::SupportUnscopables supportUnscopables); + static WithEnvironmentObject* createNonSyntactic( + JSContext* cx, HandleObject object, HandleObject enclosing, + JS::SupportUnscopables supportUnscopables); /* Return the 'o' in 'with (o)'. */ JSObject& object() const; @@ -1017,6 +1037,10 @@ class WithEnvironmentObject : public EnvironmentObject { */ bool isSyntactic() const; + // Whether Symbol.unscopables must be supported for this with-environment. + // This always returns true for syntactic with-environments. + bool supportUnscopables() const; + // For syntactic with environment objects, the with scope. WithScope& scope() const; @@ -1565,7 +1589,8 @@ inline bool IsFrameInitialEnvironment(AbstractFramePtr frame, } WithEnvironmentObject* CreateObjectsForEnvironmentChain( - JSContext* cx, HandleObjectVector chain, HandleObject terminatingEnv); + JSContext* cx, const JS::EnvironmentChain& envChain, + HandleObject terminatingEnv); ModuleObject* GetModuleObjectForScript(JSScript* script); diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 0372d78e7c0a..9a257e131eb5 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -97,7 +97,8 @@ inline bool FetchName(JSContext* cx, HandleObject receiver, HandleObject holder, /* Take the slow path if shape was not found in a native object. */ if (!receiver->is() || !holder->is() || - receiver->is()) { + (receiver->is() && + receiver->as().supportUnscopables())) { Rooted id(cx, NameToId(name)); if (!GetProperty(cx, receiver, receiver, id, vp)) { return false; @@ -108,8 +109,11 @@ inline bool FetchName(JSContext* cx, HandleObject receiver, HandleObject holder, /* Fast path for Object instance properties. */ vp.set(holder->as().getSlot(propInfo.slot())); } else { + // Unwrap 'with' environments for reasons given in + // GetNameBoundInEnvironment. + RootedObject normalized(cx, MaybeUnwrapWithEnvironment(receiver)); RootedId id(cx, NameToId(name)); - if (!NativeGetExistingProperty(cx, receiver, holder.as(), + if (!NativeGetExistingProperty(cx, normalized, holder.as(), id, propInfo, vp)) { return false; } diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 4199a135aa0e..3d124278e4a5 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -31,6 +31,7 @@ #include "jit/BaselineJIT.h" #include "jit/Jit.h" #include "jit/JitRuntime.h" +#include "js/EnvironmentChain.h" // JS::SupportUnscopables #include "js/experimental/JitInfo.h" // JSJitInfo #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit @@ -1071,8 +1072,8 @@ bool js::EnterWithOperation(JSContext* cx, AbstractFramePtr frame, } RootedObject envChain(cx, frame.environmentChain()); - WithEnvironmentObject* withobj = - WithEnvironmentObject::create(cx, obj, envChain, scope); + WithEnvironmentObject* withobj = WithEnvironmentObject::create( + cx, obj, envChain, scope, JS::SupportUnscopables::Yes); if (!withobj) { return false; } diff --git a/js/xpconnect/loader/mozJSSubScriptLoader.cpp b/js/xpconnect/loader/mozJSSubScriptLoader.cpp index a2834e1f3bc6..871a10ef8b56 100644 --- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp +++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp @@ -21,6 +21,7 @@ #include "xpcprivate.h" // xpc::OptionsBase #include "js/CompilationAndEvaluation.h" // JS::Compile #include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions, JS::DecodeOptions +#include "js/EnvironmentChain.h" // JS::EnvironmentChain #include "js/friend/JSMEnvironment.h" // JS::ExecuteInJSMEnvironment, JS::IsJSMEnvironment #include "js/SourceText.h" // JS::Source{Ownership,Text} #include "js/Wrapper.h" @@ -146,7 +147,7 @@ static bool EvalStencil(JSContext* cx, HandleObject targetObj, } retval.setUndefined(); } else { - JS::RootedObjectVector envChain(cx); + JS::EnvironmentChain envChain(cx, JS::SupportUnscopables::No); if (!envChain.append(targetObj)) { return false; }