
This commit removes all exception related code: Remove VM::exception(), VM::throw_exception() etc. Any leftover throw_exception calls are moved to throw_completion. The one method left is clear_exception() which is now a no-op. Most of these calls are just to clear whatever exception might have been thrown when handling a Completion. So to have a cleaner commit this will be removed in a next commit. It also removes the actual Exception and TemporaryClearException classes since these are no longer used. In any spot where the exception was actually used an attempt was made to preserve that behavior. However since it is no longer tracked by the VM we cannot access exceptions which were thrown in previous calls. There are two such cases which might have different behavior: - In Web::DOM::Document::interpreter() the on_call_stack_emptied hook used to print any uncaught exception but this is now no longer possible as the VM does not store uncaught exceptions. - In js the code used to be interruptable by throwing an exception on the VM. This is no longer possible but was already somewhat fragile before as you could happen to throw an exception just before a VERIFY.
121 lines
5.7 KiB
C++
121 lines
5.7 KiB
C++
/*
|
|
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
|
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/TypeCasts.h>
|
|
#include <LibCore/EventLoop.h>
|
|
#include <LibJS/Runtime/Completion.h>
|
|
#include <LibJS/Runtime/GlobalObject.h>
|
|
#include <LibJS/Runtime/NativeFunction.h>
|
|
#include <LibJS/Runtime/PromiseConstructor.h>
|
|
#include <LibJS/Runtime/PromiseReaction.h>
|
|
#include <LibJS/Runtime/VM.h>
|
|
#include <LibJS/Runtime/Value.h>
|
|
|
|
namespace JS {
|
|
|
|
Completion::Completion(ThrowCompletionOr<Value> const& throw_completion_or_value)
|
|
{
|
|
if (throw_completion_or_value.is_throw_completion()) {
|
|
m_type = Type::Throw;
|
|
m_value = throw_completion_or_value.throw_completion().value();
|
|
} else {
|
|
m_type = Type::Normal;
|
|
m_value = throw_completion_or_value.value();
|
|
}
|
|
}
|
|
|
|
// 6.2.3.1 Await, https://tc39.es/ecma262/#await
|
|
ThrowCompletionOr<Value> await(GlobalObject& global_object, Value value)
|
|
{
|
|
auto& vm = global_object.vm();
|
|
|
|
// 1. Let asyncContext be the running execution context.
|
|
// NOTE: This is not needed, as we don't suspend anything.
|
|
|
|
// 2. Let promise be ? PromiseResolve(%Promise%, value).
|
|
auto* promise_object = TRY(promise_resolve(global_object, *global_object.promise_constructor(), value));
|
|
|
|
Optional<bool> success;
|
|
Value result;
|
|
// 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called:
|
|
auto fulfilled_closure = [&success, &result](VM& vm, GlobalObject&) -> ThrowCompletionOr<Value> {
|
|
// a. Let prevContext be the running execution context.
|
|
// b. Suspend prevContext.
|
|
// FIXME: We don't have this concept yet.
|
|
|
|
// NOTE: Since we don't support context suspension, we exfiltrate the result to await()'s scope instead
|
|
success = true;
|
|
result = vm.argument(0);
|
|
|
|
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
|
|
// NOTE: This is not done, because we're not suspending anything (see above).
|
|
|
|
// d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it.
|
|
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
|
|
// FIXME: We don't have this concept yet.
|
|
|
|
// f. Return undefined.
|
|
return js_undefined();
|
|
};
|
|
|
|
// 4. Let onFulfilled be ! CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
|
|
auto on_fulfilled = NativeFunction::create(global_object, "", move(fulfilled_closure));
|
|
|
|
// 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called:
|
|
auto rejected_closure = [&success, &result](VM& vm, GlobalObject&) -> ThrowCompletionOr<Value> {
|
|
// a. Let prevContext be the running execution context.
|
|
// b. Suspend prevContext.
|
|
// FIXME: We don't have this concept yet.
|
|
|
|
// NOTE: Since we don't support context suspension, we exfiltrate the result to await()'s scope instead
|
|
success = false;
|
|
result = vm.argument(0);
|
|
|
|
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
|
|
// NOTE: This is not done, because we're not suspending anything (see above).
|
|
|
|
// d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it.
|
|
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
|
|
// FIXME: We don't have this concept yet.
|
|
|
|
// f. Return undefined.
|
|
return js_undefined();
|
|
};
|
|
|
|
// 6. Let onRejected be ! CreateBuiltinFunction(rejectedClosure, 1, "", « »).
|
|
auto on_rejected = NativeFunction::create(global_object, "", move(rejected_closure));
|
|
|
|
// 7. Perform ! PerformPromiseThen(promise, onFulfilled, onRejected).
|
|
auto* promise = verify_cast<Promise>(promise_object);
|
|
promise->perform_then(on_fulfilled, on_rejected, {});
|
|
|
|
// FIXME: Since we don't support context suspension, we attempt to "wait" for the promise to resolve
|
|
// by letting the event loop spin until our promise is no longer pending, and then synchronously
|
|
// running all queued promise jobs.
|
|
// Note: This is not used by LibJS itself, and is performed for the embedder (i.e. LibWeb).
|
|
if (Core::EventLoop::has_been_instantiated())
|
|
Core::EventLoop::current().spin_until([&] { return promise->state() != Promise::State::Pending; });
|
|
|
|
// 8. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
|
|
// NOTE: Since we don't push any EC, this step is not performed.
|
|
|
|
// 9. Set the code evaluation state of asyncContext such that when evaluation is resumed with a Completion completion, the following steps of the algorithm that invoked Await will be performed, with completion available.
|
|
// 10. Return.
|
|
// 11. NOTE: This returns to the evaluation of the operation that had most previously resumed evaluation of asyncContext.
|
|
|
|
vm.run_queued_promise_jobs();
|
|
|
|
// Make sure that the promise _actually_ resolved.
|
|
// Note that this is checked down the chain (result.is_empty()) anyway, but let's make the source of the issue more clear.
|
|
VERIFY(success.has_value());
|
|
|
|
if (success.value())
|
|
return result;
|
|
return throw_completion(result);
|
|
}
|
|
|
|
}
|