summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/ShadowRealm.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/builtin/ShadowRealm.cpp
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/builtin/ShadowRealm.cpp')
-rw-r--r--js/src/builtin/ShadowRealm.cpp688
1 files changed, 688 insertions, 0 deletions
diff --git a/js/src/builtin/ShadowRealm.cpp b/js/src/builtin/ShadowRealm.cpp
new file mode 100644
index 0000000000..adc7f3c245
--- /dev/null
+++ b/js/src/builtin/ShadowRealm.cpp
@@ -0,0 +1,688 @@
+/* -*- 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/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/StructuredClone.h"
+#include "js/TypeDecls.h"
+#include "js/Wrapper.h"
+#include "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/JSObject.h"
+#include "vm/ObjectOperations.h"
+
+#include "builtin/HandlerFunction-inl.h"
+#include "vm/Compartment-inl.h"
+#include "vm/JSObject-inl.h"
+#include "vm/Realm-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,
+ Handle<JSObject*> 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]] »).
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ShadowRealm,
+ &proto)) {
+ return false;
+ }
+
+ Rooted<ShadowRealmObject*> shadowRealmObj(
+ cx, NewObjectWithClassProto<ShadowRealmObject>(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]]).
+ Rooted<JSObject*> 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,
+ Handle<Value> value) {
+ // Step 1. Perform ? RequireInternalSlot(O, [[ShadowRealm]]).
+ // Step 2. Perform ? RequireInternalSlot(O, [[ExecutionContext]]).
+ return UnwrapAndTypeCheckValue<ShadowRealmObject>(cx, value, [cx]() {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NOT_SHADOW_REALM);
+ });
+}
+
+void js::ReportPotentiallyDetailedMessage(JSContext* cx,
+ const unsigned detailedError,
+ const unsigned genericError) {
+ Rooted<Value> exception(cx);
+ if (!cx->getPendingException(&exception)) {
+ return;
+ }
+ cx->clearPendingException();
+
+ JS::ErrorReportBuilder jsReport(cx);
+ JS::ExceptionStack exnStack(cx, exception, nullptr);
+ if (!jsReport.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects)) {
+ cx->clearPendingException();
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, genericError);
+ return;
+ }
+
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, detailedError,
+ jsReport.toStringResult().c_str());
+}
+
+// PerformShadowRealmEval ( sourceText: a String, callerRealm: a Realm Record,
+// evalRealm: a Realm Record, )
+//
+// https://tc39.es/proposal-shadowrealm/#sec-performshadowrealmeval
+static bool PerformShadowRealmEval(JSContext* cx, Handle<JSString*> sourceText,
+ Realm* callerRealm, Realm* evalRealm,
+ MutableHandle<Value> 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<GlobalObject*> 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<char16_t> srcBuf;
+ if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
+ 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<Scope*> 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.
+ Rooted<JSObject*> environment(cx, &evalRealmGlobal->lexicalEnvironment());
+ evalSuccess = ExecuteKernel(cx, script, environment,
+ /* evalInFrame = */ NullFramePtr(), rval);
+ } while (false); // AutoRealm
+
+ if (!compileSuccess) {
+ // Clone the exception into the current global and re-throw, as the
+ // exception has to come from the current global.
+ Rooted<Value> exception(cx);
+ if (!cx->getPendingException(&exception)) {
+ return false;
+ }
+
+ // Clear our exception now that we've got it, so that we don't
+ // do the following call with an exception already pending.
+ cx->clearPendingException();
+
+ Rooted<Value> clonedException(cx);
+ if (!JS_StructuredClone(cx, exception, &clonedException, nullptr,
+ nullptr)) {
+ return false;
+ }
+
+ cx->setPendingException(clonedException, ShouldCaptureStack::Always);
+ 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.
+ ReportPotentiallyDetailedMessage(cx,
+ JSMSG_SHADOW_REALM_EVALUATE_FAILURE_DETAIL,
+ JSMSG_SHADOW_REALM_EVALUATE_FAILURE);
+
+ return false;
+ }
+
+ // Wrap |rval| into the current compartment.
+ if (!cx->compartment()->wrap(cx, rval)) {
+ 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.
+ HandleValue obj = args.thisv();
+
+ // Step 2. Perform ? ValidateShadowRealmObject(O)
+ Rooted<ShadowRealmObject*> 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;
+ }
+ Rooted<JSString*> 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());
+}
+
+enum class ImportValueIndices : uint32_t {
+ CalleRealm = 0,
+
+ ExportNameString,
+
+ Length,
+};
+
+// MG:XXX: Cribbed/Overlapping with StartDynamicModuleImport; may need to
+// refactor to share.
+// https://tc39.es/proposal-shadowrealm/#sec-shadowrealmimportvalue
+static JSObject* ShadowRealmImportValue(JSContext* cx,
+ Handle<JSString*> specifierString,
+ Handle<JSString*> exportName,
+ Realm* callerRealm, Realm* evalRealm) {
+ // Step 1. Assert: evalContext is an execution context associated to a
+ // ShadowRealm instance's [[ExecutionContext]].
+
+ // Step 2. Let innerCapability be ! NewPromiseCapability(%Promise%).
+ Rooted<JSObject*> promiseConstructor(cx, JS::GetPromiseConstructor(cx));
+ if (!promiseConstructor) {
+ return nullptr;
+ }
+
+ Rooted<JSObject*> promiseObject(cx, JS::NewPromiseObject(cx, nullptr));
+ if (!promiseObject) {
+ return nullptr;
+ }
+
+ Handle<PromiseObject*> promise = promiseObject.as<PromiseObject>();
+
+ JS::ModuleDynamicImportHook importHook =
+ cx->runtime()->moduleDynamicImportHook;
+
+ if (!importHook) {
+ // Dynamic import can be disabled by a pref and is not supported in all
+ // contexts (e.g. web workers).
+ JS_ReportErrorASCII(
+ cx,
+ "Dynamic module import is disabled or not supported in this context");
+ if (!RejectPromiseWithPendingError(cx, promise)) {
+ return nullptr;
+ }
+ return promise;
+ }
+
+ {
+ // Step 3. Let runningContext be the running execution context. (Implicit)
+ // Step 4. If runningContext is not already suspended, suspend
+ // runningContext. (Implicit)
+ // Step 5. Push evalContext onto the execution context stack; evalContext is
+ // now the running execution context. (Implicit)
+ Rooted<GlobalObject*> evalRealmGlobal(cx, evalRealm->maybeGlobal());
+ AutoRealm ar(cx, evalRealmGlobal);
+
+ // Not Speced: Get referencing private to pass to importHook.
+ RootedScript script(cx);
+ const char* filename;
+ unsigned lineno;
+ uint32_t pcOffset;
+ bool mutedErrors;
+ DescribeScriptedCallerForCompilation(cx, &script, &filename, &lineno,
+ &pcOffset, &mutedErrors);
+
+ MOZ_ASSERT(script);
+
+ Rooted<Value> referencingPrivate(cx, script->sourceObject()->getPrivate());
+ cx->runtime()->addRefScriptPrivate(referencingPrivate);
+
+ Rooted<JSAtom*> specifierAtom(cx, AtomizeString(cx, specifierString));
+ if (!specifierAtom) {
+ if (!RejectPromiseWithPendingError(cx, promise)) {
+ return nullptr;
+ }
+ return promise;
+ }
+
+ Rooted<ArrayObject*> assertionArray(cx);
+ Rooted<JSObject*> moduleRequest(
+ cx, ModuleRequestObject::create(cx, specifierAtom, assertionArray));
+ if (!moduleRequest) {
+ if (!RejectPromiseWithPendingError(cx, promise)) {
+ return nullptr;
+ }
+ return promise;
+ }
+
+ // Step 6. Perform ! HostImportModuleDynamically(null, specifierString,
+ // innerCapability).
+ //
+ // By specification, this is supposed to take ReferencingScriptOrModule as
+ // null, see first parameter above. However, if we do that, we don't end up
+ // with a script reference, which is used to figure out what the base-URI
+ // should be So then we end up using the default one for the module loader;
+ // which because of the way we set the parent module loader up, means we end
+ // up having the incorrect base URI, as the module loader ends up just using
+ // the document's base URI.
+ //
+ // I have filed https://github.com/tc39/proposal-shadowrealm/issues/363 to
+ // discuss this.
+ if (!importHook(cx, referencingPrivate, moduleRequest, promise)) {
+ cx->runtime()->releaseScriptPrivate(referencingPrivate);
+
+ // If there's no exception pending then the script is terminating
+ // anyway, so just return nullptr.
+ if (!cx->isExceptionPending() ||
+ !RejectPromiseWithPendingError(cx, promise)) {
+ return nullptr;
+ }
+ return promise;
+ }
+
+ // Step 7. Suspend evalContext and remove it from the execution context
+ // stack. (Implicit)
+ // Step 8. Resume the context that is now on the top of the execution
+ // context stack as the running execution context (Implicit)
+ }
+
+ // Step 9. Let steps be the steps of an ExportGetter function as described
+ // below.
+ // Step 10. Let onFulfilled be ! CreateBuiltinFunction(steps, 1, "", «
+ // [[ExportNameString]] », callerRealm).
+
+ // The handler can only hold onto a single object, so we pack that into a new
+ // array, and store there.
+ Rooted<ArrayObject*> handlerObject(
+ cx,
+ NewDenseFullyAllocatedArray(cx, uint32_t(ImportValueIndices::Length)));
+ if (!handlerObject) {
+ return nullptr;
+ }
+
+ handlerObject->setDenseInitializedLength(
+ uint32_t(ImportValueIndices::Length));
+ handlerObject->initDenseElement(uint32_t(ImportValueIndices::CalleRealm),
+ PrivateValue(callerRealm));
+ handlerObject->initDenseElement(
+ uint32_t(ImportValueIndices::ExportNameString), StringValue(exportName));
+
+ Rooted<JSFunction*> onFulfilled(
+ cx,
+ NewHandlerWithExtra(
+ cx,
+ [](JSContext* cx, unsigned argc, Value* vp) {
+ // This is the export getter function from
+ // https://tc39.es/proposal-shadowrealm/#sec-shadowrealmimportvalue
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+
+ auto* handlerObject = ExtraFromHandler<ArrayObject>(args);
+
+ Rooted<Value> realmValue(
+ cx, handlerObject->getDenseElement(
+ uint32_t(ImportValueIndices::CalleRealm)));
+ Rooted<Value> exportNameValue(
+ cx, handlerObject->getDenseElement(
+ uint32_t(ImportValueIndices::ExportNameString)));
+
+ // Step 1. Assert: exports is a module namespace exotic object.
+ Handle<Value> exportsValue = args[0];
+ MOZ_ASSERT(exportsValue.isObject() &&
+ exportsValue.toObject().is<ModuleNamespaceObject>());
+
+ Rooted<ModuleNamespaceObject*> exports(
+ cx, &exportsValue.toObject().as<ModuleNamespaceObject>());
+
+ // Step 2. Let f be the active function object. (not implemented
+ // this way)
+ //
+ // Step 3. Let string be f.[[ExportNameString]]. Step 4.
+ // Assert: Type(string) is String.
+ MOZ_ASSERT(exportNameValue.isString());
+
+ Rooted<JSAtom*> stringAtom(
+ cx, AtomizeString(cx, exportNameValue.toString()));
+ if (!stringAtom) {
+ return false;
+ }
+ Rooted<jsid> stringId(cx, AtomToId(stringAtom));
+
+ // Step 5. Let hasOwn be ? HasOwnProperty(exports, string).
+ bool hasOwn = false;
+ if (!HasOwnProperty(cx, exports, stringId, &hasOwn)) {
+ return false;
+ }
+
+ // Step 6. If hasOwn is false, throw a TypeError exception.
+ if (!hasOwn) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_SHADOW_REALM_VALUE_NOT_EXPORTED);
+ return false;
+ }
+
+ // Step 7. Let value be ? Get(exports, string).
+ Rooted<Value> value(cx);
+ if (!GetProperty(cx, exports, exports, stringId, &value)) {
+ return false;
+ }
+
+ // Step 8. Let realm be f.[[Realm]].
+ Realm* callerRealm = static_cast<Realm*>(realmValue.toPrivate());
+
+ // Step 9. Return ? GetWrappedValue(realm, value).
+ return GetWrappedValue(cx, callerRealm, value, args.rval());
+ },
+ promise, handlerObject));
+ if (!onFulfilled) {
+ return nullptr;
+ }
+
+ Rooted<JSFunction*> onRejected(
+ cx, NewHandler(
+ cx,
+ [](JSContext* cx, unsigned argc, Value* vp) {
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr,
+ JSMSG_SHADOW_REALM_IMPORTVALUE_FAILED);
+ return false;
+ },
+ promise));
+ if (!onRejected) {
+ return nullptr;
+ }
+
+ // Step 11. Set onFulfilled.[[ExportNameString]] to exportNameString.
+ // Step 12. Let promiseCapability be ! NewPromiseCapability(%Promise%).
+ // Step 13. Return ! PerformPromiseThen(innerCapability.[[Promise]],
+ // onFulfilled, callerRealm.[[Intrinsics]].[[%ThrowTypeError%]],
+ // promiseCapability).
+ return OriginalPromiseThen(cx, promise, onFulfilled, onRejected);
+}
+
+// ShadowRealm.prototype.importValue ( specifier, exportName )
+// https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.importvalue
+static bool ShadowRealm_importValue(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1. Let O be this value.
+ HandleValue obj = args.thisv();
+
+ // Step 2. Perform ? ValidateShadowRealmObject(O).
+ Rooted<ShadowRealmObject*> shadowRealm(cx,
+ ValidateShadowRealmObject(cx, obj));
+ if (!shadowRealm) {
+ return false;
+ }
+
+ // Step 3. Let specifierString be ? ToString(specifier).
+ Rooted<JSString*> specifierString(cx, ToString<CanGC>(cx, args.get(0)));
+ if (!specifierString) {
+ return false;
+ }
+
+ // Step 4. If Type(exportName) is not String, throw a TypeError exception.
+ if (!args.get(1).isString()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_SHADOW_REALM_EXPORT_NOT_STRING);
+ return false;
+ }
+
+ Rooted<JSString*> exportName(cx, args.get(1).toString());
+ if (!exportName) {
+ return false;
+ }
+
+ // Step 5. Let callerRealm be the current Realm Record.
+ Realm* callerRealm = cx->realm();
+
+ // Step 6. Let evalRealm be O.[[ShadowRealm]].
+ Realm* evalRealm = shadowRealm->getShadowRealm();
+
+ // Step 7. Let evalContext be O.[[ExecutionContext]]
+ // (we dont' pass this explicitly, instead using the realm+global to
+ // represent)
+
+ // Step 8. Return ?
+ // ShadowRealmImportValue(specifierString, exportName,
+ // callerRealm, evalRealm,
+ // evalContext).
+
+ JSObject* res = ShadowRealmImportValue(cx, specifierString, exportName,
+ callerRealm, evalRealm);
+ if (!res) {
+ return false;
+ }
+
+ args.rval().set(ObjectValue(*res));
+ return true;
+}
+
+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<ShadowRealmObject::construct, 0,
+ gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype<ShadowRealmObject>,
+ 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,
+};