diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 9e8d6514327..cc6e1660fba 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -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', diff --git a/components/script/dom/promise.rs b/components/script/dom/promise.rs index 5caa6797c5b..a9c533dc772 100644 --- a/components/script/dom/promise.rs +++ b/components/script/dom/promise.rs @@ -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 { 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> { 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> { + 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)] diff --git a/components/script/dom/webidls/Promise.webidl b/components/script/dom/webidls/Promise.webidl index d437d244d58..5897ff422f9 100644 --- a/components/script/dom/webidls/Promise.webidl +++ b/components/script/dom/webidls/Promise.webidl @@ -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 { }; diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 298323929c1..cf0116edef0 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -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 for Sender { } } +#[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, content_process_shutdown_chan: IpcSender<()>, + + flushing_job_queue: DOMRefCell>, + promise_job_queue: DOMRefCell>, + pending_promise_job_runnable: Cell, +} + +#[derive(JSTraceable)] +struct EnqueuedPromiseCallback { + callback: Rc, + 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, script_thread: &ScriptThread) { + script_thread.flush_promise_jobs(); + } } impl Drop for ScriptThread {