Follows from #2528, and should complement #2411 to implement the module import hooks.
~~Similarly to the Intl/ICU4X PR (#2478), this has a lot of trivial changes caused by the new lifetimes. I thought about passing the queue and the hooks by value, but it was very painful having to wrap everything with `Rc` in order to be accessible by the host.
In contrast, `&dyn` can be easily provided by the host and has the advantage of not requiring additional allocations, with the downside of adding two more lifetimes to our `Context`, but I think it's worth.~~ I was able to unify all lifetimes into the shortest one of the three, making our API just like before!
Changes:
- Added a new `HostHooks` trait and a `&dyn HostHooks` field to `Context`. This allows hosts to implement the trait for their custom type, then pass it to the context.
- Added a new `JobQueue` trait and a `&dyn JobQueue` field to our `Context`, allowing custom event loops and other fun things.
- Added two simple implementations of `JobQueue`: `IdleJobQueue` which does nothing and `SimpleJobQueue` which runs all jobs until all successfully complete or until any of them throws an error.
- Modified `boa_cli` to run all jobs until the queue is empty, even if a job returns `Err`. This also prints all errors to the user.
// 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures promiseCapability and performs the following steps when called:
// 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »).
letexecutor=FunctionObjectBuilder::new(
context,
NativeFunction::from_copy_closure_with_captures(
|_this,args: &[JsValue],captures,_|{
letmutpromise_capability=captures.borrow_mut();
// a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception.
if!promise_capability.resolve.is_undefined(){
returnErr(JsNativeError::typ()
.with_message("promiseCapability.[[Resolve]] is not undefined")
.into());
}
// b. If promiseCapability.[[Reject]] is not undefined, throw a TypeError exception.
if!promise_capability.reject.is_undefined(){
returnErr(JsNativeError::typ()
.with_message("promiseCapability.[[Reject]] is not undefined")
.into());
}
letresolve=args.get_or_undefined(0);
letreject=args.get_or_undefined(1);
// c. Set promiseCapability.[[Resolve]] to resolve.
promise_capability.resolve=resolve.clone();
// d. Set promiseCapability.[[Reject]] to reject.
promise_capability.reject=reject.clone();
// e. Return undefined.
Ok(JsValue::Undefined)
},
promise_capability.clone(),
),
)
.name("")
.length(2)
.build()
.into();
// 6. Let promise be ? Construct(C, « executor »).
// 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures promiseCapability and performs the following steps when called:
// 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »).
letexecutor=FunctionObjectBuilder::new(
context,
NativeFunction::from_copy_closure_with_captures(
|_this,args: &[JsValue],captures,_|{
letmutpromise_capability=captures.borrow_mut();
// a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception.
if!promise_capability.resolve.is_undefined(){
returnErr(JsNativeError::typ()
.with_message("promiseCapability.[[Resolve]] is not undefined")
.into());
}
// b. If promiseCapability.[[Reject]] is not undefined, throw a TypeError exception.
if!promise_capability.reject.is_undefined(){
returnErr(JsNativeError::typ()
.with_message("promiseCapability.[[Reject]] is not undefined")
.into());
}
letresolve=args.get_or_undefined(0);
letreject=args.get_or_undefined(1);
// c. Set promiseCapability.[[Resolve]] to resolve.
promise_capability.resolve=resolve.clone();
// d. Set promiseCapability.[[Reject]] to reject.
promise_capability.reject=reject.clone();
// e. Return undefined.
Ok(JsValue::Undefined)
},
promise_capability.clone(),
),
)
.name("")
.length(2)
.build()
.into();
// 6. Let promise be ? Construct(C, « executor »).