Enqueue promise jobs from SpiderMonkey callbacks, and execute them in batches. Implement native Promise APIs.

Add SpiderMonkey hooks for enqueuing promise jobs. Start porting various native Promise APIs.
This commit is contained in:
Mátyás Mustoha 2016-07-20 12:47:55 +02:00 committed by Josh Matthews
parent a1091772ec
commit fd778b4240
4 changed files with 154 additions and 20 deletions

View file

@ -826,7 +826,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
rooted!(in(cx) let globalObj = CurrentGlobalOrNull(cx));
let promiseGlobal = global_root_from_object_maybe_wrapped(globalObj.handle().get());
let mut valueToResolve = RootedValue::new(cx, $${val}.get());
rooted!(in(cx) let mut valueToResolve = $${val}.get());
if !JS_WrapValue(cx, valueToResolve.handle_mut()) {
$*{exceptionCode}
}
@ -5482,6 +5482,7 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries
'js::jsapi::JS_SetProperty',
'js::jsapi::JS_SetReservedSlot',
'js::jsapi::JS_SplicePrototype',
'js::jsapi::JS_WrapValue',
'js::jsapi::MutableHandle',
'js::jsapi::MutableHandleObject',
'js::jsapi::MutableHandleValue',

View file

@ -2,26 +2,33 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use dom::bindings::callback::CallbackContainer;
use dom::bindings::codegen::Bindings::PromiseBinding::AnyCallback;
use dom::bindings::error::Fallible;
use dom::bindings::global::GlobalRef;
use dom::bindings::reflector::{Reflectable, Reflector};
use js::jsapi::{JSAutoCompartment, RootedObject, CallArgs, JS_GetFunctionObject, JS_NewFunction};
use js::jsapi::{JSContext, HandleValue, HandleObject, IsPromiseObject, CallOriginalPromiseResolve};
use js::jsapi::{MutableHandleObject, NewPromiseObject};
use js::jsval::{JSVal, UndefinedValue};
use dom::bindings::reflector::{Reflectable, MutReflectable, Reflector};
use dom::promisenativehandler::PromiseNativeHandler;
use js::conversions::ToJSValConvertible;
use js::jsapi::{CallOriginalPromiseResolve, CallOriginalPromiseReject, CallOriginalPromiseThen};
use js::jsapi::{JSAutoCompartment, CallArgs, JS_GetFunctionObject, JS_NewFunction};
use js::jsapi::{JSContext, HandleValue, HandleObject, IsPromiseObject, GetFunctionNativeReserved};
use js::jsapi::{JS_ClearPendingException, JSObject};
use js::jsapi::{MutableHandleObject, NewPromiseObject, ResolvePromise, RejectPromise};
use js::jsapi::{SetFunctionNativeReserved, NewFunctionWithReserved, AddPromiseReactions};
use js::jsval::{JSVal, UndefinedValue, ObjectValue, Int32Value};
use std::ptr;
use std::rc::Rc;
#[dom_struct]
pub struct Promise {
reflector: Reflector
reflector: Reflector,
}
impl Promise {
#[allow(unsafe_code)]
pub fn new(global: GlobalRef) -> Rc<Promise> {
let cx = global.get_cx();
let mut obj = RootedObject::new(cx, ptr::null_mut());
rooted!(in(cx) let mut obj = ptr::null_mut());
unsafe {
Promise::create_js_promise(cx, HandleObject::null(), obj.handle_mut());
}
@ -41,12 +48,13 @@ impl Promise {
}
#[allow(unsafe_code)]
unsafe fn create_js_promise(cx: *mut JSContext, proto: HandleObject, mut obj: MutableHandleObject) {
let do_nothing_func = JS_NewFunction(cx, Some(do_nothing_promise_executor), 2, 0, ptr::null());
unsafe fn create_js_promise(cx: *mut JSContext, proto: HandleObject, obj: MutableHandleObject) {
let do_nothing_func = JS_NewFunction(cx, Some(do_nothing_promise_executor), /* nargs = */ 2,
/* flags = */ 0, ptr::null());
assert!(!do_nothing_func.is_null());
let do_nothing_obj = RootedObject::new(cx, JS_GetFunctionObject(do_nothing_func));
assert!(!do_nothing_obj.handle().is_null());
*obj = NewPromiseObject(cx, do_nothing_obj.handle(), proto);
rooted!(in(cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func));
assert!(!do_nothing_obj.is_null());
obj.set(NewPromiseObject(cx, do_nothing_obj.handle(), proto));
assert!(!obj.is_null());
}
@ -55,12 +63,68 @@ impl Promise {
cx: *mut JSContext,
value: HandleValue) -> Fallible<Rc<Promise>> {
let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get());
let p = unsafe {
RootedObject::new(cx, CallOriginalPromiseResolve(cx, value))
};
rooted!(in(cx) let p = unsafe { CallOriginalPromiseResolve(cx, value) });
assert!(!p.handle().is_null());
Ok(Promise::new_with_js_promise(p.handle()))
}
#[allow(unrooted_must_root, unsafe_code)]
pub fn Reject(global: GlobalRef,
cx: *mut JSContext,
value: HandleValue) -> Fallible<Rc<Promise>> {
let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get());
rooted!(in(cx) let p = unsafe { CallOriginalPromiseReject(cx, value) });
assert!(!p.handle().is_null());
Ok(Promise::new_with_js_promise(p.handle()))
}
#[allow(unrooted_must_root, unsafe_code)]
pub fn maybe_resolve(&self,
cx: *mut JSContext,
value: HandleValue) {
unsafe {
if !ResolvePromise(cx, self.promise_obj(), value) {
JS_ClearPendingException(cx);
}
}
}
#[allow(unrooted_must_root, unsafe_code)]
pub fn maybe_reject(&self,
cx: *mut JSContext,
value: HandleValue) {
unsafe {
if !RejectPromise(cx, self.promise_obj(), value) {
JS_ClearPendingException(cx);
}
}
}
#[allow(unrooted_must_root, unsafe_code)]
pub fn then(&self,
cx: *mut JSContext,
_callee: HandleObject,
cb_resolve: AnyCallback,
cb_reject: AnyCallback,
result: MutableHandleObject) {
let promise = self.promise_obj();
rooted!(in(cx) let resolve = cb_resolve.callback());
rooted!(in(cx) let reject = cb_reject.callback());
unsafe {
rooted!(in(cx) let res =
CallOriginalPromiseThen(cx, promise, resolve.handle(), reject.handle()));
result.set(*res);
}
}
#[allow(unsafe_code)]
fn promise_obj(&self) -> HandleObject {
let obj = self.reflector().get_jsobject();
unsafe {
assert!(IsPromiseObject(obj));
}
obj
}
}
#[allow(unsafe_code)]

View file

@ -5,7 +5,12 @@
// This interface is entirely internal to Servo, and should not be accessible to
// web pages.
[NoInterfaceObject]
callback PromiseJobCallback = void();
[TreatNonCallableAsNull]
callback AnyCallback = any (any value);
[NoInterfaceObject, Exposed=(Window,Worker)]
// Need to escape "Promise" so it's treated as an identifier.
interface _Promise {
};

View file

@ -22,12 +22,14 @@ use devtools_traits::{DevtoolScriptControlMsg, DevtoolsPageInfo};
use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
use devtools_traits::CSSError;
use document_loader::DocumentLoader;
use dom::bindings::callback::ExceptionHandling;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState};
use dom::bindings::codegen::Bindings::LocationBinding::LocationMethods;
use dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::conversions::{ConversionResult, FromJSValConvertible, StringificationBehavior};
use dom::bindings::global::GlobalRef;
use dom::bindings::global::{GlobalRef, global_root_from_object};
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, MutNullableHeap, Root, RootCollection};
use dom::bindings::js::{RootCollectionPtr, RootedReference};
@ -59,8 +61,8 @@ use hyper_serde::Serde;
use ipc_channel::ipc::{self, IpcSender};
use ipc_channel::router::ROUTER;
use js::glue::GetWindowProxyClass;
use js::jsapi::{JSAutoCompartment, JSContext, JS_SetWrapObjectCallbacks};
use js::jsapi::{JSTracer, SetWindowProxyClass};
use js::jsapi::{JSAutoCompartment, JSContext, JS_SetWrapObjectCallbacks, HandleObject};
use js::jsapi::{JSTracer, SetWindowProxyClass, SetEnqueuePromiseJobCallback};
use js::jsval::UndefinedValue;
use js::rust::Runtime;
use mem::heap_size_of_self_and_children;
@ -91,6 +93,7 @@ use std::borrow::ToOwned;
use std::cell::Cell;
use std::collections::{HashMap, HashSet};
use std::option::Option;
use std::os::raw::c_void;
use std::ptr;
use std::rc::Rc;
use std::result::Result;
@ -315,6 +318,18 @@ impl OpaqueSender<CommonScriptMsg> for Sender<MainThreadScriptMsg> {
}
}
#[allow(unsafe_code)]
unsafe extern "C" fn enqueue_job(_cx: *mut JSContext,
job: HandleObject,
_allocation_site: HandleObject,
_data: *mut c_void) -> bool {
SCRIPT_THREAD_ROOT.with(|root| {
let script_thread = &*root.get().unwrap();
script_thread.enqueue_promise_job(job);
});
true
}
/// Information for an entire page. Pages are top-level browsing contexts and can contain multiple
/// frames.
#[derive(JSTraceable)]
@ -394,6 +409,16 @@ pub struct ScriptThread {
timer_event_port: Receiver<TimerEvent>,
content_process_shutdown_chan: IpcSender<()>,
flushing_job_queue: DOMRefCell<Vec<EnqueuedPromiseCallback>>,
promise_job_queue: DOMRefCell<Vec<EnqueuedPromiseCallback>>,
pending_promise_job_runnable: Cell<bool>,
}
#[derive(JSTraceable)]
struct EnqueuedPromiseCallback {
callback: Rc<PromiseJobCallback>,
pipeline: PipelineId,
}
/// In the event of thread panic, all data on the stack runs its destructor. However, there
@ -541,6 +566,7 @@ impl ScriptThread {
JS_SetWrapObjectCallbacks(runtime.rt(),
&WRAP_CALLBACKS);
SetWindowProxyClass(runtime.rt(), GetWindowProxyClass());
SetEnqueuePromiseJobCallback(runtime.rt(), Some(enqueue_job), ptr::null_mut());
}
// Ask the router to proxy IPC messages from the devtools to us.
@ -599,6 +625,10 @@ impl ScriptThread {
timer_event_port: timer_event_port,
content_process_shutdown_chan: state.content_process_shutdown_chan,
promise_job_queue: DOMRefCell::new(vec![]),
flushing_job_queue: DOMRefCell::new(vec![]),
pending_promise_job_runnable: Cell::new(false),
}
}
@ -2174,6 +2204,40 @@ impl ScriptThread {
location.Reload();
}
}
fn enqueue_promise_job(&self, job: HandleObject) {
let global = unsafe { global_root_from_object(job.get()) };
let pipeline = global.r().pipeline();
self.promise_job_queue.borrow_mut().push(EnqueuedPromiseCallback {
callback: PromiseJobCallback::new(job.get()),
pipeline: pipeline,
});
if !self.pending_promise_job_runnable.get() {
self.pending_promise_job_runnable.set(true);
let _ = self.dom_manipulation_task_source.queue(box FlushPromiseJobs, global.r());
}
}
fn flush_promise_jobs(&self) {
self.pending_promise_job_runnable.set(false);
{
let mut pending_queue = self.promise_job_queue.borrow_mut();
*self.flushing_job_queue.borrow_mut() = pending_queue.drain(..).collect();
}
for job in &*self.flushing_job_queue.borrow() {
if let Some(context) = self.find_child_context(job.pipeline) {
let _ = job.callback.Call_(&*context.active_window(), ExceptionHandling::Report);
}
}
self.flushing_job_queue.borrow_mut().clear();
}
}
struct FlushPromiseJobs;
impl Runnable for FlushPromiseJobs {
fn main_thread_handler(self: Box<FlushPromiseJobs>, script_thread: &ScriptThread) {
script_thread.flush_promise_jobs();
}
}
impl Drop for ScriptThread {