mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 15:37:46 +00:00
LibJS: Setup host hooks and have promise jobs work out the realm
This allows the host of LibJS (notably LibWeb in this case) to override certain functions such as HostEnqueuePromiseJob, so it can do it's own thing in certain situations. Notably, LibWeb will override HostEnqueuePromiseJob to put promise jobs on the microtask queue. This also makes promise jobs use AK::Function instead of JS::NativeFunction. This removes the need to go through a JavaScript function and it more closely matches the spec's idea of "abstract closures"
This commit is contained in:
parent
5aacec65ab
commit
4c1c6ef91c
12 changed files with 202 additions and 172 deletions
|
@ -46,6 +46,27 @@ VM::VM(OwnPtr<CustomData> custom_data)
|
|||
m_single_ascii_character_strings[i] = m_heap.allocate_without_global_object<PrimitiveString>(String::formatted("{:c}", i));
|
||||
}
|
||||
|
||||
// Default hook implementations. These can be overridden by the host, for example, LibWeb overrides the default hooks to place promise jobs on the microtask queue.
|
||||
host_promise_rejection_tracker = [this](Promise& promise, Promise::RejectionOperation operation) {
|
||||
promise_rejection_tracker(promise, operation);
|
||||
};
|
||||
|
||||
host_call_job_callback = [](GlobalObject& global_object, JobCallback& job_callback, Value this_value, MarkedValueList arguments) {
|
||||
return call_job_callback(global_object, job_callback, this_value, move(arguments));
|
||||
};
|
||||
|
||||
host_enqueue_finalization_registry_cleanup_job = [this](FinalizationRegistry& finalization_registry) {
|
||||
enqueue_finalization_registry_cleanup_job(finalization_registry);
|
||||
};
|
||||
|
||||
host_enqueue_promise_job = [this](Function<ThrowCompletionOr<Value>()> job, Realm* realm) {
|
||||
enqueue_promise_job(move(job), realm);
|
||||
};
|
||||
|
||||
host_make_job_callback = [](FunctionObject& function_object) {
|
||||
return make_job_callback(function_object);
|
||||
};
|
||||
|
||||
host_resolve_imported_module = [&](ScriptOrModule referencing_script_or_module, ModuleRequest const& specifier) {
|
||||
return resolve_imported_module(move(referencing_script_or_module), specifier);
|
||||
};
|
||||
|
@ -177,9 +198,6 @@ void VM::gather_roots(HashTable<Cell*>& roots)
|
|||
for (auto& symbol : m_global_symbol_map)
|
||||
roots.set(symbol.value);
|
||||
|
||||
for (auto* job : m_promise_jobs)
|
||||
roots.set(job);
|
||||
|
||||
for (auto* finalization_registry : m_finalization_registry_cleanup_jobs)
|
||||
roots.set(finalization_registry);
|
||||
}
|
||||
|
@ -604,36 +622,22 @@ void VM::run_queued_promise_jobs()
|
|||
dbgln_if(PROMISE_DEBUG, "Running queued promise jobs");
|
||||
|
||||
while (!m_promise_jobs.is_empty()) {
|
||||
auto* job = m_promise_jobs.take_first();
|
||||
dbgln_if(PROMISE_DEBUG, "Calling promise job function @ {}", job);
|
||||
auto job = m_promise_jobs.take_first();
|
||||
dbgln_if(PROMISE_DEBUG, "Calling promise job function");
|
||||
|
||||
// NOTE: If the execution context stack is empty, we make and push a temporary context.
|
||||
ExecutionContext execution_context(heap());
|
||||
bool pushed_execution_context = false;
|
||||
if (m_execution_context_stack.is_empty()) {
|
||||
static FlyString promise_execution_context_name = "(promise execution context)";
|
||||
execution_context.function_name = promise_execution_context_name;
|
||||
// FIXME: Propagate potential failure
|
||||
MUST(push_execution_context(execution_context, job->global_object()));
|
||||
pushed_execution_context = true;
|
||||
}
|
||||
|
||||
[[maybe_unused]] auto result = call(job->global_object(), *job, js_undefined());
|
||||
|
||||
// This doesn't match the spec, it actually defines that Job Abstract Closures must return
|
||||
// a normal completion. In reality that's not the case however, and all major engines clear
|
||||
// exceptions when running Promise jobs. See the commit where these two lines were initially
|
||||
// added for a much more detailed explanation. (Hash: a53542e0a3fbd7bf22b685d87f0473e489e1cf42)
|
||||
|
||||
if (pushed_execution_context)
|
||||
pop_execution_context();
|
||||
[[maybe_unused]] auto result = job();
|
||||
}
|
||||
}
|
||||
|
||||
// 9.5.4 HostEnqueuePromiseJob ( job, realm ), https://tc39.es/ecma262/#sec-hostenqueuepromisejob
|
||||
void VM::enqueue_promise_job(NativeFunction& job)
|
||||
void VM::enqueue_promise_job(Function<ThrowCompletionOr<Value>()> job, Realm*)
|
||||
{
|
||||
m_promise_jobs.append(&job);
|
||||
// An implementation of HostEnqueuePromiseJob must conform to the requirements in 9.5 as well as the following:
|
||||
// - FIXME: If realm is not null, each time job is invoked the implementation must perform implementation-defined steps such that execution is prepared to evaluate ECMAScript code at the time of job's invocation.
|
||||
// - FIXME: Let scriptOrModule be GetActiveScriptOrModule() at the time HostEnqueuePromiseJob is invoked. If realm is not null, each time job is invoked the implementation must perform implementation-defined steps
|
||||
// such that scriptOrModule is the active script or module at the time of job's invocation.
|
||||
// - Jobs must run in the same order as the HostEnqueuePromiseJob invocations that scheduled them.
|
||||
m_promise_jobs.append(move(job));
|
||||
}
|
||||
|
||||
void VM::run_queued_finalization_registry_cleanup_jobs()
|
||||
|
@ -652,7 +656,7 @@ void VM::enqueue_finalization_registry_cleanup_job(FinalizationRegistry& registr
|
|||
}
|
||||
|
||||
// 27.2.1.9 HostPromiseRejectionTracker ( promise, operation ), https://tc39.es/ecma262/#sec-host-promise-rejection-tracker
|
||||
void VM::promise_rejection_tracker(const Promise& promise, Promise::RejectionOperation operation) const
|
||||
void VM::promise_rejection_tracker(Promise& promise, Promise::RejectionOperation operation) const
|
||||
{
|
||||
switch (operation) {
|
||||
case Promise::RejectionOperation::Reject:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue