Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mmatyas authored and jdm committed Sep 22, 2016
1 parent a109177 commit fd778b4
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 20 deletions.
3 changes: 2 additions & 1 deletion components/script/dom/bindings/codegen/CodegenRust.py
Expand Up @@ -826,7 +826,7 @@ def wrapObjectTemplate(templateBody, nullValue, isDefinitelyObject, type,
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}
}
Expand Down Expand Up @@ -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',
Expand Down
94 changes: 79 additions & 15 deletions components/script/dom/promise.rs
Expand Up @@ -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());
}
Expand All @@ -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());
}

Expand All @@ -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)]
Expand Down
7 changes: 6 additions & 1 deletion components/script/dom/webidls/Promise.webidl
Expand Up @@ -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 {
};
70 changes: 67 additions & 3 deletions components/script/script_thread.rs
Expand Up @@ -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};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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),
}
}

Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit fd778b4

Please sign in to comment.