From 3c9ac3d3341a1e32685db2587f7be7b48de633e6 Mon Sep 17 00:00:00 2001 From: Matthew Gaudet Date: Tue, 28 Jun 2022 22:04:16 +0000 Subject: [PATCH] Bug 1767525 - Implement (under a flag) ShadowRealm constructor and evaluate function. r=jandem Shell only, without HostInitializeShadowRealm, nor importValue Differential Revision: https://phabricator.services.mozilla.com/D146236 --- js/public/ProtoKey.h | 1 + js/public/RealmOptions.h | 7 + js/public/ShadowRealmCallbacks.h | 50 +++ js/public/StableStringChars.h | 6 +- js/public/friend/ErrorNumbers.msg | 14 + js/src/builtin/ShadowRealm.cpp | 389 +++++++++++++++++++++++ js/src/builtin/ShadowRealm.h | 37 +++ js/src/builtin/WrappedFunctionObject.cpp | 298 +++++++++++++++++ js/src/builtin/WrappedFunctionObject.h | 43 +++ js/src/jsapi.cpp | 10 + js/src/moz.build | 3 + js/src/shell/js.cpp | 6 + js/src/shell/jsshell.h | 1 + js/src/tests/non262/ShadowRealms/ccw.js | 33 ++ js/src/vm/GlobalObject.cpp | 4 + js/src/vm/Realm.cpp | 7 + js/src/vm/Runtime.h | 16 + 17 files changed, 923 insertions(+), 2 deletions(-) create mode 100644 js/public/ShadowRealmCallbacks.h create mode 100644 js/src/builtin/ShadowRealm.cpp create mode 100644 js/src/builtin/ShadowRealm.h create mode 100644 js/src/builtin/WrappedFunctionObject.cpp create mode 100644 js/src/builtin/WrappedFunctionObject.h create mode 100644 js/src/tests/non262/ShadowRealms/ccw.js diff --git a/js/public/ProtoKey.h b/js/public/ProtoKey.h index d281cb298662..8b96f1621ce7 100644 --- a/js/public/ProtoKey.h +++ b/js/public/ProtoKey.h @@ -90,6 +90,7 @@ REAL(Set, OCLASP(Set)) \ REAL(DataView, OCLASP(DataView)) \ REAL(Symbol, OCLASP(Symbol)) \ + REAL(ShadowRealm, OCLASP(ShadowRealm)) \ REAL(SharedArrayBuffer, OCLASP(SharedArrayBuffer)) \ REAL_IF_INTL(Intl, CLASP(Intl)) \ REAL_IF_INTL(Collator, OCLASP(Collator)) \ diff --git a/js/public/RealmOptions.h b/js/public/RealmOptions.h index a60800042ba2..14f5d28a83d9 100644 --- a/js/public/RealmOptions.h +++ b/js/public/RealmOptions.h @@ -214,6 +214,12 @@ class JS_PUBLIC_API RealmCreationOptions { return *this; } + bool getShadowRealmsEnabled() const { return shadowRealms_; } + RealmCreationOptions& setShadowRealmsEnabled(bool flag) { + shadowRealms_ = flag; + return *this; + } + #ifdef NIGHTLY_BUILD bool getArrayGroupingEnabled() const { return arrayGrouping_; } RealmCreationOptions& setArrayGroupingEnabled(bool flag) { @@ -274,6 +280,7 @@ class JS_PUBLIC_API RealmCreationOptions { bool propertyErrorMessageFix_ = false; bool iteratorHelpers_ = false; bool arrayFindLast_ = false; + bool shadowRealms_ = false; #ifdef NIGHTLY_BUILD bool arrayGrouping_ = true; #endif diff --git a/js/public/ShadowRealmCallbacks.h b/js/public/ShadowRealmCallbacks.h new file mode 100644 index 000000000000..bbd01b9d8cc6 --- /dev/null +++ b/js/public/ShadowRealmCallbacks.h @@ -0,0 +1,50 @@ +/* -*- 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_ShadowReamCallbacks_h +#define js_ShadowReamCallbacks_h + +#include "jstypes.h" +#include "js/RootingAPI.h" +#include "js/TypeDecls.h" + +struct JS_PUBLIC_API JSContext; + +namespace JS { + +class RealmOptions; + +using GlobalInitializeCallback = bool (*)(JSContext*, JS::Handle); + +// Install the HostInitializeShadowRealm callback that will be invoked when +// creating a shadow realm. +// +// The callback will be passed the realm's global object, so that it is possible +// for the embedding to make any host-determined manipulations to the global, +// such as installing interfaces or helpers that should exist even within +// ShadowRealms. (For example, in the web platform, WebIDL with the +// [Exposed=*] attribute should be installed within a shadow realm.) +extern JS_PUBLIC_API void SetShadowRealmInitializeGlobalCallback( + JSContext* cx, GlobalInitializeCallback callback); + +using GlobalCreationCallback = + JSObject* (*)(JSContext* cx, JS::RealmOptions& creationOptions, + JSPrincipals* principals, + JS::Handle enclosingGlobal); + +// Create the Global object for a ShadowRealm. +// +// This isn't directly specified, however at least in Gecko, in order to +// correctly implement HostInitializeShadowRealm, there are requirements +// placed on the global for the ShadowRealm. +// +// This callback should return a Global object compatible with the +// callback installed by SetShadowRealmInitializeGlobalCallback +extern JS_PUBLIC_API void SetShadowRealmGlobalCreationCallback( + JSContext* cx, GlobalCreationCallback callback); + +} // namespace JS + +#endif // js_ShadowReamCallbacks_h diff --git a/js/public/StableStringChars.h b/js/public/StableStringChars.h index f34e1bb758b5..a12813d89c79 100644 --- a/js/public/StableStringChars.h +++ b/js/public/StableStringChars.h @@ -80,12 +80,12 @@ class MOZ_STACK_CLASS JS_PUBLIC_API AutoStableStringChars final { mozilla::Range latin1Range() const { MOZ_ASSERT(state_ == Latin1); - return mozilla::Range(latin1Chars_, GetStringLength(s_)); + return mozilla::Range(latin1Chars_, length()); } mozilla::Range twoByteRange() const { MOZ_ASSERT(state_ == TwoByte); - return mozilla::Range(twoByteChars_, GetStringLength(s_)); + return mozilla::Range(twoByteChars_, length()); } /* If we own the chars, transfer ownership to the caller. */ @@ -99,6 +99,8 @@ class MOZ_STACK_CLASS JS_PUBLIC_API AutoStableStringChars final { return true; } + size_t length() const { return GetStringLength(s_); } + private: AutoStableStringChars(const AutoStableStringChars& other) = delete; void operator=(const AutoStableStringChars& other) = delete; diff --git a/js/public/friend/ErrorNumbers.msg b/js/public/friend/ErrorNumbers.msg index d914b407ad35..d7bd0cf1a864 100644 --- a/js/public/friend/ErrorNumbers.msg +++ b/js/public/friend/ErrorNumbers.msg @@ -178,6 +178,7 @@ MSG_DEF(JSMSG_TOO_MANY_ARGUMENTS, 0, JSEXN_RANGEERR, "too many arguments pr MSG_DEF(JSMSG_CSP_BLOCKED_EVAL, 0, JSEXN_EVALERR, "call to eval() blocked by CSP") MSG_DEF(JSMSG_CSP_BLOCKED_FUNCTION, 0, JSEXN_EVALERR, "call to Function() blocked by CSP") MSG_DEF(JSMSG_CSP_BLOCKED_WASM, 1, JSEXN_WASMCOMPILEERROR, "call to {0}() blocked by CSP") +MSG_DEF(JSMSG_CSP_BLOCKED_SHADOWREALM, 0, JSEXN_EVALERR, "call to ShadowRealm.prototype.evaluate blocked by CSP") // Wrappers MSG_DEF(JSMSG_ACCESSOR_DEF_DENIED, 1, JSEXN_ERR, "Permission denied to define accessor property {0}") @@ -781,4 +782,17 @@ MSG_DEF(JSMSG_BAD_TUPLE_INDEX, 0, JSEXN_RANGEERR, "index out of range for MSG_DEF(JSMSG_BAD_TUPLE_OBJECT, 0, JSEXN_TYPEERR, "value of TupleObject must be a Tuple") MSG_DEF(JSMSG_RECORD_TUPLE_TO_NUMBER, 0, JSEXN_TYPEERR, "can't convert Record or Tuple to number") + +// Shadow Realms +MSG_DEF(JSMSG_NOT_SHADOW_REALM, 0, JSEXN_TYPEERR, "Object is not a ShadowRealm") +MSG_DEF(JSMSG_SHADOW_REALM_EVALUATE_NOT_STRING, 0, JSEXN_TYPEERR, "a ShadowRealm can only evaluate a string") +MSG_DEF(JSMSG_SHADOW_REALM_INVALID_RETURN, 0, JSEXN_TYPEERR, "return value not primitive or callable") +MSG_DEF(JSMSG_SHADOW_REALM_WRAP_FAILURE, 0, JSEXN_TYPEERR, "unable to wrap callable return object") +MSG_DEF(JSMSG_SHADOW_REALM_EVALUATE_FAILURE, 0, JSEXN_TYPEERR, "evaluate failed") +MSG_DEF(JSMSG_SHADOW_REALM_WRAPPED_EXECUTION_FAILURE, 0, JSEXN_TYPEERR, "wrapped function threw") +MSG_DEF(JSMSG_SHADOW_REALM_GENERIC_SYNTAX, 0, JSEXN_SYNTAXERR, "Couldn't compile string") + +MSG_DEF(JSMSG_SHADOW_REALM_IMPORTVALUE_NOT_IMPLEMENTED, 0, JSEXN_TYPEERR, "importValue not yet implemented") + + //clang-format on diff --git a/js/src/builtin/ShadowRealm.cpp b/js/src/builtin/ShadowRealm.cpp new file mode 100644 index 000000000000..602d739b51eb --- /dev/null +++ b/js/src/builtin/ShadowRealm.cpp @@ -0,0 +1,389 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/. */ + +#include "builtin/ShadowRealm.h" + +#include "mozilla/Assertions.h" + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "builtin/ModuleObject.h" +#include "builtin/Promise.h" +#include "builtin/WrappedFunctionObject.h" +#include "frontend/BytecodeCompilation.h" +#include "js/CompilationAndEvaluation.h" +#include "js/ErrorReport.h" +#include "js/Exception.h" +#include "js/GlobalObject.h" +#include "js/Principals.h" +#include "js/Promise.h" +#include "js/PropertyAndElement.h" +#include "js/PropertyDescriptor.h" +#include "js/ShadowRealmCallbacks.h" +#include "js/SourceText.h" +#include "js/StableStringChars.h" +#include "js/TypeDecls.h" +#include "js/Wrapper.h" +#include "vm/GlobalObject.h" +#include "vm/ObjectOperations.h" + +#include "builtin/HandlerFunction-inl.h" +#include "vm/JSObject-inl.h" + +using namespace js; + +using JS::AutoStableStringChars; +using JS::CompileOptions; +using JS::SourceOwnership; +using JS::SourceText; + +static JSObject* DefaultNewShadowRealmGlobal(JSContext* cx, + JS::RealmOptions& options, + JSPrincipals* principals, + HandleObject unused) { + static const JSClass shadowRealmGlobal = { + "ShadowRealmGlobal", JSCLASS_GLOBAL_FLAGS, &JS::DefaultGlobalClassOps}; + + return JS_NewGlobalObject(cx, &shadowRealmGlobal, principals, + JS::FireOnNewGlobalHook, options); +} + +// https://tc39.es/proposal-shadowrealm/#sec-shadowrealm-constructor +/*static*/ +bool ShadowRealmObject::construct(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. If NewTarget is undefined, throw a TypeError exception. + if (!args.isConstructing()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_NOT_CONSTRUCTOR, "ShadowRealm"); + return false; + } + + // Step 2. Let O be ? OrdinaryCreateFromConstructor(NewTarget, + // "%ShadowRealm.prototype%", ยซ [[ShadowRealm]], [[ExecutionContext]] ยป). + RootedObject proto(cx); + if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ShadowRealm, + &proto)) { + return false; + } + + Rooted shadowRealmObj( + cx, NewObjectWithClassProto(cx, proto)); + if (!shadowRealmObj) { + return false; + } + + // Instead of managing Realms, spidermonkey associates a realm with a global + // object, and so we will manage and store a global. + + // Step 3. Let realmRec be CreateRealm(). + + // Initially steal creation options from current realm: + JS::RealmOptions options(cx->realm()->creationOptions(), + cx->realm()->behaviors()); + + // We don't want to have to deal with CCWs in addition to + // WrappedFunctionObjects. + options.creationOptions().setExistingCompartment(cx->compartment()); + + JS::GlobalCreationCallback newGlobal = + cx->runtime()->getShadowRealmGlobalCreationCallback(); + // If an embedding didn't provide a callback to initialize the global, + // use the basic default one. + if (!newGlobal) { + newGlobal = DefaultNewShadowRealmGlobal; + } + + // Our shadow realm inherits the principals of the current realm, + // but is otherwise constrained. + JSPrincipals* principals = JS::GetRealmPrincipals(cx->realm()); + + // Steps 5-11: In SpiderMonkey these fall under the aegis of the global + // creation. It's worth noting that the newGlobal callback + // needs to respect the SetRealmGlobalObject call below, which + // sets the global to + // OrdinaryObjectCreate(intrinsics.[[%Object.prototype%]]). + // + // Step 5. Let context be a new execution context. + // Step 6. Set the Function of context to null. + // Step 7. Set the Realm of context to realmRec. + // Step 8. Set the ScriptOrModule of context to null. + // Step 9. Set O.[[ExecutionContext]] to context. + // Step 10. Perform ? SetRealmGlobalObject(realmRec, undefined, undefined). + // Step 11. Perform ? SetDefaultGlobalBindings(O.[[ShadowRealm]]). + RootedObject global(cx, newGlobal(cx, options, principals, cx->global())); + if (!global) { + return false; + } + + // Make sure the new global hook obeyed our request in the + // creation options to have a same compartment global. + MOZ_RELEASE_ASSERT(global->compartment() == cx->compartment()); + + // Step 4. Set O.[[ShadowRealm]] to realmRec. + shadowRealmObj->initFixedSlot(GlobalSlot, ObjectValue(*global)); + + // Step 12. Perform ? HostInitializeShadowRealm(O.[[ShadowRealm]]). + JS::GlobalInitializeCallback hostInitializeShadowRealm = + cx->runtime()->getShadowRealmInitializeGlobalCallback(); + if (hostInitializeShadowRealm) { + if (!hostInitializeShadowRealm(cx, global)) { + return false; + } + } + + // Step 13. Return O. + args.rval().setObject(*shadowRealmObj); + return true; +} + +// https://tc39.es/proposal-shadowrealm/#sec-validateshadowrealmobject +// (slightly modified into a cast operator too) +static ShadowRealmObject* ValidateShadowRealmObject(JSContext* cx, + HandleObject O) { + // Step 1. Perform ? RequireInternalSlot(O, [[ShadowRealm]]). + // Step 2. Perform ? RequireInternalSlot(O, [[ExecutionContext]]). + RootedObject maybeUnwrappedO(cx, O); + if (IsCrossCompartmentWrapper(O)) { + maybeUnwrappedO = CheckedUnwrapDynamic(O, cx); + // Unwrapping failed; security wrapper denied. + if (!maybeUnwrappedO) { + return nullptr; + } + } + + if (!maybeUnwrappedO->is()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_NOT_SHADOW_REALM); + return nullptr; + } + + return &maybeUnwrappedO->as(); +} + +// PerformShadowRealmEval ( sourceText: a String, callerRealm: a Realm Record, +// evalRealm: a Realm Record, ) +// +// https://tc39.es/proposal-shadowrealm/#sec-performshadowrealmeval +static bool PerformShadowRealmEval(JSContext* cx, HandleString sourceText, + Realm* callerRealm, Realm* evalRealm, + MutableHandleValue rval) { + MOZ_ASSERT(callerRealm != evalRealm); + + // Step 1. Perform ? HostEnsureCanCompileStrings(callerRealm, evalRealm). + if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::JS, sourceText)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_CSP_BLOCKED_SHADOWREALM); + return false; + } + + // Need to compile the script into the realm we will execute into. + // + // We hoist the error handling out however to ensure that errors + // are thrown from the correct realm. + bool compileSuccess = false; + bool evalSuccess = false; + + do { + Rooted evalRealmGlobal(cx, evalRealm->maybeGlobal()); + AutoRealm ar(cx, evalRealmGlobal); + + // Step 2. Perform the following substeps in an implementation-defined + // order, possibly interleaving parsing and error detection: + // a. Let script be ParseText(! StringToCodePoints(sourceText), Script). + // b. If script is a List of errors, throw a SyntaxError exception. + // c. If script Contains ScriptBody is false, return undefined. + // d. Let body be the ScriptBody of script. + // e. If body Contains NewTarget is true, throw a SyntaxError exception. + // f. If body Contains SuperProperty is true, throw a SyntaxError + // exception. g. If body Contains SuperCall is true, throw a SyntaxError + // exception. + + AutoStableStringChars linearChars(cx); + if (!linearChars.initTwoByte(cx, sourceText)) { + return false; + } + SourceText srcBuf; + + const char16_t* chars = linearChars.twoByteRange().begin().get(); + SourceOwnership ownership = linearChars.maybeGiveOwnershipToCaller() + ? SourceOwnership::TakeOwnership + : SourceOwnership::Borrowed; + if (!srcBuf.init(cx, chars, linearChars.length(), ownership)) { + return false; + } + + // Lets propagate some information into the compilation here. + // + // We may need to censor the stacks eventually, see + // https://bugzilla.mozilla.org/show_bug.cgi?id=1770017 + RootedScript callerScript(cx); + const char* filename; + unsigned lineno; + uint32_t pcOffset; + bool mutedErrors; + DescribeScriptedCallerForCompilation(cx, &callerScript, &filename, &lineno, + &pcOffset, &mutedErrors); + + CompileOptions options(cx); + options.setIsRunOnce(true) + .setNoScriptRval(false) + .setMutedErrors(mutedErrors) + .setFileAndLine(filename, lineno); + + Rooted enclosing(cx, &evalRealmGlobal->emptyGlobalScope()); + RootedScript script( + cx, frontend::CompileEvalScript(cx, options, srcBuf, enclosing, + evalRealmGlobal)); + + compileSuccess = !!script; + if (!compileSuccess) { + break; + } + + // Step 3. Let strictEval be IsStrict of script. + // Step 4. Let runningContext be the running execution context. + // Step 5. Let lexEnv be NewDeclarativeEnvironment(evalRealm.[[GlobalEnv]]). + // Step 6. Let varEnv be evalRealm.[[GlobalEnv]]. + // Step 7. If strictEval is true, set varEnv to lexEnv. + // Step 8. If runningContext is not already suspended, suspend + // runningContext. Step 9. Let evalContext be a new ECMAScript code + // execution context. Step 10. Set evalContext's Function to null. Step 11. + // Set evalContext's Realm to evalRealm. Step 12. Set evalContext's + // ScriptOrModule to null. Step 13. Set evalContext's VariableEnvironment to + // varEnv. Step 14. Set evalContext's LexicalEnvironment to lexEnv. Step 15. + // Push evalContext onto the execution context stack; evalContext is + // now the running execution context. + // Step 16. Let result be EvalDeclarationInstantiation(body, varEnv, + // lexEnv, null, strictEval). + // Step 17. If result.[[Type]] is normal, then + // a. Set result to the result of evaluating body. + // Step 18. If result.[[Type]] is normal and result.[[Value]] is empty, then + // a. Set result to NormalCompletion(undefined). + + // Step 19. Suspend evalContext and remove it from the execution context + // stack. + // Step 20. Resume the context that is now on the top of the execution + // context stack as the running execution context. + RootedObject environment(cx, &evalRealmGlobal->lexicalEnvironment()); + evalSuccess = ExecuteKernel(cx, script, environment, + /* evalInFrame = */ NullFramePtr(), rval); + } while (false); // AutoRealm + + if (!compileSuccess) { + // MG:XXX: figure out how to extract the syntax error information for + // re-throw here (See DebuggerObject::getErrorColumnNumber ?) + // + // https://bugzilla.mozilla.org/show_bug.cgi?id=1769848 + + // The SyntaxError here needs to come from the calling global, so has to + // happen outside the AutoRealm above. + JS_ClearPendingException(cx); + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_SHADOW_REALM_GENERIC_SYNTAX); + return false; + } + + if (!evalSuccess) { + // Step 21. If result.[[Type]] is not normal, throw a TypeError + // exception. + // + // The type error here needs to come from the calling global, so has to + // happen outside the AutoRealm above. + + // MG:XXX: Figure out how to extract the error message and include in + // message of TypeError (if possible): See discussion in + // https://github.com/tc39/proposal-shadowrealm/issues/353 for some + // potential pitfalls (i.e. what if the error has a getter on the message + // property?) + // + // I imagine we could do something like GetPropertyPure, and have a nice + // error message if we *don't* have anything to worry about. + // + // https://bugzilla.mozilla.org/show_bug.cgi?id=1769849 + + JS_ClearPendingException(cx); + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_SHADOW_REALM_EVALUATE_FAILURE); + return false; + } + + // Step 22. Return ? GetWrappedValue(callerRealm, result.[[Value]]). + return GetWrappedValue(cx, callerRealm, rval, rval); +} + +// ShadowRealm.prototype.evaluate ( sourceText ) +// https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.evaluate +static bool ShadowRealm_evaluate(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. Let O be this value (implicit ToObject) + RootedObject obj(cx, ToObject(cx, args.thisv())); + if (!obj) { + return false; + } + + // Step 2. Perform ? ValidateShadowRealmObject(O) + Rooted shadowRealm(cx, + ValidateShadowRealmObject(cx, obj)); + if (!shadowRealm) { + return false; + } + + // Step 3. If Type(sourceText) is not String, throw a TypeError exception. + if (!args.get(0).isString()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_SHADOW_REALM_EVALUATE_NOT_STRING); + return false; + } + RootedString sourceText(cx, args.get(0).toString()); + + // Step 4. Let callerRealm be the current Realm Record. + Realm* callerRealm = cx->realm(); + + // Step 5. Let evalRealm be O.[[ShadowRealm]]. + Realm* evalRealm = shadowRealm->getShadowRealm(); + // Step 6. Return ? PerformShadowRealmEval(sourceText, callerRealm, + // evalRealm). + return PerformShadowRealmEval(cx, sourceText, callerRealm, evalRealm, + args.rval()); +} + +// ShadowRealm.prototype.importValue ( specifier, exportName ) +// https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.importvalue +static bool ShadowRealm_importValue(JSContext* cx, unsigned argc, Value* vp) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_SHADOW_REALM_IMPORTVALUE_NOT_IMPLEMENTED); + return false; +} + +static const JSFunctionSpec shadowrealm_methods[] = { + JS_FN("evaluate", ShadowRealm_evaluate, 1, 0), + JS_FN("importValue", ShadowRealm_importValue, 2, 0), JS_FS_END}; + +static const JSPropertySpec shadowrealm_properties[] = { + JS_STRING_SYM_PS(toStringTag, "ShadowRealm", JSPROP_READONLY), JS_PS_END}; + +static const ClassSpec ShadowRealmObjectClassSpec = { + GenericCreateConstructor, + GenericCreatePrototype, + nullptr, // Static methods + nullptr, // Static properties + shadowrealm_methods, // Methods + shadowrealm_properties // Properties +}; + +const JSClass ShadowRealmObject::class_ = { + "ShadowRealm", + JSCLASS_HAS_CACHED_PROTO(JSProto_ShadowRealm) | + JSCLASS_HAS_RESERVED_SLOTS(ShadowRealmObject::SlotCount), + JS_NULL_CLASS_OPS, &ShadowRealmObjectClassSpec}; + +const JSClass ShadowRealmObject::protoClass_ = { + "ShadowRealm.prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_ShadowRealm), + JS_NULL_CLASS_OPS, &ShadowRealmObjectClassSpec}; diff --git a/js/src/builtin/ShadowRealm.h b/js/src/builtin/ShadowRealm.h new file mode 100644 index 000000000000..eaf936b4828a --- /dev/null +++ b/js/src/builtin/ShadowRealm.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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 builtin_ShadowRealm_h +#define builtin_ShadowRealm_h + +#include "js/Wrapper.h" +#include "vm/GlobalObject.h" +#include "vm/NativeObject.h" + +namespace js { + +class ShadowRealmObject : public NativeObject { + public: + static const JSClass class_; + static const JSClass protoClass_; + + enum { GlobalSlot, SlotCount }; + + static bool construct(JSContext* cx, unsigned argc, Value* vp); + + Realm* getShadowRealm() { + MOZ_ASSERT(getWrappedGlobal()); + return getWrappedGlobal()->nonCCWRealm(); + } + + JSObject* getWrappedGlobal() const { + return &getFixedSlot(GlobalSlot).toObject(); + } +}; + +} // namespace js + +#endif diff --git a/js/src/builtin/WrappedFunctionObject.cpp b/js/src/builtin/WrappedFunctionObject.cpp new file mode 100644 index 000000000000..774bf89e41fc --- /dev/null +++ b/js/src/builtin/WrappedFunctionObject.cpp @@ -0,0 +1,298 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/. */ + +#include "builtin/WrappedFunctionObject.h" + +#include "jsapi.h" + +#include "js/CallAndConstruct.h" +#include "js/Class.h" +#include "js/ErrorReport.h" +#include "js/Exception.h" +#include "js/friend/DumpFunctions.h" +#include "js/TypeDecls.h" +#include "js/Value.h" +#include "js/Wrapper.h" +#include "vm/JSFunction.h" +#include "vm/ObjectOperations.h" + +#include "vm/JSObject-inl.h" + +using namespace js; +using namespace JS; + +// GetWrappedValue ( callerRealm: a Realm Record, value: unknown ) +bool js::GetWrappedValue(JSContext* cx, Realm* callerRealm, HandleValue value, + MutableHandleValue res) { + // Step 2. Return value (Reordered) + if (!value.isObject()) { + res.set(value); + return true; + } + + // Step 1. If Type(value) is Object, then + // a. If IsCallable(value) is false, throw a TypeError exception. + RootedObject objectVal(cx, &value.toObject()); + if (!IsCallable(objectVal)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_SHADOW_REALM_INVALID_RETURN); + return false; + } + + // b. Return ? WrappedFunctionCreate(callerRealm, value). + return WrappedFunctionCreate(cx, callerRealm, objectVal, res); +} + +// [[Call]] +// https://tc39.es/proposal-shadowrealm/#sec-wrapped-function-exotic-objects-call-thisargument-argumentslist +static bool WrappedFunction_Call(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + RootedObject callee(cx, &args.callee()); + MOZ_ASSERT(callee->is()); + + Handle F = callee.as(); + + // 1. Let target be F.[[WrappedTargetFunction]]. + RootedObject target(cx, F->getTargetFunction()); + + // 2. Assert: IsCallable(target) is true. + MOZ_ASSERT(IsCallable(ObjectValue(*target))); + + // 3. Let targetRealm be ? GetFunctionRealm(target). + Rooted targetRealm(cx, GetFunctionRealm(cx, target)); + if (!targetRealm) { + return false; + } + // 4. Let callerRealm be ? GetFunctionRealm(F). + Rooted callerRealm(cx, GetFunctionRealm(cx, F)); + if (!callerRealm) { + return false; + } + + // 5. NOTE: Any exception objects produced after this point are associated + // with callerRealm. + RootedValue result(cx); + { + RootedObject global(cx, JS::GetRealmGlobalOrNull(callerRealm)); + MOZ_RELEASE_ASSERT( + global, "global is null; executing in a realm that's being GC'd?"); + AutoRealm ar(cx, global); + + // https://searchfox.org/mozilla-central/source/js/public/CallAndConstruct.h#57-73 + // 6. Let wrappedArgs be a new empty List. + RootedValueVector wrappedArgs(cx); + + // 7. For each element arg of argumentsList, do + // a. Let wrappedValue be ? GetWrappedValue(targetRealm, arg). + // b. Append wrappedValue to wrappedArgs. + RootedValue element(cx); + for (size_t i = 0; i < args.length(); i++) { + element = args.get(i); + if (!GetWrappedValue(cx, targetRealm, element, &element)) { + return false; + } + + if (!wrappedArgs.append(element)) { + return false; + } + } + + // 8. Let wrappedThisArgument to ? GetWrappedValue(targetRealm, + // thisArgument). + RootedValue wrappedThisArgument(cx); + if (!GetWrappedValue(cx, targetRealm, args.thisv(), &wrappedThisArgument)) { + return false; + } + + // 9. Let result be the Completion Record of Call(target, + // wrappedThisArgument, wrappedArgs). + if (!JS::Call(cx, wrappedThisArgument, target, wrappedArgs, &result)) { + // 11. Else (reordered); + // a. Throw a TypeError exception. + JS_ClearPendingException(cx); + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_SHADOW_REALM_WRAPPED_EXECUTION_FAILURE); + return false; + } + + // 10. If result.[[Type]] is normal or result.[[Type]] is return, then + // a. Return ? GetWrappedValue(callerRealm, result.[[Value]]). + if (!GetWrappedValue(cx, callerRealm, result, args.rval())) { + return false; + } + } + + return true; +} + +static bool CopyNameAndLength(JSContext* cx, HandleObject F, + HandleObject Target, char* prefix = nullptr, + unsigned argCount = 0) { + // 1. If argCount is undefined, then set argCount to 0 (implicit) + // 2. Let L be 0. + double L = 0; + + // 3. Let targetHasLength be ? HasOwnProperty(Target, "length"). + + RootedId length(cx, NameToId(cx->names().length)); + RootedId name(cx, NameToId(cx->names().name)); + // Try to avoid invoking the resolve hook. + if (Target->is() && + !Target->as().hasResolvedLength()) { + RootedValue targetLen(cx); + if (!JSFunction::getUnresolvedLength(cx, Target.as(), + &targetLen)) { + return false; + } + + L = std::max(0.0, targetLen.toNumber() - argCount); + } else { + bool targetHasLength; + if (!HasOwnProperty(cx, Target, length, &targetHasLength)) { + return false; + } + // https://searchfox.org/mozilla-central/source/js/src/vm/JSFunction.cpp#1298 + // 4. If targetHasLength is true, then + if (targetHasLength) { + // a. Let targetLen be ? Get(Target, "length"). + RootedValue targetLen(cx); + if (!GetProperty(cx, Target, Target, length, &targetLen)) { + return false; + } + + // b. If Type(targetLen) is Number, then + // i. If targetLen is +โˆž๐”ฝ, set L to +โˆž. + // ii. Else if targetLen is -โˆž๐”ฝ, set L to 0. + // iii. Else, + // 1. Let targetLenAsInt be ! ToIntegerOrInfinity(targetLen). + // 2. Assert: targetLenAsInt is finite. + // 3. Set L to max(targetLenAsInt - argCount, 0). + if (targetLen.isNumber()) { + L = std::max(0.0, JS::ToInteger(targetLen.toNumber()) - argCount); + } + } + } + + // 5. Perform ! SetFunctionLength(F, L). + RootedValue rootedL(cx, DoubleValue(L)); + if (!JS_DefinePropertyById(cx, F, length, rootedL, JSPROP_READONLY)) { + return false; + } + + // 6. Let targetName be ? Get(Target, "name"). + RootedValue targetName(cx); + if (!GetProperty(cx, Target, Target, cx->names().name, &targetName)) { + return false; + } + + // 7. If Type(targetName) is not String, set targetName to the empty String. + if (!targetName.isString()) { + targetName = StringValue(cx->runtime()->emptyString); + } + + RootedString targetString(cx, targetName.toString()); + RootedId targetNameId(cx); + if (!JS_StringToId(cx, targetString, &targetNameId)) { + return false; + } + + // 8. Perform ! SetFunctionName(F, targetName, prefix). + // (inlined and specialized from js::SetFunctionName) + Rooted funName(cx, IdToFunctionName(cx, targetNameId)); + if (!funName) { + return false; + } + RootedValue rootedFunName(cx, StringValue(funName)); + return JS_DefinePropertyById(cx, F, name, rootedFunName, JSPROP_READONLY); +} + +static const JSClassOps classOps = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + nullptr, // finalize + WrappedFunction_Call, // call + nullptr, // construct + nullptr, // trace +}; + +const JSClass WrappedFunctionObject::class_ = { + "WrappedFunctionObject", + JSCLASS_HAS_CACHED_PROTO( + JSProto_Function) | // MG:XXX: Is this going to correctly set the + // prototype for me? + JSCLASS_HAS_RESERVED_SLOTS(WrappedFunctionObject::SlotCount), + &classOps}; + +JSObject* GetRealmFunctionPrototype(JSContext* cx, Realm* realm) { + CHECK_THREAD(cx); + Rooted global(cx, realm->maybeGlobal()); + MOZ_RELEASE_ASSERT(global); + return GlobalObject::getOrCreateFunctionPrototype(cx, global); +} + +// WrappedFunctionCreate ( callerRealm: a Realm Record, Target: a function +// object) +bool js::WrappedFunctionCreate(JSContext* cx, Realm* callerRealm, + HandleObject target, MutableHandleValue res) { + // Ensure that the function object has the correct realm by allocating it + // into that realm. + RootedObject global(cx, JS::GetRealmGlobalOrNull(callerRealm)); + MOZ_RELEASE_ASSERT(global, + "global is null; executing in a realm that's being GC'd?"); + AutoRealm ar(cx, global); + + MOZ_ASSERT(target); + + // Target *could* be a function from another realm + RootedObject maybeWrappedTarget(cx, target); + if (!JS_WrapObject(cx, &maybeWrappedTarget)) { + return false; + } + + // 1. Let internalSlotsList be the internal slots listed in Table 2, plus + // [[Prototype]] and [[Extensible]]. + // 2. Let wrapped be ! MakeBasicObject(internalSlotsList). + // 3. Set wrapped.[[Prototype]] to + // callerRealm.[[Intrinsics]].[[%Function.prototype%]]. + RootedObject functionPrototype(cx, + GetRealmFunctionPrototype(cx, callerRealm)); + MOZ_ASSERT(cx->compartment() == functionPrototype->compartment()); + + Rooted wrapped( + cx, + NewObjectWithGivenProto(cx, functionPrototype)); + if (!wrapped) { + return false; + } + + // 4. Set wrapped.[[Call]] as described in 2.1 (implicit in JSClass call + // hook) + // 5. Set wrapped.[[WrappedTargetFunction]] to Target. + wrapped->setTargetFunction(*maybeWrappedTarget); + // 6. Set wrapped.[[Realm]] to callerRealm. (implicitly the realm of + // wrapped, which we assured with the AutoRealm + + MOZ_ASSERT(wrapped->realm() == callerRealm); + + // 7. Let result be CopyNameAndLength(wrapped, Target). + if (!CopyNameAndLength(cx, wrapped, maybeWrappedTarget)) { + // 8. If result is an Abrupt Completion, throw a TypeError exception. + JS_ClearPendingException(cx); + + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_SHADOW_REALM_WRAP_FAILURE); + return false; + } + + // 9. Return wrapped. + res.set(ObjectValue(*wrapped)); + return true; +} diff --git a/js/src/builtin/WrappedFunctionObject.h b/js/src/builtin/WrappedFunctionObject.h new file mode 100644 index 000000000000..5e490196cb6f --- /dev/null +++ b/js/src/builtin/WrappedFunctionObject.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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 builtin_WrappedFunctionObject_h +#define builtin_WrappedFunctionObject_h + +#include "js/Value.h" +#include "vm/NativeObject.h" + +namespace js { + +// Implementing Wrapped Function Exotic Objects from the ShadowRealms proposal +// https://tc39.es/proposal-shadowrealm/#sec-wrapped-function-exotic-objects +// +// These are produced as callables are passed across ShadowRealm boundaries, +// preventing functions from piercing the shadow realm barrier. +class WrappedFunctionObject : public NativeObject { + public: + static const JSClass class_; + + enum { WrappedTargetFunctionSlot, SlotCount }; + + JSObject* getTargetFunction() const { + return &getFixedSlot(WrappedTargetFunctionSlot).toObject(); + } + + void setTargetFunction(JSObject& obj) { + setFixedSlot(WrappedTargetFunctionSlot, ObjectValue(obj)); + } +}; + +bool WrappedFunctionCreate(JSContext* cx, Realm* callerRealm, + HandleObject target, MutableHandleValue res); + +bool GetWrappedValue(JSContext* cx, Realm* callerRealm, HandleValue value, + MutableHandleValue res); + +} // namespace js + +#endif diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index b1ad656cbb7d..82c5d29e7881 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4557,6 +4557,16 @@ JS_PUBLIC_API void JS::SetOutOfMemoryCallback(JSContext* cx, cx->runtime()->oomCallbackData = data; } +JS_PUBLIC_API void JS::SetShadowRealmInitializeGlobalCallback( + JSContext* cx, JS::GlobalInitializeCallback callback) { + cx->runtime()->shadowRealmInitializeGlobalCallback = callback; +} + +JS_PUBLIC_API void JS::SetShadowRealmGlobalCreationCallback( + JSContext* cx, JS::GlobalCreationCallback callback) { + cx->runtime()->shadowRealmGlobalCreationCallback = callback; +} + JS::FirstSubsumedFrame::FirstSubsumedFrame( JSContext* cx, bool ignoreSelfHostedFrames /* = true */) : JS::FirstSubsumedFrame(cx, cx->realm()->principals(), diff --git a/js/src/moz.build b/js/src/moz.build index b1ecf8f88cb4..1e398524be32 100755 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -196,6 +196,7 @@ EXPORTS.js += [ "../public/SavedFrameAPI.h", "../public/ScalarType.h", "../public/ScriptPrivate.h", + "../public/ShadowRealmCallbacks.h", "../public/SharedArrayBuffer.h", "../public/SliceBudget.h", "../public/SourceText.h", @@ -314,6 +315,7 @@ UNIFIED_SOURCES += [ "builtin/Promise.cpp", "builtin/Reflect.cpp", "builtin/ReflectParse.cpp", + "builtin/ShadowRealm.cpp", "builtin/String.cpp", "builtin/Symbol.cpp", "builtin/TestingFunctions.cpp", @@ -321,6 +323,7 @@ UNIFIED_SOURCES += [ "builtin/WeakMapObject.cpp", "builtin/WeakRefObject.cpp", "builtin/WeakSetObject.cpp", + "builtin/WrappedFunctionObject.cpp", "ds/Bitmap.cpp", "ds/LifoAlloc.cpp", "jsapi.cpp", diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index f7eb11735761..68a6ab792d08 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -140,6 +140,7 @@ #include "js/MemoryFunctions.h" #include "js/Modules.h" // JS::GetModulePrivate, JS::SetModule{DynamicImport,Metadata,Resolve}Hook, JS::SetModulePrivate #include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetReservedSlot, JS::SetReservedSlot +#include "js/Principals.h" #include "js/Printf.h" #include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineFunction, JS_DefineFunctions, JS_DefineProperties, JS_DefineProperty, JS_GetElement, JS_GetProperty, JS_GetPropertyById, JS_HasProperty, JS_SetElement, JS_SetProperty, JS_SetPropertyById #include "js/PropertySpec.h" @@ -626,6 +627,7 @@ bool shell::enableWeakRefs = false; bool shell::enableToSource = false; bool shell::enablePropertyErrorMessageFix = false; bool shell::enableIteratorHelpers = false; +bool shell::enableShadowRealms = false; #ifdef NIGHTLY_BUILD bool shell::enableArrayGrouping = true; #endif @@ -4246,6 +4248,7 @@ static void SetStandardRealmOptions(JS::RealmOptions& options) { .setToSourceEnabled(enableToSource) .setPropertyErrorMessageFixEnabled(enablePropertyErrorMessageFix) .setIteratorHelpersEnabled(enableIteratorHelpers) + .setShadowRealmsEnabled(enableShadowRealms) #ifdef NIGHTLY_BUILD .setArrayGroupingEnabled(enableArrayGrouping) #endif @@ -11039,6 +11042,7 @@ static bool SetContextOptions(JSContext* cx, const OptionParser& op) { enablePropertyErrorMessageFix = !op.getBoolOption("disable-property-error-message-fix"); enableIteratorHelpers = op.getBoolOption("enable-iterator-helpers"); + enableShadowRealms = op.getBoolOption("enable-shadow-realms"); #ifdef NIGHTLY_BUILD enableArrayGrouping = op.getBoolOption("enable-array-grouping"); #endif @@ -12074,6 +12078,7 @@ int main(int argc, char** argv) { "property of null or undefined") || !op.addBoolOption('\0', "enable-iterator-helpers", "Enable iterator helpers") || + !op.addBoolOption('\0', "enable-shadow-realms", "Enable ShadowRealms") || !op.addBoolOption('\0', "enable-array-grouping", "Enable Array Grouping") || !op.addBoolOption('\0', "enable-array-find-last", @@ -12636,6 +12641,7 @@ int main(int argc, char** argv) { // Waiting is allowed on the shell's main thread, for now. JS_SetFutexCanWait(cx); JS::SetWarningReporter(cx, WarningReporter); + if (!SetContextOptions(cx, op)) { return 1; } diff --git a/js/src/shell/jsshell.h b/js/src/shell/jsshell.h index 480a9d4b7161..06180892c211 100644 --- a/js/src/shell/jsshell.h +++ b/js/src/shell/jsshell.h @@ -134,6 +134,7 @@ extern bool enableWeakRefs; extern bool enableToSource; extern bool enablePropertyErrorMessageFix; extern bool enableIteratorHelpers; +extern bool enableShadowRealms; extern bool enableArrayGrouping; extern bool enablePrivateClassFields; extern bool enablePrivateClassMethods; diff --git a/js/src/tests/non262/ShadowRealms/ccw.js b/js/src/tests/non262/ShadowRealms/ccw.js new file mode 100644 index 000000000000..7596f158238f --- /dev/null +++ b/js/src/tests/non262/ShadowRealms/ccw.js @@ -0,0 +1,33 @@ +// |reftest| shell-option(--enable-shadow-realms) skip-if(!xulRuntime.shell) + +var g = newGlobal({ newCompartment: true }); + +var sr = g.evaluate(`new ShadowRealm()`); + + +// sr should be a CCW to a ShadowRealm. +ShadowRealm.prototype.evaluate.call(sr, "var x = 10"); +assertEq(sr.evaluate("x"), 10); + +// wrappedf should *not* be a CCW, because we're using this realm's ShadowRealm.prototype.evaluate, +// and so the active realm when invoking will be this current realm. +// +// However, the target function (wrappedf's f) comes from another compartment, and will have to be a CCW. +var wrappedf = ShadowRealm.prototype.evaluate.call(sr, "function f() { return 10; }; f"); +assertEq(wrappedf(), 10); + +var evaluate_from_other_realm = g.evaluate('ShadowRealm.prototype.evaluate'); + +// wrappedb should be a CCW, since the callee of the .call comes from the other +// compartment. +var wrappedb = evaluate_from_other_realm.call(sr, "function b() { return 12; }; b"); +assertEq(wrappedb(), 12); + +nukeAllCCWs() +// This throws, but the dead object message is lost and replaced with the wrapped function +// object message. +assertThrowsInstanceOf(() => wrappedf(), TypeError); +assertThrowsInstanceOf(() => wrappedb(), TypeError); + +if (typeof reportCompare === 'function') + reportCompare(true, true); \ No newline at end of file diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index 699dda1cc722..71d588b58128 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -31,6 +31,7 @@ #include "builtin/Object.h" #include "builtin/RegExp.h" #include "builtin/SelfHostingDefines.h" +#include "builtin/ShadowRealm.h" #include "builtin/Stream.h" #include "builtin/streams/QueueingStrategies.h" // js::{ByteLength,Count}QueueingStrategy #include "builtin/streams/ReadableStream.h" // js::ReadableStream @@ -209,6 +210,9 @@ bool GlobalObject::skipDeselectedConstructor(JSContext* cx, JSProtoKey key) { case JSProto_AsyncIterator: return !cx->realm()->creationOptions().getIteratorHelpersEnabled(); + case JSProto_ShadowRealm: + return !cx->realm()->creationOptions().getShadowRealmsEnabled(); + default: MOZ_CRASH("unexpected JSProtoKey"); } diff --git a/js/src/vm/Realm.cpp b/js/src/vm/Realm.cpp index 006c29677c44..aaa40541fa65 100644 --- a/js/src/vm/Realm.cpp +++ b/js/src/vm/Realm.cpp @@ -13,6 +13,7 @@ #include "jsfriendapi.h" +#include "builtin/WrappedFunctionObject.h" #include "debugger/DebugAPI.h" #include "debugger/Debugger.h" #include "gc/Policy.h" @@ -777,6 +778,12 @@ JS_PUBLIC_API Realm* JS::GetFunctionRealm(JSContext* cx, HandleObject objArg) { continue; } + // WrappedFunctionObjects also have a [[Realm]] internal slot, + // which is the nonCCWRealm by construction. + if (obj->is()) { + return obj->nonCCWRealm(); + } + // Step 4. if (IsScriptedProxy(obj)) { // Steps 4.a-b. diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 30947399a452..bd7db0de53c9 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -48,6 +48,7 @@ # include "js/Proxy.h" // For AutoEnterPolicy #endif #include "js/ScriptPrivate.h" +#include "js/ShadowRealmCallbacks.h" #include "js/Stack.h" #include "js/Stream.h" // JS::AbortSignalIsAborted #include "js/StreamConsumer.h" @@ -1030,6 +1031,21 @@ struct JSRuntime { }; ErrorInterceptionSupport errorInterception; #endif // defined(NIGHTLY_BUILD) + + public: + JS::GlobalInitializeCallback getShadowRealmInitializeGlobalCallback() { + return shadowRealmInitializeGlobalCallback; + } + + JS::GlobalCreationCallback getShadowRealmGlobalCreationCallback() { + return shadowRealmGlobalCreationCallback; + } + + js::MainThreadData + shadowRealmInitializeGlobalCallback; + + js::MainThreadData + shadowRealmGlobalCreationCallback; }; // Context for sending telemetry to the embedder from any thread, main or