| /* |
| * Copyright (C) 2013-2017 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "JSMicrotask.h" |
| |
| #include "AggregateError.h" |
| #include "BuiltinNames.h" |
| #include "Debugger.h" |
| #include "DeferTermination.h" |
| #include "FunctionCodeBlock.h" |
| #include "FunctionExecutable.h" |
| #include "GlobalObjectMethodTable.h" |
| #include "Interpreter.h" |
| #include "InterpreterInlines.h" |
| #include "IteratorOperations.h" |
| #include "MicrotaskCallInlines.h" |
| #include "JSArray.h" |
| #include "JSAsyncFromSyncIterator.h" |
| #include "JSAsyncFunctionGenerator.h" |
| #include "JSAsyncGenerator.h" |
| #include "JSAsyncGeneratorInlines.h" |
| #include "JSFunction.h" |
| #include "JSGenerator.h" |
| #include "JSGlobalObject.h" |
| #include "JSObjectInlines.h" |
| #include "JSModuleLoader.h" |
| #include "JSModuleNamespaceObject.h" |
| #include "JSModuleRecord.h" |
| #include "JSPromise.h" |
| #include "JSPromiseCombinatorsGlobalContext.h" |
| #include "JSPromiseConstructor.h" |
| #include "JSPromisePrototype.h" |
| #include "JSPromiseReaction.h" |
| #include "JSSentinel.h" |
| #include "LLIntThunks.h" |
| #include "Microtask.h" |
| #include "ModuleGraphLoadingState.h" |
| #include "ModuleLoaderPayload.h" |
| #include "ModuleLoadingContext.h" |
| #include "ModuleRegistryEntry.h" |
| #include "ObjectConstructor.h" |
| #include "ScriptFetcher.h" |
| #include "ThrowScope.h" |
| #include "TopExceptionScope.h" |
| #include "VMTrapsInlines.h" |
| #if ENABLE(WEBASSEMBLY) |
| #include "JSWebAssemblyStreamingContext.h" |
| #include "WebAssemblyCompileOptions.h" |
| #endif |
| #include <wtf/NoTailCalls.h> |
| |
| WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN |
| |
| namespace JSC { |
| |
| static ALWAYS_INLINE JSCell* NODELETE dynamicCastToCell(JSValue value) |
| { |
| if (value.isCell()) |
| return value.asCell(); |
| return nullptr; |
| } |
| |
| template<typename... Args> requires (std::is_convertible_v<Args, JSValue> && ...) |
| static JSValue callMicrotask(JSGlobalObject* globalObject, JSValue functionObject, JSValue thisValue, JSCell* context, ASCIILiteral message, MicrotaskCallCache* microtaskCallCache, Args... args) |
| { |
| NO_TAIL_CALLS(); |
| |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| static_assert(sizeof...(args) <= MicrotaskCall::maxCallArguments); |
| |
| if (microtaskCallCache) [[likely]] { |
| if (auto* microtaskCall = microtaskCallCache->find(functionObject)) [[likely]] { |
| if (!vm.isSafeToRecurseSoft()) [[unlikely]] |
| return throwStackOverflowError(globalObject, scope); |
| auto* jsFunction = uncheckedDowncast<JSFunction>(functionObject.asCell()); |
| if (auto result = microtaskCall->tryCallWithArguments(vm, jsFunction, thisValue, context, args...)) [[likely]] { |
| scope.release(); |
| return result; |
| } |
| RETURN_IF_EXCEPTION_WITH_TRAPS_DEFERRED(scope, scope.exception()); |
| } |
| } |
| |
| auto callData = JSC::getCallDataInline(functionObject); |
| if (callData.type == CallData::Type::None) |
| return throwTypeError(globalObject, scope, message); |
| |
| ASSERT_WITH_MESSAGE(callData.type == CallData::Type::JS || callData.type == CallData::Type::Native, "Expected object to be callable but received %d", static_cast<int>(callData.type)); |
| ASSERT_WITH_MESSAGE(!thisValue.isEmpty(), "Expected thisValue to be non-empty. Use jsUndefined() if you meant to use undefined."); |
| |
| scope.assertNoException(); |
| |
| ASSERT(!vm.isCollectorBusyOnCurrentThread()); |
| |
| bool isJSCall = callData.type == CallData::Type::JS; |
| JSScope* functionScope = nullptr; |
| FunctionExecutable* functionExecutable = nullptr; |
| TaggedNativeFunction nativeFunction; |
| JSGlobalObject* calleeGlobalObject = nullptr; |
| |
| RefPtr<JSC::JITCode> jitCode; |
| CodeBlock* newCodeBlock = nullptr; |
| if (isJSCall) { |
| functionScope = callData.js.scope; |
| functionExecutable = callData.js.functionExecutable; |
| if (!vm.isSafeToRecurseSoft()) [[unlikely]] |
| return throwStackOverflowError(functionScope->realm(), scope); |
| |
| { |
| DeferTraps deferTraps(vm); // We can't jettison this code if we're about to run it. |
| |
| // Compile the callee: |
| functionExecutable->prepareForExecution<FunctionExecutable>(vm, uncheckedDowncast<JSFunction>(functionObject.asCell()), functionScope, CodeSpecializationKind::CodeForCall, newCodeBlock); |
| RETURN_IF_EXCEPTION_WITH_TRAPS_DEFERRED(scope, scope.exception()); |
| ASSERT(newCodeBlock); |
| newCodeBlock->m_shouldAlwaysBeInlined = false; |
| } |
| |
| if (microtaskCallCache) { |
| auto* jsFunction = uncheckedDowncast<JSFunction>(functionObject.asCell()); |
| auto* microtaskCall = microtaskCallCache->find(functionObject); |
| if (!microtaskCall) |
| microtaskCall = microtaskCallCache->nextEntryToReplace(); |
| microtaskCall->initialize(vm, jsFunction); |
| RETURN_IF_EXCEPTION_WITH_TRAPS_DEFERRED(scope, scope.exception()); |
| |
| if (auto result = microtaskCall->tryCallWithArguments(vm, jsFunction, thisValue, context, args...)) [[likely]] { |
| scope.release(); |
| return result; |
| } |
| RETURN_IF_EXCEPTION_WITH_TRAPS_DEFERRED(scope, scope.exception()); |
| } |
| |
| #if (CPU(ARM64) || CPU(X86_64)) && CPU(ADDRESS64) && !ENABLE(C_LOOP) |
| if ((sizeof...(args) + 1) >= newCodeBlock->numParameters()) [[likely]] { |
| auto* entry = functionExecutable->generatedJITCodeAddressForCall(); |
| auto* callee = asObject(functionObject.asCell()); |
| if constexpr (!sizeof...(args)) |
| return JSValue::decode(vmEntryToJavaScriptWith0Arguments(entry, &vm, newCodeBlock, callee, thisValue, context)); |
| else if constexpr (sizeof...(args) == 1) |
| return JSValue::decode(vmEntryToJavaScriptWith1Arguments(entry, &vm, newCodeBlock, callee, thisValue, context, args...)); |
| else if constexpr (sizeof...(args) == 2) |
| return JSValue::decode(vmEntryToJavaScriptWith2Arguments(entry, &vm, newCodeBlock, callee, thisValue, context, args...)); |
| else if constexpr (sizeof...(args) == 3) |
| return JSValue::decode(vmEntryToJavaScriptWith3Arguments(entry, &vm, newCodeBlock, callee, thisValue, context, args...)); |
| else if constexpr (sizeof...(args) == 4) |
| return JSValue::decode(vmEntryToJavaScriptWith4Arguments(entry, &vm, newCodeBlock, callee, thisValue, context, args...)); |
| else if constexpr (sizeof...(args) == 5) |
| return JSValue::decode(vmEntryToJavaScriptWith5Arguments(entry, &vm, newCodeBlock, callee, thisValue, context, args...)); |
| else if constexpr (sizeof...(args) == 6) |
| return JSValue::decode(vmEntryToJavaScriptWith6Arguments(entry, &vm, newCodeBlock, callee, thisValue, context, args...)); |
| else |
| return { }; |
| } |
| #endif |
| |
| calleeGlobalObject = functionScope->realm(); |
| { |
| AssertNoGC assertNoGC; // Ensure no GC happens. GC can replace CodeBlock in Executable. |
| jitCode = functionExecutable->generatedJITCodeForCall(); |
| } |
| } else { |
| ASSERT(callData.type == CallData::Type::Native); |
| nativeFunction = callData.native.function; |
| calleeGlobalObject = asObject(functionObject)->realm(); |
| if (!vm.isSafeToRecurseSoft()) [[unlikely]] |
| return throwStackOverflowError(calleeGlobalObject, scope); |
| } |
| |
| // For native calls, fall back to the regular path |
| scope.release(); |
| ProtoCallFrame protoCallFrame; |
| std::array<EncodedJSValue, sizeof...(args)> argArray = { { JSValue::encode(args)... } }; |
| protoCallFrame.init(newCodeBlock, calleeGlobalObject, asObject(functionObject), thisValue, context, sizeof...(args) + 1, argArray.data()); |
| |
| if (isJSCall) { |
| ASSERT(jitCode == functionExecutable->generatedJITCodeForCall().ptr()); |
| return JSValue::decode(vmEntryToJavaScript(jitCode->addressForCall(), &vm, &protoCallFrame)); |
| } |
| |
| #if ENABLE(WEBASSEMBLY) |
| if (callData.native.isWasm) |
| return JSValue::decode(vmEntryToWasm(uncheckedDowncast<WebAssemblyFunction>(functionObject)->jsToWasm(ArityCheckMode::MustCheckArity).taggedPtr(), &vm, &protoCallFrame)); |
| #endif |
| |
| return JSValue::decode(vmEntryToNative(nativeFunction.taggedPtr(), &vm, &protoCallFrame)); |
| } |
| |
| static void promiseResolveThenableJobFastSlow(JSGlobalObject* globalObject, JSPromise* promise, JSPromise* promiseToResolve) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm); |
| |
| // https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob |
| // NewPromiseResolveThenableJob step a: create resolving functions first so |
| // an abrupt completion of the inlined `then` (SpeciesConstructor or |
| // NewPromiseCapability throwing) routes to reject(error) per step c. |
| auto [resolve, reject] = promiseToResolve->createResolvingFunctions(vm, globalObject); |
| |
| JSObject* constructor = promiseSpeciesConstructor(globalObject, promise); |
| if (!scope.exception()) [[likely]] { |
| auto capability = JSPromise::createNewPromiseCapability(globalObject, constructor); |
| if (!scope.exception()) [[likely]] { |
| promise->performPromiseThen(vm, globalObject, resolve, reject, capability); |
| return; |
| } |
| } |
| |
| JSValue error = scope.exception()->value(); |
| if (!scope.clearExceptionExceptTermination()) [[unlikely]] |
| return; |
| |
| MarkedArgumentBuffer arguments; |
| arguments.append(error); |
| ASSERT(!arguments.hasOverflowed()); |
| auto callData = JSC::getCallDataInline(reject); |
| call(globalObject, reject, callData, jsUndefined(), arguments); |
| EXCEPTION_ASSERT(scope.exception() || true); |
| } |
| |
| static void promiseResolveThenableJobWithInternalMicrotaskFastSlow(JSGlobalObject* globalObject, JSPromise* promise, InternalMicrotask task, JSValue context) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm); |
| |
| auto [resolve, reject] = JSPromise::createResolvingFunctionsWithInternalMicrotask(vm, globalObject, task, context); |
| |
| JSObject* constructor = promiseSpeciesConstructor(globalObject, promise); |
| if (!scope.exception()) [[likely]] { |
| auto capability = JSPromise::createNewPromiseCapability(globalObject, constructor); |
| if (!scope.exception()) [[likely]] { |
| promise->performPromiseThen(vm, globalObject, resolve, reject, capability); |
| return; |
| } |
| } |
| |
| JSValue error = scope.exception()->value(); |
| if (!scope.clearExceptionExceptTermination()) [[unlikely]] |
| return; |
| |
| MarkedArgumentBuffer arguments; |
| arguments.append(error); |
| ASSERT(!arguments.hasOverflowed()); |
| auto callData = JSC::getCallDataInline(reject); |
| call(globalObject, reject, callData, jsUndefined(), arguments); |
| EXCEPTION_ASSERT(scope.exception() || true); |
| } |
| |
| static void promiseResolveThenableJob(JSGlobalObject* globalObject, JSValue promise, JSValue then, JSValue resolve, JSValue reject, MicrotaskCallCache* microtaskCallCache = nullptr) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm); |
| |
| { |
| callMicrotask(globalObject, then, promise, dynamicCastToCell(then), "|then| is not a function"_s, microtaskCallCache, resolve, reject); |
| if (!scope.exception()) [[likely]] |
| return; |
| } |
| |
| JSValue error = scope.exception()->value(); |
| if (!scope.clearExceptionExceptTermination()) [[unlikely]] |
| return; |
| |
| MarkedArgumentBuffer arguments; |
| arguments.append(error); |
| ASSERT(!arguments.hasOverflowed()); |
| call(globalObject, reject, jsUndefined(), arguments, "|reject| is not a function"_s); |
| EXCEPTION_ASSERT(scope.exception() || true); |
| } |
| |
| // Settle a cooperative-driver request (target is a driver, not a real .next() JSPromise). The iterator |
| // result belongs to the producer's realm (for an async generator that is the generator's realm even when |
| // driven cross-realm; for an AsyncFromSyncIterator it is the wrapper's realm), so use it for the object and |
| // the promise-then watchpoint. While the watchpoint holds the result is delivered internally and never |
| // escapes, so reuse the producer's cached result object; on invalidation `.then` becomes observable, so |
| // create a fresh object and take the full resolve path. |
| template<typename Producer> |
| static ALWAYS_INLINE void settleDriverWithIteratorResult(VM& vm, Producer* producer, JSValue value, bool done, JSValue target) |
| { |
| JSGlobalObject* realm = producer->realm(); |
| if (realm->promiseThenWatchpointSet().isStillValid()) [[likely]] { |
| JSObject* iteratorResult; |
| JSValue cached = producer->cachedDriverResult(); |
| |
| // The cached object is handed to `target` inside a fulfillment microtask and its value/done are only read |
| // when that microtask runs. A single driver consumes serially (one outstanding request at a time), so it is |
| // safe to mutate-and-reuse the cached object as long as the previous delivery was already consumed. |
| // A JSAsyncGenerator is user-visible and can be driven by several consumers at once (two `for await` loops |
| // over one generator, or two drivers hitting the completed fast path in the same turn). Its result may still |
| // be in flight for one driver when we settle another. So reuse is only safe when this settlement targets the |
| // same driver the object was last handed to. |
| bool canReuse = cached.isObject(); |
| if constexpr (std::is_same_v<Producer, JSAsyncGenerator>) |
| canReuse = canReuse && producer->cachedDriverResultTarget() == target; |
| if (canReuse) [[likely]] { |
| iteratorResult = asObject(cached); |
| iteratorResult->putDirectOffset(vm, iteratorResultObjectValuePropertyOffset, value); |
| iteratorResult->putDirectOffset(vm, iteratorResultObjectDonePropertyOffset, jsBoolean(done)); |
| } else { |
| iteratorResult = createIteratorResultObject(realm, value, done); |
| producer->setCachedDriverResult(vm, iteratorResult); |
| if constexpr (std::is_same_v<Producer, JSAsyncGenerator>) |
| producer->setCachedDriverResultTarget(vm, target); |
| } |
| JSPromise::fulfillWithInternalMicrotask(vm, realm, iteratorResult, InternalMicrotask::AsyncGeneratorDriverResume, target); |
| return; |
| } |
| |
| auto* iteratorResult = createIteratorResultObject(realm, value, done); |
| JSPromise::resolveWithInternalMicrotask(realm, vm, iteratorResult, InternalMicrotask::AsyncGeneratorDriverResume, target); |
| } |
| |
| static void asyncFromSyncIteratorContinueOrDone(JSGlobalObject* globalObject, VM& vm, JSAsyncFromSyncIterator* iterator, JSValue result, JSPromise::Status status, bool done, MicrotaskCallCache* microtaskCallCache) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| auto [target, closeSyncIteratorOnRejection] = iterator->extractTarget(); |
| |
| switch (status) { |
| case JSPromise::Status::Pending: { |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| case JSPromise::Status::Rejected: { |
| if (!done && closeSyncIteratorOnRejection) { |
| JSObject* syncIterator = iterator->syncIterator(); |
| auto catchScope = DECLARE_TOP_EXCEPTION_SCOPE(vm); |
| JSValue returnMethod = syncIterator->get(globalObject, vm.propertyNames->returnKeyword); |
| if (!catchScope.exception() && returnMethod.isCallable()) |
| callMicrotask(globalObject, returnMethod, syncIterator, dynamicCastToCell(returnMethod), "return is not a function"_s, microtaskCallCache); |
| if (!catchScope.clearExceptionExceptTermination()) [[unlikely]] { |
| scope.release(); |
| return; |
| } |
| } |
| scope.release(); |
| if (auto* promise = dynamicDowncast<JSPromise>(target)) |
| promise->reject(vm, result); |
| else |
| JSPromise::rejectWithInternalMicrotask(vm, globalObject, result, InternalMicrotask::AsyncGeneratorDriverResume, target); |
| break; |
| } |
| case JSPromise::Status::Fulfilled: { |
| // A real .next()/.return()/.throw() settles its result JSPromise; the result object is created |
| // fresh (it is observable by user code). |
| if (auto* promise = dynamicDowncast<JSPromise>(target)) { |
| auto* resultObject = createIteratorResultObject(globalObject, result, done); |
| scope.release(); |
| promise->resolve(globalObject, vm, resultObject); |
| break; |
| } |
| |
| scope.release(); |
| settleDriverWithIteratorResult(vm, iterator, result, done, target); |
| break; |
| } |
| } |
| } |
| |
| static void promiseRaceResolveJob(JSGlobalObject* globalObject, VM& vm, JSPromise* promise, JSValue resolution, JSPromise::Status status) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (promise->status() != JSPromise::Status::Pending) |
| return; |
| |
| switch (status) { |
| case JSPromise::Status::Pending: { |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| case JSPromise::Status::Fulfilled: { |
| scope.release(); |
| promise->resolve(globalObject, vm, resolution); |
| break; |
| } |
| case JSPromise::Status::Rejected: { |
| scope.release(); |
| promise->reject(vm, resolution); |
| break; |
| } |
| } |
| } |
| |
| static void promiseAllResolveJob(JSGlobalObject* globalObject, VM& vm, JSPromiseCombinatorsGlobalContext* globalContext, JSValue resolution, uint64_t index, JSPromise::Status status) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| switch (status) { |
| case JSPromise::Status::Pending: { |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| case JSPromise::Status::Fulfilled: { |
| auto* values = uncheckedDowncast<JSArray>(globalContext->values()); |
| |
| values->putDirectIndex(globalObject, index, resolution); |
| RETURN_IF_EXCEPTION(scope, void()); |
| |
| uint64_t count = globalContext->remainingElementsCount() - 1; |
| globalContext->setRemainingElementsCount(count); |
| if (!count) { |
| auto* promise = uncheckedDowncast<JSPromise>(globalContext->promise()); |
| scope.release(); |
| promise->resolve(globalObject, vm, values); |
| } |
| break; |
| } |
| case JSPromise::Status::Rejected: { |
| auto* promise = uncheckedDowncast<JSPromise>(globalContext->promise()); |
| scope.release(); |
| promise->reject(vm, resolution); |
| break; |
| } |
| } |
| } |
| |
| static void promiseAllSettledResolveJob(JSGlobalObject* globalObject, VM& vm, JSPromiseCombinatorsGlobalContext* globalContext, JSValue resolution, uint64_t index, JSPromise::Status status) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| auto* values = uncheckedDowncast<JSArray>(globalContext->values()); |
| |
| JSObject* resultObject = nullptr; |
| switch (status) { |
| case JSPromise::Status::Pending: { |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| case JSPromise::Status::Fulfilled: { |
| resultObject = createPromiseAllSettledFulfilledResult(globalObject, resolution); |
| break; |
| } |
| case JSPromise::Status::Rejected: { |
| resultObject = createPromiseAllSettledRejectedResult(globalObject, resolution); |
| break; |
| } |
| } |
| |
| values->putDirectIndex(globalObject, index, resultObject); |
| RETURN_IF_EXCEPTION(scope, void()); |
| |
| uint64_t count = globalContext->remainingElementsCount() - 1; |
| globalContext->setRemainingElementsCount(count); |
| if (!count) { |
| auto* promise = uncheckedDowncast<JSPromise>(globalContext->promise()); |
| scope.release(); |
| promise->resolve(globalObject, vm, values); |
| } |
| } |
| |
| static void promiseAnyResolveJob(JSGlobalObject* globalObject, VM& vm, JSPromiseCombinatorsGlobalContext* globalContext, JSValue resolution, uint64_t index, JSPromise::Status status) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| switch (status) { |
| case JSPromise::Status::Pending: { |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| case JSPromise::Status::Fulfilled: { |
| auto* promise = uncheckedDowncast<JSPromise>(globalContext->promise()); |
| scope.release(); |
| promise->resolve(globalObject, vm, resolution); |
| break; |
| } |
| case JSPromise::Status::Rejected: { |
| auto* errors = uncheckedDowncast<JSArray>(globalContext->values()); |
| |
| errors->putDirectIndex(globalObject, index, resolution); |
| RETURN_IF_EXCEPTION(scope, void()); |
| |
| uint64_t count = globalContext->remainingElementsCount() - 1; |
| globalContext->setRemainingElementsCount(count); |
| if (!count) { |
| auto* promise = uncheckedDowncast<JSPromise>(globalContext->promise()); |
| auto* aggregateError = createAggregateError(vm, globalObject->errorStructure(ErrorType::AggregateError), errors, String(), jsUndefined()); |
| scope.release(); |
| promise->reject(vm, aggregateError); |
| } |
| break; |
| } |
| } |
| } |
| |
| static void asyncGeneratorBodyCall(JSGlobalObject*, JSAsyncGenerator*, JSValue resumeValue, int32_t resumeMode, MicrotaskCallCache*); |
| static void asyncGeneratorCompleteStep(JSGlobalObject*, JSAsyncGenerator*, JSValue, bool isThrow, bool done); |
| static void asyncGeneratorDrainQueue(JSGlobalObject*, JSAsyncGenerator*); |
| static void asyncGeneratorDispatchSuspend(JSGlobalObject*, JSAsyncGenerator*, JSValue value, MicrotaskCallCache*); |
| |
| // https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep |
| static void asyncGeneratorCompleteStep(JSGlobalObject* globalObject, JSAsyncGenerator* generator, JSValue value, bool isThrow, bool done) |
| { |
| VM& vm = globalObject->vm(); |
| |
| // 1-4. Remove the first request from the queue. |
| auto* target = generator->dequeue(vm); |
| ASSERT(target); |
| |
| // A real .next()/.throw()/.return() settles its result JSPromise directly. |
| if (auto* promise = dynamicDowncast<JSPromise>(target)) { |
| // 6. throw completion -> reject. |
| if (isThrow) { |
| promise->reject(vm, value); |
| return; |
| } |
| |
| // 7. normal completion -> resolve with CreateIteratorResultObject(value, done). The iterator |
| // result object belongs to the generator's realm, not the realm of whoever called next(). |
| auto* iteratorResult = createIteratorResultObject(generator->realm(), value, done); |
| promise->resolve(globalObject, vm, iteratorResult); |
| return; |
| } |
| |
| // resolveWithInternalMicrotask keeps resolvePromise's thenable check, matching a real Promise settlement. |
| if (isThrow) { |
| JSPromise::rejectWithInternalMicrotask(vm, globalObject, value, InternalMicrotask::AsyncGeneratorDriverResume, target); |
| return; |
| } |
| |
| settleDriverWithIteratorResult(vm, generator, value, done, target); |
| } |
| |
| // https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn |
| void asyncGeneratorAwaitReturn(JSGlobalObject* globalObject, JSAsyncGenerator* generator) |
| { |
| VM& vm = globalObject->vm(); |
| ASSERT(generator->state() == static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorState::DrainingQueue)); |
| JSPromise::resolveWithInternalMicrotaskForAsyncAwait(globalObject, vm, generator->resumeValue(), InternalMicrotask::AsyncGeneratorAwaitReturn, generator); |
| } |
| |
| // https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue |
| static void asyncGeneratorDrainQueue(JSGlobalObject* globalObject, JSAsyncGenerator* generator) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| ASSERT(generator->state() == static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorState::DrainingQueue)); |
| |
| // 3. Repeat, while the queue is not empty. |
| while (!generator->isQueueEmpty()) { |
| int32_t resumeMode = generator->resumeMode(); |
| |
| // 5c. return completion -> AsyncGeneratorAwaitReturn and stop. |
| if (resumeMode == static_cast<int32_t>(JSGenerator::ResumeMode::ReturnMode)) { |
| scope.release(); |
| asyncGeneratorAwaitReturn(globalObject, generator); |
| return; |
| } |
| |
| // 5d. throw -> reject; normal -> resolve { undefined, true }. |
| bool isThrow = resumeMode == static_cast<int32_t>(JSGenerator::ResumeMode::ThrowMode); |
| asyncGeneratorCompleteStep(globalObject, generator, isThrow ? generator->resumeValue() : jsUndefined(), isThrow, /* done */ true); |
| RETURN_IF_EXCEPTION(scope, void()); |
| } |
| |
| // 3a. queue empty -> completed. |
| generator->setState(static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorState::Completed)); |
| } |
| |
| // https://tc39.es/ecma262/#sec-asyncgeneratorresume (then AsyncGeneratorStart's completion handling). |
| static void asyncGeneratorBodyCall(JSGlobalObject* globalObject, JSAsyncGenerator* generator, JSValue resumeValue, int32_t resumeMode, MicrotaskCallCache* microtaskCallCache) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| int32_t state = generator->state(); |
| generator->setState(static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorState::Executing)); |
| |
| JSValue generatorFunction = generator->next(); |
| JSValue generatorThis = generator->thisValue(); |
| JSValue generatorFrame = generator->frame(); |
| |
| JSValue value; |
| JSValue error; |
| { |
| auto catchScope = DECLARE_TOP_EXCEPTION_SCOPE(vm); |
| value = callMicrotask(globalObject, generatorFunction, generatorThis, generator, "handler is not a function"_s, microtaskCallCache, |
| generator, jsNumber(state >> JSAsyncGenerator::reasonShift), resumeValue, jsNumber(resumeMode), generatorFrame); |
| if (catchScope.exception()) [[unlikely]] { |
| error = catchScope.exception()->value(); |
| if (!catchScope.clearExceptionExceptTermination()) [[unlikely]] |
| return; |
| } |
| } |
| |
| state = generator->state(); |
| |
| // The body suspended at an `await` or a `yield`/`yield*`. |
| if (state > 0) { |
| scope.release(); |
| asyncGeneratorDispatchSuspend(globalObject, generator, value, microtaskCallCache); |
| return; |
| } |
| |
| // https://tc39.es/ecma262/#sec-asyncgeneratorstart |
| ASSERT(state == static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorState::Executing)); |
| // 4.g. Set acGen.[[AsyncGeneratorState]] to draining-queue. |
| generator->setState(static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorState::DrainingQueue)); |
| // 4.h. If result is a normal completion, set result to NormalCompletion(undefined). |
| // 4.i. If result is a return completion, set result to NormalCompletion(result.[[Value]]). |
| // 4.j. Perform AsyncGeneratorCompleteStep(acGen, result, true). |
| asyncGeneratorCompleteStep(globalObject, generator, error ? error : value, /* isThrow */ !!error, /* done */ true); |
| RETURN_IF_EXCEPTION(scope, void()); |
| // 4.k. Perform AsyncGeneratorDrainQueue(acGen). |
| RELEASE_AND_RETURN(scope, asyncGeneratorDrainQueue(globalObject, generator)); |
| } |
| |
| // https://tc39.es/ecma262/#sec-asyncgeneratorunwrapyieldresumption |
| static void asyncGeneratorUnwrapYieldResumption(JSGlobalObject* globalObject, JSAsyncGenerator* generator, JSValue resumeValue, int32_t resumeMode, MicrotaskCallCache* microtaskCallCache) |
| { |
| VM& vm = globalObject->vm(); |
| int32_t state = generator->state(); |
| // A suspended-start (Init, state 0) generator may be resumed here -- e.g. the for-await driver |
| // starting a fresh producer -- which is always a Normal-mode resume handled by asyncGeneratorBodyCall |
| // below. Only the ReturnMode branch, which edits the suspend-reason bits, needs a positive state. |
| ASSERT(state > 0 || (state == static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorState::Init) && resumeMode != static_cast<int32_t>(JSGenerator::ResumeMode::ReturnMode))); |
| // 1. If resumptionValue is not a return completion, return ? resumptionValue. |
| if (resumeMode != static_cast<int32_t>(JSGenerator::ResumeMode::ReturnMode)) { |
| asyncGeneratorBodyCall(globalObject, generator, resumeValue, resumeMode, microtaskCallCache); |
| return; |
| } |
| |
| // 2. Let awaited be Completion(Await(resumptionValue.[[Value]])). |
| // 3. If awaited is a throw completion, return ? awaited. |
| // 4. Assert: awaited is a normal completion. |
| // 5. Return ReturnCompletion(awaited.[[Value]]). |
| generator->setState((state & ~JSAsyncGenerator::reasonMask) | static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorSuspendReason::Await)); |
| JSPromise::resolveWithInternalMicrotaskForAsyncAwait(globalObject, vm, resumeValue, InternalMicrotask::AsyncGeneratorBodyCallReturn, generator); |
| } |
| |
| // https://tc39.es/ecma262/#sec-asyncgeneratorresume |
| void asyncGeneratorResume(JSGlobalObject* globalObject, JSAsyncGenerator* generator, MicrotaskCallCache* microtaskCallCache) |
| { |
| // 1. Assert: gen.[[AsyncGeneratorState]] is either suspended-start or suspended-yield. |
| ASSERT(generator->state() == static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorState::Init) || JSAsyncGenerator::isSuspendedYieldState(generator->state())); |
| asyncGeneratorUnwrapYieldResumption(globalObject, generator, generator->resumeValue(), generator->resumeMode(), microtaskCallCache); |
| } |
| |
| void enqueueAsyncGeneratorDriver(JSGlobalObject* globalObject, JSAsyncGenerator* iterator, JSObject* driver, MicrotaskCallCache* microtaskCallCache) |
| { |
| VM& vm = globalObject->vm(); |
| |
| // Mirror AsyncGeneratorEnqueue's completed-state fast path: settle { undefined, true } without enqueuing. |
| int32_t state = iterator->state(); |
| if (state == static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorState::Completed)) { |
| settleDriverWithIteratorResult(vm, iterator, jsUndefined(), /* done */ true, driver); |
| return; |
| } |
| |
| iterator->enqueue(vm, jsUndefined(), static_cast<int32_t>(JSGenerator::ResumeMode::NormalMode), driver); |
| |
| // https://tc39.es/ecma262/#sec-asyncgeneratorenqueue step 6: a non-busy generator resumes immediately. |
| if (state == static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorState::Init) || JSAsyncGenerator::isSuspendedYieldState(state)) |
| asyncGeneratorResume(globalObject, iterator, microtaskCallCache); |
| } |
| |
| // If the Promise species is tampered after the fused open, fall back to the real next() so the consumer's Await |
| // still performs the observable PromiseResolve (Promise.prototype.constructor lookup) the fused driver would skip. |
| JSValue asyncIteratorNextWithDriver(JSGlobalObject* globalObject, JSObject* iterator, JSObject* driver, MicrotaskCallCache* microtaskCallCache) |
| { |
| VM& vm = globalObject->vm(); |
| auto* generator = dynamicDowncast<JSAsyncGenerator>(iterator); |
| |
| if (globalObject->promiseSpeciesWatchpointSet().state() != IsWatched) [[unlikely]] { |
| if (generator) |
| return asyncGeneratorNext(globalObject, generator, jsUndefined(), microtaskCallCache); |
| return asyncFromSyncIteratorNext(globalObject, uncheckedDowncast<JSAsyncFromSyncIterator>(iterator), JSValue()); |
| } |
| |
| if (generator) |
| enqueueAsyncGeneratorDriver(globalObject, generator, driver, microtaskCallCache); |
| else |
| driveAsyncFromSyncIteratorWithDriver(globalObject, uncheckedDowncast<JSAsyncFromSyncIterator>(iterator), driver); |
| return vm.fastAsyncGeneratorSentinel(); |
| } |
| |
| // https://tc39.es/ecma262/#sec-asyncgeneratoryield |
| static void asyncGeneratorYield(JSGlobalObject* globalObject, JSAsyncGenerator* generator, JSValue value, MicrotaskCallCache* microtaskCallCache) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| // Stay executing across CompleteStep so reentrant requests only enqueue. |
| int32_t state = generator->state(); |
| generator->setState((state & ~JSAsyncGenerator::reasonMask) | static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorSuspendReason::Await)); |
| |
| // 9. Perform AsyncGeneratorCompleteStep(gen, completion, false, previousRealm). |
| asyncGeneratorCompleteStep(globalObject, generator, value, /* isThrow */ false, /* done */ false); |
| RETURN_IF_EXCEPTION(scope, void()); |
| |
| // 10. Let queue be gen.[[AsyncGeneratorQueue]]. |
| // 11. If queue is not empty, then |
| if (!generator->isQueueEmpty()) { |
| // 11.a. NOTE: Execution continues without suspending the generator. |
| // 11.b. Let toYield be the first element of queue. |
| // 11.c. Let resumptionValue be Completion(toYield.[[Completion]]). |
| // 11.d. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue). |
| scope.release(); |
| asyncGeneratorUnwrapYieldResumption(globalObject, generator, generator->resumeValue(), generator->resumeMode(), microtaskCallCache); |
| return; |
| } |
| // 12. Set gen.[[AsyncGeneratorState]] to suspended-yield. |
| state = generator->state(); |
| generator->setState((state & ~JSAsyncGenerator::reasonMask) | static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorSuspendReason::Yield)); |
| } |
| |
| // Plain `yield`'s operand Await (AsyncGeneratorYield(? Await(value))) has settled. |
| static void asyncGeneratorYieldAwaited(JSGlobalObject* globalObject, JSAsyncGenerator* generator, JSValue result, JSPromise::Status status, MicrotaskCallCache* microtaskCallCache) |
| { |
| switch (status) { |
| case JSPromise::Status::Pending: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return; |
| case JSPromise::Status::Rejected: |
| // `? Await(value)` threw -> resume the body with a throw at the yield. |
| asyncGeneratorBodyCall(globalObject, generator, result, static_cast<int32_t>(JSGenerator::ResumeMode::ThrowMode), microtaskCallCache); |
| return; |
| case JSPromise::Status::Fulfilled: |
| asyncGeneratorYield(globalObject, generator, result, microtaskCallCache); |
| return; |
| } |
| } |
| |
| // The body suspended (state > 0); dispatch on the suspend reason: |
| // Await `await x` -> resume the body once x settles (AsyncGeneratorBodyCallNormal). |
| // Yield `yield x` -> AsyncGeneratorYield(? Await(value)): Await first (AsyncGeneratorYieldAwaited). |
| // YieldNoAwait `yield* x` -> AsyncGeneratorYield(value): deliver directly. |
| static void asyncGeneratorDispatchSuspend(JSGlobalObject* globalObject, JSAsyncGenerator* generator, JSValue value, MicrotaskCallCache* microtaskCallCache) |
| { |
| VM& vm = globalObject->vm(); |
| |
| if (value == vm.fastAsyncGeneratorSentinel()) |
| return; |
| |
| int32_t state = generator->state(); |
| switch (static_cast<JSAsyncGenerator::AsyncGeneratorSuspendReason>(state & JSAsyncGenerator::reasonMask)) { |
| case JSAsyncGenerator::AsyncGeneratorSuspendReason::Await: { |
| JSPromise::resolveWithInternalMicrotaskForAsyncAwait(globalObject, vm, value, InternalMicrotask::AsyncGeneratorBodyCallNormal, generator); |
| return; |
| } |
| case JSAsyncGenerator::AsyncGeneratorSuspendReason::Yield: { |
| generator->setState((state & ~JSAsyncGenerator::reasonMask) | static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorSuspendReason::Await)); |
| JSPromise::resolveWithInternalMicrotaskForAsyncAwait(globalObject, vm, value, InternalMicrotask::AsyncGeneratorYieldAwaited, generator); |
| return; |
| } |
| case JSAsyncGenerator::AsyncGeneratorSuspendReason::YieldNoAwait: { |
| asyncGeneratorYield(globalObject, generator, value, microtaskCallCache); |
| return; |
| } |
| } |
| } |
| |
| static void asyncGeneratorBodyCallNormal(JSGlobalObject* globalObject, JSAsyncGenerator* generator, JSValue result, JSPromise::Status status, MicrotaskCallCache* microtaskCallCache) |
| { |
| switch (status) { |
| case JSPromise::Status::Pending: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| case JSPromise::Status::Rejected: |
| asyncGeneratorBodyCall(globalObject, generator, result, static_cast<int32_t>(JSGenerator::ResumeMode::ThrowMode), microtaskCallCache); |
| return; |
| case JSPromise::Status::Fulfilled: |
| asyncGeneratorBodyCall(globalObject, generator, result, static_cast<int32_t>(JSGenerator::ResumeMode::NormalMode), microtaskCallCache); |
| return; |
| } |
| } |
| |
| static void asyncGeneratorBodyCallReturn(JSGlobalObject* globalObject, JSAsyncGenerator* generator, JSValue result, JSPromise::Status status, MicrotaskCallCache* microtaskCallCache) |
| { |
| switch (status) { |
| case JSPromise::Status::Pending: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| case JSPromise::Status::Rejected: |
| asyncGeneratorBodyCall(globalObject, generator, result, static_cast<int32_t>(JSGenerator::ResumeMode::ThrowMode), microtaskCallCache); |
| return; |
| case JSPromise::Status::Fulfilled: |
| asyncGeneratorBodyCall(globalObject, generator, result, static_cast<int32_t>(JSGenerator::ResumeMode::ReturnMode), microtaskCallCache); |
| return; |
| } |
| } |
| |
| // https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn |
| static void asyncGeneratorAwaitReturnContinuation(JSGlobalObject* globalObject, JSAsyncGenerator* generator, JSValue result, JSPromise::Status status) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| ASSERT(generator->state() == static_cast<int32_t>(JSAsyncGenerator::AsyncGeneratorState::DrainingQueue)); |
| |
| switch (status) { |
| case JSPromise::Status::Pending: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return; |
| case JSPromise::Status::Fulfilled: |
| asyncGeneratorCompleteStep(globalObject, generator, result, /* isThrow */ false, /* done */ true); |
| RETURN_IF_EXCEPTION(scope, void()); |
| break; |
| case JSPromise::Status::Rejected: |
| asyncGeneratorCompleteStep(globalObject, generator, result, /* isThrow */ true, /* done */ true); |
| RETURN_IF_EXCEPTION(scope, void()); |
| break; |
| } |
| |
| RELEASE_AND_RETURN(scope, asyncGeneratorDrainQueue(globalObject, generator)); |
| } |
| |
| static void promiseFinallyAwaitJob(JSGlobalObject* globalObject, VM& vm, JSValue settledValue, JSSlimPromiseReaction* context, JSPromise::Status status) |
| { |
| auto* resultPromise = uncheckedDowncast<JSPromise>(context->promise()); |
| JSValue originalValue = context->handlerOrContext(); |
| bool wasFulfilled = context->isFulfillHandler(); |
| |
| if (status == JSPromise::Status::Rejected) { |
| resultPromise->rejectPromise(vm, settledValue); |
| return; |
| } |
| |
| if (wasFulfilled) |
| resultPromise->resolvePromise(globalObject, vm, originalValue); |
| else |
| resultPromise->rejectPromise(vm, originalValue); |
| } |
| |
| static void promiseFinallyReactionJob(JSGlobalObject* globalObject, VM& vm, JSPromise* resultPromise, JSValue valueOrReason, JSSlimPromiseReaction* context, JSPromise::Status status, MicrotaskCallCache* microtaskCallCache) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSValue onFinally = context->handlerOrContext(); |
| |
| JSValue result; |
| JSValue error; |
| { |
| auto catchScope = DECLARE_TOP_EXCEPTION_SCOPE(vm); |
| result = callMicrotask(globalObject, onFinally, jsUndefined(), dynamicCastToCell(onFinally), "onFinally is not a function"_s, microtaskCallCache); |
| if (catchScope.exception()) { |
| error = catchScope.exception()->value(); |
| if (!catchScope.clearExceptionExceptTermination()) [[unlikely]] { |
| scope.release(); |
| return; |
| } |
| } |
| } |
| |
| if (error) { |
| resultPromise->rejectPromise(vm, error); |
| return; |
| } |
| |
| context->setHandlerOrContext(vm, valueOrReason); |
| context->setPerCellBit(status == JSPromise::Status::Fulfilled); |
| |
| if (result.inherits<JSPromise>()) { |
| auto* promise = uncheckedDowncast<JSPromise>(result); |
| if (promise->realm() == globalObject && promise->isThenFastAndNonObservable()) { |
| scope.release(); |
| promise->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::PromiseFinallyAwaitJob, resultPromise, context); |
| return; |
| } |
| } |
| |
| if (!result.isObject()) { |
| scope.release(); |
| promiseFinallyAwaitJob(globalObject, vm, result, context, JSPromise::Status::Fulfilled); |
| return; |
| } |
| |
| auto* resolutionObject = asObject(result); |
| if (isDefinitelyNonThenable(resolutionObject, globalObject)) { |
| scope.release(); |
| promiseFinallyAwaitJob(globalObject, vm, result, context, JSPromise::Status::Fulfilled); |
| return; |
| } |
| |
| JSValue then; |
| { |
| auto catchScope = DECLARE_TOP_EXCEPTION_SCOPE(vm); |
| then = resolutionObject->get(globalObject, vm.propertyNames->then); |
| if (catchScope.exception()) [[unlikely]] { |
| error = catchScope.exception()->value(); |
| if (!catchScope.clearExceptionExceptTermination()) [[unlikely]] { |
| scope.release(); |
| return; |
| } |
| } |
| } |
| if (error) [[unlikely]] { |
| scope.release(); |
| promiseFinallyAwaitJob(globalObject, vm, error, context, JSPromise::Status::Rejected); |
| return; |
| } |
| |
| if (!then.isCallable()) [[likely]] { |
| scope.release(); |
| promiseFinallyAwaitJob(globalObject, vm, result, context, JSPromise::Status::Fulfilled); |
| return; |
| } |
| |
| auto [resolve, reject] = JSPromise::createResolvingFunctionsWithInternalMicrotask(vm, globalObject, InternalMicrotask::PromiseFinallyAwaitJob, context); |
| scope.release(); |
| promiseResolveThenableJob(globalObject, resolutionObject, then, resolve, reject, microtaskCallCache); |
| } |
| |
| static void asyncModuleExecutionDone(JSGlobalObject* globalObject, ThrowScope& scope, JSModuleRecord* module, JSValue value, JSPromise::Status status) |
| { |
| scope.release(); |
| if (status == JSPromise::Status::Fulfilled) |
| module->asyncExecutionFulfilled(globalObject); |
| else { |
| ASSERT(status == JSPromise::Status::Rejected); |
| module->asyncExecutionRejected(globalObject, value); |
| } |
| } |
| |
| static void asyncModuleExecutionResume(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, JSModuleRecord* module, JSValue resolution, JSPromise::Status status) |
| { |
| auto* capability = module->asyncCapability(); |
| |
| JSValue resumeMode = jsNumber(status == JSPromise::Status::Fulfilled |
| ? static_cast<int32_t>(JSGenerator::ResumeMode::NormalMode) |
| : static_cast<int32_t>(JSGenerator::ResumeMode::ThrowMode)); |
| |
| JSValue result = module->evaluate(globalObject, resolution, resumeMode); |
| |
| if (scope.exception()) |
| capability->rejectWithCaughtException(vm, scope); |
| else { |
| JSValue state = module->internalField(AbstractModuleRecord::Field::State).get(); |
| if (!state.isNumber() || state.asNumber() == static_cast<int32_t>(JSGenerator::State::Executing)) |
| capability->resolve(globalObject, vm, result); |
| else |
| JSPromise::resolveWithInternalMicrotaskForAsyncAwait(globalObject, vm, result, InternalMicrotask::AsyncModuleExecutionResume, module); |
| } |
| } |
| |
| static void moduleRegistryFetchSettled(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // arguments[0] = pre-created modulePromise |
| // arguments[1] = resolution (JSSourceCode*) or rejection (error) |
| // arguments[2] = ModuleRegistryEntry* |
| auto* entry = uncheckedDowncast<ModuleRegistryEntry>(arguments[2]); |
| auto* modulePromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto status = static_cast<JSPromise::Status>(payload); |
| if (status == JSPromise::Status::Fulfilled) { |
| auto* jsSourceCode = downcast<JSSourceCode>(arguments[1]); |
| JSPromise* makeModulePromise = JSModuleLoader::makeModule(globalObject, entry->key(), jsSourceCode); |
| if (scope.exception()) { |
| modulePromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| makeModulePromise->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::ModuleRegistryModuleSettled, modulePromise, entry); |
| } else { |
| JSValue errorValue = arguments[1]; |
| if (auto* error = dynamicDowncast<ErrorInstance>(errorValue)) |
| JSModuleLoader::attachErrorInfo(globalObject, error, nullptr, entry->key(), entry->moduleType(), JSModuleLoader::ModuleFailure::Kind::Instantiation); |
| entry->setFetchError(globalObject, errorValue); |
| modulePromise->reject(vm, errorValue); |
| } |
| } |
| |
| static void moduleRegistryModuleSettled(JSGlobalObject* globalObject, VM& vm, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // arguments[0] = pre-created modulePromise |
| // arguments[1] = resolution (AbstractModuleRecord*) or rejection (error) |
| // arguments[2] = ModuleRegistryEntry* |
| auto* entry = uncheckedDowncast<ModuleRegistryEntry>(arguments[2]); |
| auto* modulePromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto status = static_cast<JSPromise::Status>(payload); |
| if (status == JSPromise::Status::Fulfilled) { |
| auto* moduleRecord = downcast<AbstractModuleRecord>(arguments[1]); |
| entry->fetchComplete(globalObject, moduleRecord); |
| modulePromise->fulfill(vm, moduleRecord); |
| } else { |
| JSValue errorValue = arguments[1]; |
| entry->setEvaluationError(globalObject, errorValue); |
| modulePromise->reject(vm, errorValue); |
| } |
| } |
| |
| static void moduleGraphLoadingError(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // arguments[0] = unused (jsUndefined) |
| // arguments[1] = resolution value or error |
| // arguments[2] = ModuleGraphLoadingState* |
| auto status = static_cast<JSPromise::Status>(payload); |
| if (status == JSPromise::Status::Rejected) { |
| auto* state = uncheckedDowncast<ModuleGraphLoadingState>(arguments[2]); |
| JSValue errorValue = arguments[1]; |
| if (auto* error = dynamicDowncast<ErrorInstance>(errorValue)) { |
| errorValue = JSModuleLoader::maybeDuplicateFetchError(globalObject, error); |
| RETURN_IF_EXCEPTION(scope, void()); |
| } |
| state->promise()->reject(vm, errorValue); |
| } |
| } |
| |
| static void moduleLoadStep(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // arguments[0] = pre-created loadPromise |
| // arguments[1] = resolution value or error |
| // arguments[2] = ModuleLoadingContext* |
| auto* context = uncheckedDowncast<ModuleLoadingContext>(arguments[2]); |
| auto* loadPromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto status = static_cast<JSPromise::Status>(payload); |
| |
| switch (context->step()) { |
| case ModuleLoadingContext::Step::Main: { |
| // modulePromise settled: on fulfillment, call loadRequestedModules and chain next step |
| if (status == JSPromise::Status::Fulfilled) { |
| auto* module = downcast<AbstractModuleRecord>(arguments[1]); |
| context->module(vm, module); |
| JSPromise* requestedPromise = globalObject->moduleLoader()->loadRequestedModules(globalObject, module, context->scriptFetcher()); |
| if (scope.exception()) { |
| loadPromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| context->setStep(ModuleLoadingContext::Step::Requested); |
| requestedPromise->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::ModuleLoadStep, loadPromise, context); |
| } else |
| loadPromise->reject(vm, arguments[1]); |
| return; |
| } |
| case ModuleLoadingContext::Step::Requested: { |
| // loadRequestedModules settled: on fulfillment, call finishLoading and update entry |
| if (status == JSPromise::Status::Fulfilled) { |
| auto* module = context->module(); |
| globalObject->moduleLoader()->finishLoadingImportedModule(globalObject, context->referrer(), context->moduleRequest(), context->payload(), module, context->scriptFetcher()); |
| if (scope.exception()) { |
| loadPromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| |
| // setEntryRecord logic |
| auto* entry = context->entry(); |
| if (auto* cyclic = dynamicDowncast<CyclicModuleRecord>(module); cyclic && cyclic->status() != CyclicModuleRecord::Status::Unlinked) { |
| ASSERT(cyclic->status() != CyclicModuleRecord::Status::Linking); |
| loadPromise->fulfill(vm, entry->record()); |
| } else { |
| entry->setRecord(vm, module); |
| entry->setStatus(ModuleRegistryEntry::Status::Fetched); |
| loadPromise->fulfill(vm, entry->record()); |
| } |
| } else { |
| // onRejected logic: store evaluation error on entry |
| auto* entry = context->entry(); |
| JSValue errorValue = arguments[1]; |
| entry->setEvaluationError(globalObject, errorValue); |
| loadPromise->reject(vm, errorValue); |
| } |
| return; |
| } |
| case ModuleLoadingContext::Step::Cached: { |
| // Cached loadPromise settled: on fulfillment, call finishLoading |
| if (status == JSPromise::Status::Fulfilled) { |
| auto* module = downcast<AbstractModuleRecord>(arguments[1]); |
| globalObject->moduleLoader()->finishLoadingImportedModule(globalObject, context->referrer(), context->moduleRequest(), context->payload(), module, context->scriptFetcher()); |
| if (scope.exception()) { |
| loadPromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| loadPromise->fulfill(vm, module); |
| } else { |
| auto* entry = context->entry(); |
| JSValue errorValue = arguments[1]; |
| entry->setEvaluationError(globalObject, errorValue); |
| loadPromise->reject(vm, errorValue); |
| } |
| return; |
| } |
| } |
| |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| static void moduleLoadTopSettled(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // loadModule first overload: fetch promise settled |
| // arguments[0] = pre-created intermediatePromise |
| // arguments[1] = resolution (JSSourceCode*) or error |
| // arguments[2] = ModuleLoadingContext* |
| auto* context = uncheckedDowncast<ModuleLoadingContext>(arguments[2]); |
| auto* intermediatePromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto status = static_cast<JSPromise::Status>(payload); |
| if (status == JSPromise::Status::Fulfilled) { |
| auto* jsSourceCode = downcast<JSSourceCode>(arguments[1]); |
| |
| const Identifier& specifier = context->moduleRequest().m_specifier; |
| auto type = context->moduleRequest().type(); |
| ScriptFetcher* scriptFetcher = context->scriptFetcher(); |
| |
| globalObject->moduleLoader()->provideFetch(globalObject, specifier, type, jsSourceCode); |
| if (scope.exception()) { |
| intermediatePromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| |
| JSPromise* statePromise = JSPromise::create(vm, globalObject->promiseStructure()); |
| statePromise->markAsHandled(); |
| AbstractModuleRecord::ModuleRequest request { specifier, ScriptFetchParameters::create(type) }; |
| // combinedCell is the host-defined payload AND the AND-join state for loadPromise+statePromise. |
| // For dynamic import we wrap statePromise; for graph load we use the ModuleGraphLoadingState directly. |
| JSCell* combinedCell; |
| JSPromise* loadPromise; |
| |
| OptionSet<ModuleLoadFlag> innerLoadFlags; |
| if (context->useImportMap()) |
| innerLoadFlags.add(ModuleLoadFlag::UseImportMap); |
| if (context->dynamic()) { |
| combinedCell = ModuleLoaderPayload::create(vm, statePromise, context->deferred()); |
| loadPromise = globalObject->moduleLoader()->loadModule(globalObject, globalObject, request, combinedCell, scriptFetcher, innerLoadFlags); |
| } else { |
| combinedCell = ModuleGraphLoadingState::create(vm, statePromise, scriptFetcher); |
| if (context->evaluate()) |
| innerLoadFlags.add(ModuleLoadFlag::Evaluate); |
| loadPromise = globalObject->moduleLoader()->loadModule(globalObject, globalObject, request, combinedCell, scriptFetcher, innerLoadFlags); |
| if (scope.exception()) { |
| intermediatePromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| // Specifier transform: instead of creating a closure, use a microtask |
| JSPromise* transformedStatePromise = JSPromise::create(vm, globalObject->promiseStructure()); |
| transformedStatePromise->markAsHandled(); |
| statePromise->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::ModuleLoadSpecifierTransform, transformedStatePromise, context); |
| statePromise = transformedStatePromise; |
| } |
| |
| if (scope.exception()) { |
| intermediatePromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| |
| JSPromise* combinedPromise = JSPromise::create(vm, globalObject->promiseStructure()); |
| combinedPromise->markAsHandled(); |
| |
| loadPromise->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::ModuleLoadCombinedLoadSettled, combinedPromise, combinedCell); |
| statePromise->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::ModuleLoadCombinedStateSettled, combinedPromise, combinedCell); |
| |
| intermediatePromise->pipeFrom(vm, combinedPromise); |
| } else { |
| // onFetchRejected logic |
| const Identifier& specifier = context->moduleRequest().m_specifier; |
| auto type = context->moduleRequest().type(); |
| ModuleRegistryEntry* entry = globalObject->moduleLoader()->ensureRegistered(globalObject, specifier, type); |
| if (scope.exception()) { |
| intermediatePromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| JSValue errorValue = arguments[1]; |
| if (auto* error = dynamicDowncast<ErrorInstance>(errorValue)) { |
| auto failure = JSModuleLoader::getErrorInfo(globalObject, error); |
| if (failure.isEvaluationError(specifier, type)) |
| entry->setEvaluationError(globalObject, error); |
| else |
| entry->setFetchError(globalObject, error); |
| } |
| intermediatePromise->reject(vm, errorValue); |
| } |
| } |
| |
| static void moduleLoadTopRejected(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // loadModule first overload: onLoadRejected |
| // arguments[0] = pre-created resultPromise |
| // arguments[1] = resolution or error |
| // arguments[2] = ModuleLoadingContext* |
| auto* context = uncheckedDowncast<ModuleLoadingContext>(arguments[2]); |
| auto* resultPromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto status = static_cast<JSPromise::Status>(payload); |
| if (status == JSPromise::Status::Fulfilled) |
| resultPromise->fulfill(vm, arguments[1]); |
| else { |
| const Identifier& specifier = context->moduleRequest().m_specifier; |
| auto type = context->moduleRequest().type(); |
| ModuleRegistryEntry* entry = globalObject->moduleLoader()->ensureRegistered(globalObject, specifier, type); |
| if (scope.exception()) { |
| resultPromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| if (JSValue fetchErrorValue = entry->fetchError()) { |
| if (ErrorInstance* fetchError = dynamicDowncast<ErrorInstance>(fetchErrorValue)) { |
| ErrorInstance* fetchErrorCopy = JSModuleLoader::maybeDuplicateFetchError(globalObject, fetchError); |
| if (scope.exception()) { |
| resultPromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| resultPromise->reject(vm, fetchErrorCopy); |
| } else |
| resultPromise->reject(vm, fetchErrorValue); |
| return; |
| } |
| JSValue error = arguments[1]; |
| entry->setEvaluationError(globalObject, error); |
| resultPromise->reject(vm, error); |
| } |
| } |
| |
| static void moduleLoadSpecifierTransform(JSGlobalObject*, VM& vm, ThrowScope& scope, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // Transforms resolution to specifier identifier |
| // arguments[0] = pre-created transformedStatePromise |
| // arguments[1] = resolution |
| // arguments[2] = ModuleLoadingContext* |
| auto* transformedPromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto status = static_cast<JSPromise::Status>(payload); |
| if (status == JSPromise::Status::Fulfilled) { |
| auto* context = uncheckedDowncast<ModuleLoadingContext>(arguments[2]); |
| scope.release(); |
| transformedPromise->fulfill(vm, identifierToJSValue(vm, context->moduleRequest().m_specifier)); |
| } else |
| transformedPromise->reject(vm, arguments[1]); |
| } |
| |
| static void moduleLoadCombinedLoadSettled(JSGlobalObject*, VM& vm, ThrowScope& scope, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // Combined promise: load side settled |
| // arguments[0] = combinedPromise |
| // arguments[1] = resolution or error |
| // arguments[2] = ModuleGraphLoadingState* (graph load) or ModuleLoaderPayload* (dynamic import) |
| auto* combinedPromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto* combinedCell = arguments[2].asCell(); |
| ASSERT(isModuleLoaderHostDefinedPayload(combinedCell)); |
| auto status = static_cast<JSPromise::Status>(payload); |
| scope.release(); |
| bool fullySettled; |
| if (auto* state = dynamicDowncast<ModuleGraphLoadingState>(combinedCell)) |
| fullySettled = state->decrementRemaining(); |
| else |
| fullySettled = uncheckedDowncast<ModuleLoaderPayload>(combinedCell)->decrementRemaining(); |
| switch (combinedPromise->status()) { |
| case JSPromise::Status::Pending: { |
| if (status == JSPromise::Status::Fulfilled) { |
| if (!fullySettled) |
| return; |
| JSValue fulfillmentValue; |
| if (auto* state = dynamicDowncast<ModuleGraphLoadingState>(combinedCell)) |
| fulfillmentValue = state->fulfillment(); |
| else |
| fulfillmentValue = uncheckedDowncast<ModuleLoaderPayload>(combinedCell)->fulfillment(); |
| ASSERT(fulfillmentValue); |
| combinedPromise->fulfill(vm, fulfillmentValue); |
| } else |
| combinedPromise->reject(vm, arguments[1]); |
| return; |
| } |
| default: |
| return; |
| } |
| } |
| |
| static void moduleLoadCombinedStateSettled(JSGlobalObject*, VM& vm, ThrowScope& scope, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // Combined promise: state side settled |
| // arguments[0] = combinedPromise |
| // arguments[1] = resolution or error |
| // arguments[2] = ModuleGraphLoadingState* (graph load) or ModuleLoaderPayload* (dynamic import) |
| auto* combinedPromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto* combinedCell = arguments[2].asCell(); |
| ASSERT(isModuleLoaderHostDefinedPayload(combinedCell)); |
| auto status = static_cast<JSPromise::Status>(payload); |
| scope.release(); |
| bool fullySettled; |
| if (auto* state = dynamicDowncast<ModuleGraphLoadingState>(combinedCell)) { |
| fullySettled = state->decrementRemaining(); |
| if (status == JSPromise::Status::Fulfilled && !fullySettled) |
| state->setFulfillment(vm, arguments[1]); |
| } else { |
| auto* p = uncheckedDowncast<ModuleLoaderPayload>(combinedCell); |
| fullySettled = p->decrementRemaining(); |
| if (status == JSPromise::Status::Fulfilled && !fullySettled) |
| p->setFulfillment(vm, arguments[1]); |
| } |
| switch (combinedPromise->status()) { |
| case JSPromise::Status::Pending: |
| if (status == JSPromise::Status::Fulfilled) { |
| if (fullySettled) |
| combinedPromise->fulfill(vm, arguments[1]); |
| } else |
| combinedPromise->reject(vm, arguments[1]); |
| return; |
| default: |
| return; |
| } |
| } |
| |
| static void moduleLoadLinkEvaluateSettled(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // loadModule second overload: onFulfilled |
| // arguments[0] = pre-created resultPromise |
| // arguments[1] = resolution (AbstractModuleRecord*) or error |
| // arguments[2] = ModuleLoadingContext* |
| auto* context = uncheckedDowncast<ModuleLoadingContext>(arguments[2]); |
| auto* resultPromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto status = static_cast<JSPromise::Status>(payload); |
| if (status == JSPromise::Status::Fulfilled) { |
| auto* record = downcast<AbstractModuleRecord>(arguments[1]); |
| if (context->evaluate()) { |
| record->link(globalObject, context->scriptFetcher()); |
| JSModuleLoader::attachErrorInfo(globalObject, scope, record, record->moduleKey(), record->moduleType(), JSModuleLoader::ModuleFailure::Kind::Instantiation); |
| if (scope.exception()) { |
| resultPromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| JSPromise* evaluatePromise = record->evaluate(globalObject); |
| JSModuleLoader::attachErrorInfo(globalObject, scope, record, record->moduleKey(), record->moduleType(), JSModuleLoader::ModuleFailure::Kind::Evaluation); |
| if (scope.exception()) { |
| resultPromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| // Chain: when evaluation completes, resolve resultPromise with record |
| evaluatePromise->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::ModuleLoadReturnRecord, resultPromise, record); |
| } else |
| resultPromise->fulfill(vm, identifierToJSValue(vm, record->moduleKey())); |
| } else |
| resultPromise->reject(vm, arguments[1]); |
| } |
| |
| static void moduleLoadReturnRecord(JSGlobalObject*, VM& vm, ThrowScope& scope, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // Resolves promise with the record after evaluation completes |
| // arguments[0] = resultPromise |
| // arguments[1] = resolution or error |
| // arguments[2] = AbstractModuleRecord* |
| auto* resultPromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto status = static_cast<JSPromise::Status>(payload); |
| scope.release(); |
| if (status == JSPromise::Status::Fulfilled) |
| resultPromise->fulfill(vm, arguments[2]); |
| else |
| resultPromise->reject(vm, arguments[1]); |
| } |
| |
| static void moduleLoadStoreError(JSGlobalObject* globalObject, ThrowScope& scope, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // loadModule second overload: fire-and-forget error categorization |
| // arguments[0] = unused |
| // arguments[1] = resolution or error |
| // arguments[2] = ModuleLoadingContext* |
| auto status = static_cast<JSPromise::Status>(payload); |
| if (status == JSPromise::Status::Rejected) { |
| auto* context = uncheckedDowncast<ModuleLoadingContext>(arguments[2]); |
| JSValue errorValue = arguments[1]; |
| const Identifier& specifier = context->moduleRequest().m_specifier; |
| auto type = context->moduleRequest().type(); |
| ModuleRegistryEntry* entry = globalObject->moduleLoader()->ensureRegistered(globalObject, specifier, type); |
| RETURN_IF_EXCEPTION(scope, void()); |
| if (auto* error = dynamicDowncast<ErrorInstance>(errorValue)) { |
| auto failure = JSModuleLoader::getErrorInfo(globalObject, error); |
| if (failure.isEvaluationError(specifier, type)) |
| entry->setEvaluationError(globalObject, error); |
| else if (JSModuleLoader::isFetchError(globalObject, error)) |
| entry->setFetchError(globalObject, error); |
| else |
| entry->setInstantiationError(globalObject, error); |
| } else |
| entry->setEvaluationError(globalObject, errorValue); |
| } |
| } |
| |
| static void resolveDeferredImportNamespace(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, JSPromise* capabilityPromise, AbstractModuleRecord* module) |
| { |
| // ContinueDynamicImport, fulfilledClosure with phase = defer |
| // https://tc39.es/proposal-defer-import-eval/#sec-ContinueDynamicImport |
| // Let namespace be GetModuleNamespace(module, phase). |
| JSModuleNamespaceObject* moduleNamespace = module->getModuleNamespace(globalObject, AbstractModuleRecord::ModulePhase::Defer); |
| if (scope.exception()) [[unlikely]] { |
| capabilityPromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| // Perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace »). |
| // (See dynamicImportEvaluateSettled for why fulfill is used on this internal promise.) |
| capabilityPromise->fulfill(vm, moduleNamespace); |
| } |
| |
| static void dynamicImportLoadSettled(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload, bool deferred) |
| { |
| // https://tc39.es/ecma262/#sec-ContinueDynamicImport |
| // https://tc39.es/proposal-defer-import-eval/#sec-ContinueDynamicImport (deferred) |
| // Step-4 rejectedClosure or Step-6 linkAndEvaluateClosure |
| // |
| // continueDynamicImport: loadPromise settled |
| // arguments[0] = capabilityPromise |
| // arguments[1] = resolution or error |
| // arguments[2] = AbstractModuleRecord* |
| auto* capabilityPromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto* module = uncheckedDowncast<AbstractModuleRecord>(arguments[2]); |
| auto status = static_cast<JSPromise::Status>(payload); |
| if (status != JSPromise::Status::Fulfilled) { |
| // Step-4 rejectedClosure |
| // 4.a. Perform ! Call(promiseCapability.[[Reject]], undefined, « reason »). |
| capabilityPromise->reject(vm, arguments[1]); |
| return; |
| } |
| |
| // Step-6 linkAndEvaluateClosure |
| // 6.a. Let link be Completion(module.Link()). |
| module->link(globalObject, nullptr); |
| |
| // 6.b. If link is an abrupt completion, then |
| if (Exception* exception = scope.exception()) [[unlikely]] { |
| // 6.b.i. Perform ! Call(promiseCapability.[[Reject]], undefined, « link.[[Value]] »). |
| JSModuleLoader::attachErrorInfo(globalObject, exception, module, module->moduleKey(), module->moduleType(), JSModuleLoader::ModuleFailure::Kind::Instantiation); |
| capabilityPromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| |
| if (!deferred) { |
| // 6.c. Let evaluatePromise be module.Evaluate(). |
| JSPromise* evaluatePromise = module->evaluate(globalObject); |
| if (scope.exception()) [[unlikely]] { |
| capabilityPromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| |
| // 6.d-f. Perform PerformPromiseThen(evaluatePromise, onFulfilled, onRejected). |
| evaluatePromise->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::DynamicImportEvaluateSettled, capabilityPromise, module); |
| return; |
| } |
| |
| // Deferred phase: do not evaluate the deferred root. Eagerly evaluate only the |
| // post-order list of unexecuted top-level-await modules in the graph; once they |
| // all settle, hand back the deferred namespace. |
| // |
| // Let evaluationList be GatherAsynchronousTransitiveDependencies(module). |
| OrderedHashSet<AbstractModuleRecord*> evaluationList; |
| UncheckedKeyHashSet<AbstractModuleRecord*> seen; |
| module->gatherAsynchronousTransitiveDependencies(evaluationList, seen); |
| |
| // If evaluationList is empty, perform fulfilledClosure() and return. |
| if (evaluationList.isEmpty()) { |
| resolveDeferredImportNamespace(globalObject, vm, scope, capabilityPromise, module); |
| return; |
| } |
| |
| // For each Module Record dep of evaluationList, append dep.Evaluate() to asyncDepsEvaluationPromises. |
| MarkedArgumentBuffer asyncDepsEvaluationPromises; |
| for (AbstractModuleRecord* dep : evaluationList) { |
| JSPromise* depPromise = dep->evaluate(globalObject); |
| if (scope.exception()) [[unlikely]] { |
| capabilityPromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| ASSERT(depPromise); |
| asyncDepsEvaluationPromises.append(depPromise); |
| } |
| if (asyncDepsEvaluationPromises.hasOverflowed()) [[unlikely]] { |
| throwOutOfMemoryError(globalObject, scope); |
| capabilityPromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| |
| // Let evaluatePromise be ! SafePerformPromiseAll(asyncDepsEvaluationPromises); |
| // PerformPromiseThen(evaluatePromise, onFulfilled, onRejected). |
| // We inline the AND-join: each dep promise either rejects capabilityPromise (idempotent), |
| // or decrements the join count; the last dep to fulfill resolves the deferred namespace. |
| auto* joinContext = JSPromiseCombinatorsGlobalContext::create(vm, capabilityPromise, module, asyncDepsEvaluationPromises.size()); |
| for (unsigned i = 0; i < asyncDepsEvaluationPromises.size(); ++i) { |
| auto* depPromise = uncheckedDowncast<JSPromise>(asyncDepsEvaluationPromises.at(i)); |
| depPromise->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::DynamicImportDeferDependencySettled, capabilityPromise, joinContext); |
| } |
| } |
| |
| static void dynamicImportDeferDependencySettled(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // SafePerformPromiseAll AND-join for the deferred-phase ContinueDynamicImport. |
| // arguments[0] = capabilityPromise |
| // arguments[1] = resolution or error |
| // arguments[2] = JSPromiseCombinatorsGlobalContext* (m_promise = capabilityPromise, m_values = module, m_remainingElementsCount = count) |
| auto* capabilityPromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto* joinContext = uncheckedDowncast<JSPromiseCombinatorsGlobalContext>(arguments[2]); |
| auto status = static_cast<JSPromise::Status>(payload); |
| if (status != JSPromise::Status::Fulfilled) { |
| // First rejection wins; reject() on a settled promise is a no-op. |
| capabilityPromise->reject(vm, arguments[1]); |
| return; |
| } |
| uint64_t count = joinContext->remainingElementsCount(); |
| ASSERT(count > 0); |
| uint64_t remaining = count - 1; |
| joinContext->setRemainingElementsCount(remaining); |
| if (remaining) |
| return; |
| auto* module = uncheckedDowncast<AbstractModuleRecord>(joinContext->values()); |
| resolveDeferredImportNamespace(globalObject, vm, scope, capabilityPromise, module); |
| } |
| |
| static void dynamicImportEvaluateSettled(JSGlobalObject* globalObject, VM& vm, ThrowScope& scope, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // https://tc39.es/ecma262/#sec-ContinueDynamicImport |
| // Step-4 rejectedClosure or Step-6.c fulfilledClosure |
| // |
| // continueDynamicImport: evaluate settled |
| // arguments[0] = capabilityPromise |
| // arguments[1] = resolution or error |
| // arguments[2] = AbstractModuleRecord* |
| auto* capabilityPromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto* module = uncheckedDowncast<AbstractModuleRecord>(arguments[2]); |
| auto status = static_cast<JSPromise::Status>(payload); |
| if (status == JSPromise::Status::Fulfilled) { |
| // 6.d.i. Let namespace be GetModuleNamespace(module). |
| JSModuleNamespaceObject* moduleNamespace = module->getModuleNamespace(globalObject); |
| if (scope.exception()) [[unlikely]] { |
| capabilityPromise->rejectWithCaughtException(vm, scope); |
| return; |
| } |
| |
| // 6.d.ii. Perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace »). |
| // This step resolves the promiseCapability with the namespace. However, |
| // capabilityPromise here is the internal statePromise from moduleLoadTopSettled, |
| // not the user-visible import() promise. The actual spec-required resolve() |
| // happens in importModuleNamespace. Use fulfill here to avoid unnecessary |
| // thenable unwrapping on internal pipeline. |
| // |
| // FIXME: This is different from the spec while user-observable behavior is correctly |
| // aligned (as "resolve" will happen in resultPromise side from dynamic import). |
| // But ideally, this carried capabilityPromise should be the last user-observable |
| // promise and we should do "resolve" here. This requires some clean up. |
| capabilityPromise->fulfill(vm, moduleNamespace); |
| } else |
| capabilityPromise->reject(vm, arguments[1]); |
| } |
| |
| static void importModuleNamespace(JSGlobalObject* globalObject, VM& vm, ThrowScope&, std::span<const JSValue, maxMicrotaskArguments> arguments, uint8_t payload) |
| { |
| // requestImportModule: namespace getter |
| // arguments[0] = resultPromise |
| // arguments[1] = module namespace (from dynamic import pipeline) or error |
| // arguments[2] = unused |
| auto* resultPromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto status = static_cast<JSPromise::Status>(payload); |
| if (status == JSPromise::Status::Fulfilled) { |
| // The value is a JSModuleNamespaceObject forwarded from the internal |
| // pipeline (dynamicImportEvaluateSettled → combinedPromise → here). |
| // resultPromise is the user-visible import() promise. Must use resolve() per spec: |
| // ContinueDynamicImport https://tc39.es/ecma262/#sec-ContinueDynamicImport |
| // Step 6.d.ii: Call(promiseCapability.[[Resolve]], undefined, « namespace »). |
| // A module namespace that exports "then" is a thenable per spec. |
| auto* moduleNamespace = downcast<JSModuleNamespaceObject>(arguments[1]); |
| resultPromise->resolve(globalObject, vm, moduleNamespace); |
| } else |
| resultPromise->reject(vm, arguments[1]); |
| return; |
| } |
| |
| static void promiseResolveWithoutHandlerJobSlow(JSGlobalObject* globalObject, VM& vm, JSValue capability, JSValue resolution, JSPromise::Status status) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (status == JSPromise::Status::Rejected) { |
| JSValue reject = capability.get(globalObject, vm.propertyNames->reject); |
| RETURN_IF_EXCEPTION(scope, void()); |
| |
| MarkedArgumentBuffer arguments; |
| arguments.append(resolution); |
| ASSERT(!arguments.hasOverflowed()); |
| scope.release(); |
| call(globalObject, reject, jsUndefined(), arguments, "reject is not a function"_s); |
| return; |
| } |
| |
| JSValue resolve = capability.get(globalObject, vm.propertyNames->resolve); |
| RETURN_IF_EXCEPTION(scope, void()); |
| |
| MarkedArgumentBuffer arguments; |
| arguments.append(resolution); |
| ASSERT(!arguments.hasOverflowed()); |
| scope.release(); |
| call(globalObject, resolve, jsUndefined(), arguments, "resolve is not a function"_s); |
| } |
| |
| static void promiseResolveWithoutHandlerJob(JSGlobalObject* globalObject, VM& vm, JSValue promiseOrCapability, JSValue resolution, JSPromise::Status status) |
| { |
| if (auto* promise = dynamicDowncast<JSPromise>(promiseOrCapability)) [[likely]] { |
| switch (status) { |
| case JSPromise::Status::Pending: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| case JSPromise::Status::Fulfilled: |
| promise->resolvePromise(promise->realm(), vm, resolution); |
| break; |
| case JSPromise::Status::Rejected: |
| promise->rejectPromise(vm, resolution); |
| break; |
| } |
| return; |
| } |
| |
| promiseResolveWithoutHandlerJobSlow(globalObject, vm, promiseOrCapability, resolution, status); |
| } |
| |
| #if ENABLE(WEBASSEMBLY) |
| static void webAssemblyCompileStreaming(JSGlobalObject* globalObject, VM& vm, JSValue resolution, JSWebAssemblyStreamingContext* context, JSPromise::Status status) |
| { |
| JSPromise* outerPromise = context->promise(); |
| if (status == JSPromise::Status::Rejected) { |
| outerPromise->reject(vm, resolution); |
| return; |
| } |
| ASSERT(globalObject->globalObjectMethodTable()->compileStreaming); |
| globalObject->globalObjectMethodTable()->compileStreaming(globalObject, outerPromise, resolution, context->takeCompileOptions()); |
| } |
| |
| static void webAssemblyInstantiateStreaming(JSGlobalObject* globalObject, VM& vm, JSValue resolution, JSWebAssemblyStreamingContext* context, JSPromise::Status status) |
| { |
| JSPromise* outerPromise = context->promise(); |
| if (status == JSPromise::Status::Rejected) { |
| outerPromise->reject(vm, resolution); |
| return; |
| } |
| |
| // FIXME: <http://webkit.org/b/184888> if there's an importObject and it contains a Memory, then we can compile the module with the right memory type (fast or not) by looking at the memory's type. |
| ASSERT(globalObject->globalObjectMethodTable()->instantiateStreaming); |
| globalObject->globalObjectMethodTable()->instantiateStreaming(globalObject, outerPromise, resolution, context->importObject(), context->takeCompileOptions()); |
| } |
| #endif |
| |
| static void asyncFunctionArrangeAwaitResume(JSGlobalObject* globalObject, VM& vm, JSAsyncFunctionGenerator* generator, JSValue value) |
| { |
| if (value == vm.fastAsyncGeneratorSentinel()) |
| return; |
| JSPromise::resolveWithInternalMicrotaskForAsyncAwait(globalObject, vm, value, InternalMicrotask::AsyncFunctionResume, generator); |
| } |
| |
| // Drives one step of a suspended async function's continuation. |
| static void asyncFunctionGeneratorBodyCall(JSGlobalObject* generatorGlobalObject, VM& vm, JSAsyncFunctionGenerator* generator, JSValue resolution, JSGenerator::ResumeMode resumeMode, MicrotaskCallCache* microtaskCallCache) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| int32_t state = generator->state(); |
| generator->setState(static_cast<int32_t>(JSGenerator::State::Executing)); |
| JSValue next = generator->next(); |
| JSValue thisValue = generator->thisValue(); |
| JSValue frame = generator->frame(); |
| JSValue value; |
| JSValue error; |
| { |
| auto catchScope = DECLARE_TOP_EXCEPTION_SCOPE(vm); |
| value = callMicrotask(generatorGlobalObject, next, thisValue, generator, "handler is not a function"_s, microtaskCallCache, |
| generator, jsNumber(state), resolution, jsNumber(static_cast<int32_t>(resumeMode)), frame); |
| if (catchScope.exception()) { |
| error = catchScope.exception()->value(); |
| if (!catchScope.clearExceptionExceptTermination()) [[unlikely]] { |
| scope.release(); |
| return; |
| } |
| } |
| } |
| |
| if (error) { |
| auto* promise = uncheckedDowncast<JSPromise>(generator->context()); |
| promise->reject(vm, error); |
| return; |
| } |
| |
| if (generator->state() == static_cast<int32_t>(JSGenerator::State::Executing)) { |
| auto* promise = uncheckedDowncast<JSPromise>(generator->context()); |
| scope.release(); |
| promise->resolve(generatorGlobalObject, vm, value); |
| return; |
| } |
| |
| // The body suspended again at op_async_iterator_next's fast branch + op_yield. The resume |
| // is already arranged by enqueuing this driver on the producer, so skip the normal |
| // await-Promise attachment below. |
| scope.release(); |
| asyncFunctionArrangeAwaitResume(generatorGlobalObject, vm, generator, value); |
| } |
| |
| JSC_DEFINE_HOST_FUNCTION(asyncFunctionDrive, (JSGlobalObject* globalObject, CallFrame* callFrame)) |
| { |
| VM& vm = globalObject->vm(); |
| JSValue resolution = callFrame->uncheckedArgument(0); |
| auto* generator = uncheckedDowncast<JSAsyncFunctionGenerator>(callFrame->uncheckedArgument(1)); |
| asyncFunctionArrangeAwaitResume(globalObject, vm, generator, resolution); |
| return encodedJSUndefined(); |
| } |
| |
| static JSGenerator::ResumeMode resumeModeForStatus(JSPromise::Status status) |
| { |
| RELEASE_ASSERT(status != JSPromise::Status::Pending); |
| return status == JSPromise::Status::Rejected ? JSGenerator::ResumeMode::ThrowMode : JSGenerator::ResumeMode::NormalMode; |
| } |
| |
| // A for-await driver (op_async_iterator_next's fast branch) resumed directly by the producer it is |
| // consuming. `context` is the driver. |
| static void asyncGeneratorDriverResume(VM& vm, JSValue context, JSValue resolution, JSPromise::Status status, MicrotaskCallCache* microtaskCallCache) |
| { |
| JSGenerator::ResumeMode resumeMode = resumeModeForStatus(status); |
| if (auto* asyncFunctionGenerator = dynamicDowncast<JSAsyncFunctionGenerator>(context)) { |
| asyncFunctionGeneratorBodyCall(asyncFunctionGenerator->realm(), vm, asyncFunctionGenerator, resolution, resumeMode, microtaskCallCache); |
| return; |
| } |
| |
| auto* generator = uncheckedDowncast<JSAsyncGenerator>(context); |
| asyncGeneratorBodyCall(generator->realm(), generator, resolution, static_cast<int32_t>(resumeMode), microtaskCallCache); |
| } |
| |
| void runInternalMicrotask(JSGlobalObject* globalObject, VM& vm, InternalMicrotask task, uint8_t payload, std::span<const JSValue, maxMicrotaskArguments> arguments, MicrotaskCallCache* microtaskCallCache) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| switch (task) { |
| case InternalMicrotask::None: { |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| |
| case InternalMicrotask::PromiseResolveThenableJobFast: { |
| auto* promise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto* promiseToResolve = uncheckedDowncast<JSPromise>(arguments[1]); |
| |
| if (!promiseSpeciesWatchpointIsValid(vm, promise)) [[unlikely]] |
| RELEASE_AND_RETURN(scope, promiseResolveThenableJobFastSlow(globalObject, promise, promiseToResolve)); |
| |
| scope.release(); |
| promise->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::PromiseResolveWithoutHandlerJob, promiseToResolve, jsUndefined()); |
| return; |
| } |
| |
| case InternalMicrotask::PromiseResolveThenableJobWithInternalMicrotaskFast: { |
| auto* promise = uncheckedDowncast<JSPromise>(arguments[0]); |
| JSValue context = arguments[1]; |
| auto task = static_cast<InternalMicrotask>(payload); |
| |
| if (!promiseSpeciesWatchpointIsValid(vm, promise)) [[unlikely]] |
| RELEASE_AND_RETURN(scope, promiseResolveThenableJobWithInternalMicrotaskFastSlow(globalObject, promise, task, context)); |
| |
| promise->performPromiseThenWithInternalMicrotask(vm, task, nullptr, context); |
| return; |
| } |
| |
| case InternalMicrotask::PromiseResolveThenableJob: { |
| JSValue promise = arguments[0]; |
| JSValue then = arguments[1]; |
| JSPromise* promiseToResolve = uncheckedDowncast<JSPromise>(arguments[2]); |
| auto [resolve, reject] = promiseToResolve->createResolvingFunctions(vm, globalObject); |
| RELEASE_AND_RETURN(scope, promiseResolveThenableJob(globalObject, promise, then, resolve, reject, microtaskCallCache)); |
| } |
| |
| case InternalMicrotask::PromiseResolveThenableJobWithInternalMicrotask: { |
| auto task = static_cast<InternalMicrotask>(payload); |
| JSValue promise = arguments[0]; |
| JSValue then = arguments[1]; |
| JSValue context = arguments[2]; |
| auto [resolve, reject] = JSPromise::createResolvingFunctionsWithInternalMicrotask(vm, globalObject, task, context); |
| RELEASE_AND_RETURN(scope, promiseResolveThenableJob(globalObject, promise, then, resolve, reject, microtaskCallCache)); |
| } |
| |
| case InternalMicrotask::PromiseResolveWithoutHandlerJob: { |
| RELEASE_AND_RETURN(scope, promiseResolveWithoutHandlerJob(globalObject, vm, arguments[0], arguments[1], static_cast<JSPromise::Status>(payload))); |
| } |
| |
| case InternalMicrotask::PromiseFulfillWithoutHandlerJob: { |
| auto* promise = uncheckedDowncast<JSPromise>(arguments[0]); |
| JSValue resolution = arguments[1]; |
| switch (static_cast<JSPromise::Status>(payload)) { |
| case JSPromise::Status::Pending: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| case JSPromise::Status::Fulfilled: |
| scope.release(); |
| promise->fulfillPromise(vm, resolution); |
| break; |
| case JSPromise::Status::Rejected: |
| scope.release(); |
| promise->rejectPromise(vm, resolution); |
| break; |
| } |
| return; |
| } |
| |
| case InternalMicrotask::PromiseRaceResolveJob: { |
| auto* promise = uncheckedDowncast<JSPromise>(arguments[0]); |
| RELEASE_AND_RETURN(scope, promiseRaceResolveJob(promise->realm(), vm, promise, arguments[1], static_cast<JSPromise::Status>(payload))); |
| } |
| |
| case InternalMicrotask::PromiseAllResolveJob: { |
| auto* globalContext = uncheckedDowncast<JSPromiseCombinatorsGlobalContext>(arguments[0]); |
| auto* resultPromise = uncheckedDowncast<JSPromise>(globalContext->promise()); |
| RELEASE_AND_RETURN(scope, promiseAllResolveJob(resultPromise->realm(), vm, globalContext, arguments[1], static_cast<uint64_t>(arguments[2].asAnyInt()), static_cast<JSPromise::Status>(payload))); |
| } |
| |
| case InternalMicrotask::PromiseAllSettledResolveJob: { |
| auto* globalContext = uncheckedDowncast<JSPromiseCombinatorsGlobalContext>(arguments[0]); |
| auto* resultPromise = uncheckedDowncast<JSPromise>(globalContext->promise()); |
| RELEASE_AND_RETURN(scope, promiseAllSettledResolveJob(resultPromise->realm(), vm, globalContext, arguments[1], static_cast<uint64_t>(arguments[2].asAnyInt()), static_cast<JSPromise::Status>(payload))); |
| } |
| |
| case InternalMicrotask::PromiseAnyResolveJob: { |
| auto* globalContext = uncheckedDowncast<JSPromiseCombinatorsGlobalContext>(arguments[0]); |
| auto* resultPromise = uncheckedDowncast<JSPromise>(globalContext->promise()); |
| RELEASE_AND_RETURN(scope, promiseAnyResolveJob(resultPromise->realm(), vm, globalContext, arguments[1], static_cast<uint64_t>(arguments[2].asAnyInt()), static_cast<JSPromise::Status>(payload))); |
| } |
| |
| case InternalMicrotask::PromiseReactionJob: { |
| JSValue promiseOrCapability = arguments[0]; |
| JSValue handler = arguments[1]; |
| |
| JSValue result; |
| JSValue error; |
| { |
| auto catchScope = DECLARE_TOP_EXCEPTION_SCOPE(vm); |
| result = callMicrotask(globalObject, handler, jsUndefined(), dynamicCastToCell(handler), "handler is not a function"_s, microtaskCallCache, arguments[2]); |
| if (catchScope.exception()) { |
| if (promiseOrCapability.isUndefinedOrNull()) { |
| scope.release(); |
| return; |
| } |
| error = catchScope.exception()->value(); |
| if (!catchScope.clearExceptionExceptTermination()) [[unlikely]] { |
| scope.release(); |
| return; |
| } |
| } |
| |
| if (promiseOrCapability.isUndefinedOrNull()) { |
| scope.release(); |
| return; |
| } |
| |
| ASSERT(result || error); |
| } |
| |
| if (error) { |
| if (auto* promise = dynamicDowncast<JSPromise>(promiseOrCapability)) |
| RELEASE_AND_RETURN(scope, promise->rejectPromise(vm, error)); |
| |
| JSValue reject = promiseOrCapability.get(globalObject, vm.propertyNames->reject); |
| RETURN_IF_EXCEPTION(scope, void()); |
| |
| MarkedArgumentBuffer arguments; |
| arguments.append(error); |
| ASSERT(!arguments.hasOverflowed()); |
| scope.release(); |
| call(globalObject, reject, jsUndefined(), arguments, "reject is not a function"_s); |
| return; |
| } |
| |
| if (auto* promise = dynamicDowncast<JSPromise>(promiseOrCapability)) |
| RELEASE_AND_RETURN(scope, promise->resolvePromise(promise->realm(), vm, result)); |
| |
| JSValue resolve = promiseOrCapability.get(globalObject, vm.propertyNames->resolve); |
| RETURN_IF_EXCEPTION(scope, void()); |
| |
| MarkedArgumentBuffer arguments; |
| arguments.append(result); |
| ASSERT(!arguments.hasOverflowed()); |
| scope.release(); |
| call(globalObject, resolve, jsUndefined(), arguments, "resolve is not a function"_s); |
| return; |
| } |
| |
| case InternalMicrotask::InvokeFunctionJob: { |
| JSValue handler = arguments[0]; |
| scope.release(); |
| callMicrotask(globalObject, handler, jsUndefined(), nullptr, "handler is not a function"_s, microtaskCallCache); |
| return; |
| } |
| |
| case InternalMicrotask::AsyncFunctionResume: { |
| JSValue resolution = arguments[1]; |
| auto* generator = uncheckedDowncast<JSAsyncFunctionGenerator>(arguments[2]); |
| JSGlobalObject* generatorGlobalObject = generator->realm(); |
| JSGenerator::ResumeMode resumeMode = resumeModeForStatus(static_cast<JSPromise::Status>(payload)); |
| |
| scope.release(); |
| asyncFunctionGeneratorBodyCall(generatorGlobalObject, vm, generator, resolution, resumeMode, microtaskCallCache); |
| return; |
| } |
| |
| case InternalMicrotask::AsyncFromSyncIteratorContinue: |
| case InternalMicrotask::AsyncFromSyncIteratorDone: { |
| auto* iterator = uncheckedDowncast<JSAsyncFromSyncIterator>(arguments[2]); |
| RELEASE_AND_RETURN(scope, asyncFromSyncIteratorContinueOrDone(iterator->realm(), vm, iterator, arguments[1], static_cast<JSPromise::Status>(payload), task == InternalMicrotask::AsyncFromSyncIteratorDone, microtaskCallCache)); |
| } |
| |
| case InternalMicrotask::AsyncGeneratorYieldAwaited: { |
| auto* generator = uncheckedDowncast<JSAsyncGenerator>(arguments[2]); |
| RELEASE_AND_RETURN(scope, asyncGeneratorYieldAwaited(generator->realm(), generator, arguments[1], static_cast<JSPromise::Status>(payload), microtaskCallCache)); |
| } |
| |
| case InternalMicrotask::AsyncGeneratorBodyCallNormal: { |
| auto* generator = uncheckedDowncast<JSAsyncGenerator>(arguments[2]); |
| RELEASE_AND_RETURN(scope, asyncGeneratorBodyCallNormal(generator->realm(), generator, arguments[1], static_cast<JSPromise::Status>(payload), microtaskCallCache)); |
| } |
| |
| case InternalMicrotask::AsyncGeneratorBodyCallReturn: { |
| auto* generator = uncheckedDowncast<JSAsyncGenerator>(arguments[2]); |
| RELEASE_AND_RETURN(scope, asyncGeneratorBodyCallReturn(generator->realm(), generator, arguments[1], static_cast<JSPromise::Status>(payload), microtaskCallCache)); |
| } |
| |
| case InternalMicrotask::AsyncGeneratorAwaitReturn: { |
| auto* generator = uncheckedDowncast<JSAsyncGenerator>(arguments[2]); |
| RELEASE_AND_RETURN(scope, asyncGeneratorAwaitReturnContinuation(generator->realm(), generator, arguments[1], static_cast<JSPromise::Status>(payload))); |
| } |
| |
| case InternalMicrotask::AsyncGeneratorDriverResume: |
| RELEASE_AND_RETURN(scope, asyncGeneratorDriverResume(vm, arguments[2], arguments[1], static_cast<JSPromise::Status>(payload), microtaskCallCache)); |
| |
| case InternalMicrotask::PromiseFinallyReactionJob: { |
| // Phase 1: Original promise settled |
| // arguments[0] = resultPromise |
| // arguments[1] = value/reason from original promise |
| // arguments[2] = context (JSSlimPromiseReaction: promise=resultPromise, handlerOrContext=onFinally) |
| // payload = Fulfilled/Rejected status |
| auto* resultPromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| scope.release(); |
| promiseFinallyReactionJob(resultPromise->realm(), vm, |
| resultPromise, |
| arguments[1], |
| uncheckedDowncast<JSSlimPromiseReaction>(arguments[2]), |
| static_cast<JSPromise::Status>(payload), |
| microtaskCallCache); |
| return; |
| } |
| |
| case InternalMicrotask::PromiseFinallyAwaitJob: { |
| // Phase 2: onFinally's result settled |
| // arguments[0] = unused (we get resultPromise from context) |
| // arguments[1] = settled value from onFinally's result |
| // arguments[2] = context (JSSlimPromiseReaction: promise=resultPromise, handlerOrContext=originalValue, perCellBit=wasFulfilled) |
| // payload = status of onFinally's result |
| auto* context = uncheckedDowncast<JSSlimPromiseReaction>(arguments[2]); |
| auto* resultPromise = uncheckedDowncast<JSPromise>(context->promise()); |
| scope.release(); |
| promiseFinallyAwaitJob(resultPromise->realm(), vm, |
| arguments[1], |
| context, |
| static_cast<JSPromise::Status>(payload)); |
| return; |
| } |
| |
| case InternalMicrotask::AsyncModuleExecutionDone: { |
| auto* module = uncheckedDowncast<JSModuleRecord>(arguments[2]); |
| asyncModuleExecutionDone(module->realm(), scope, module, arguments[1], static_cast<JSPromise::Status>(payload)); |
| return; |
| } |
| |
| case InternalMicrotask::AsyncModuleExecutionResume: { |
| auto* module = uncheckedDowncast<JSModuleRecord>(arguments[2]); |
| asyncModuleExecutionResume(module->realm(), vm, scope, module, arguments[1], static_cast<JSPromise::Status>(payload)); |
| return; |
| } |
| |
| case InternalMicrotask::ModuleRegistryFetchSettled: { |
| moduleRegistryFetchSettled(globalObject, vm, scope, arguments, payload); |
| return; |
| } |
| |
| case InternalMicrotask::ModuleRegistryModuleSettled: { |
| moduleRegistryModuleSettled(globalObject, vm, arguments, payload); |
| return; |
| } |
| |
| case InternalMicrotask::ModuleGraphLoadingError: { |
| moduleGraphLoadingError(globalObject, vm, scope, arguments, payload); |
| return; |
| } |
| |
| case InternalMicrotask::ModuleLoadStep: { |
| moduleLoadStep(globalObject, vm, scope, arguments, payload); |
| return; |
| } |
| |
| case InternalMicrotask::ModuleLoadTopSettled: { |
| moduleLoadTopSettled(globalObject, vm, scope, arguments, payload); |
| return; |
| } |
| |
| case InternalMicrotask::ModuleLoadTopRejected: { |
| moduleLoadTopRejected(globalObject, vm, scope, arguments, payload); |
| return; |
| } |
| |
| case InternalMicrotask::ModuleLoadSpecifierTransform: { |
| moduleLoadSpecifierTransform(globalObject, vm, scope, arguments, payload); |
| return; |
| } |
| |
| case InternalMicrotask::ModuleLoadCombinedLoadSettled: { |
| moduleLoadCombinedLoadSettled(globalObject, vm, scope, arguments, payload); |
| return; |
| } |
| |
| case InternalMicrotask::ModuleLoadCombinedStateSettled: { |
| moduleLoadCombinedStateSettled(globalObject, vm, scope, arguments, payload); |
| return; |
| } |
| |
| case InternalMicrotask::ModuleLoadLinkEvaluateSettled: { |
| moduleLoadLinkEvaluateSettled(globalObject, vm, scope, arguments, payload); |
| return; |
| } |
| |
| case InternalMicrotask::ModuleLoadReturnRecord: { |
| moduleLoadReturnRecord(globalObject, vm, scope, arguments, payload); |
| return; |
| } |
| |
| case InternalMicrotask::ModuleLoadReturnModuleKey: { |
| // loadAndEvaluateModule: extract module key from AbstractModuleRecord |
| // arguments[0] = resultPromise |
| // arguments[1] = resolution (AbstractModuleRecord*) or error |
| auto* resultPromise = uncheckedDowncast<JSPromise>(arguments[0]); |
| auto status = static_cast<JSPromise::Status>(payload); |
| scope.release(); |
| if (status == JSPromise::Status::Fulfilled) { |
| auto* module = downcast<AbstractModuleRecord>(arguments[1]); |
| resultPromise->fulfillPromise(vm, identifierToJSValue(vm, module->moduleKey())); |
| } else |
| resultPromise->rejectPromise(vm, arguments[1]); |
| return; |
| } |
| |
| case InternalMicrotask::ModuleLoadStoreError: { |
| moduleLoadStoreError(globalObject, scope, arguments, payload); |
| return; |
| } |
| |
| case InternalMicrotask::DynamicImportLoadSettled: { |
| dynamicImportLoadSettled(globalObject, vm, scope, arguments, payload, /* deferred */ false); |
| return; |
| } |
| |
| case InternalMicrotask::DynamicImportDeferLoadSettled: { |
| dynamicImportLoadSettled(globalObject, vm, scope, arguments, payload, /* deferred */ true); |
| return; |
| } |
| |
| case InternalMicrotask::DynamicImportEvaluateSettled: { |
| dynamicImportEvaluateSettled(globalObject, vm, scope, arguments, payload); |
| return; |
| } |
| |
| case InternalMicrotask::DynamicImportDeferDependencySettled: { |
| dynamicImportDeferDependencySettled(globalObject, vm, scope, arguments, payload); |
| return; |
| } |
| |
| case InternalMicrotask::ImportModuleNamespace: { |
| importModuleNamespace(globalObject, vm, scope, arguments, payload); |
| return; |
| } |
| |
| #if ENABLE(WEBASSEMBLY) |
| case InternalMicrotask::WebAssemblyCompileStreaming: |
| scope.release(); |
| webAssemblyCompileStreaming(globalObject, vm, arguments[1], uncheckedDowncast<JSWebAssemblyStreamingContext>(arguments[2].asCell()), static_cast<JSPromise::Status>(payload)); |
| return; |
| |
| case InternalMicrotask::WebAssemblyInstantiateStreaming: |
| scope.release(); |
| webAssemblyInstantiateStreaming(globalObject, vm, arguments[1], uncheckedDowncast<JSWebAssemblyStreamingContext>(arguments[2].asCell()), static_cast<JSPromise::Status>(payload)); |
| return; |
| #endif |
| |
| case InternalMicrotask::Opaque: { |
| RELEASE_ASSERT_NOT_REACHED(); |
| return; |
| } |
| } |
| } |
| |
| } // namespace JSC |
| |
| WTF_ALLOW_UNSAFE_BUFFER_USAGE_END |