aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/promise.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom/promise.rs')
-rw-r--r--components/script/dom/promise.rs288
1 files changed, 288 insertions, 0 deletions
diff --git a/components/script/dom/promise.rs b/components/script/dom/promise.rs
new file mode 100644
index 00000000000..5fd1ef6f85e
--- /dev/null
+++ b/components/script/dom/promise.rs
@@ -0,0 +1,288 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+//! Native representation of JS Promise values.
+//!
+//! This implementation differs from the traditional Rust DOM object, because the reflector
+//! is provided by SpiderMonkey and has no knowledge of an associated native representation
+//! (ie. dom::Promise). This means that native instances use native reference counting (Rc)
+//! to ensure that no memory is leaked, which means that there can be multiple instances of
+//! native Promise values that refer to the same JS value yet are distinct native objects
+//! (ie. address equality for the native objects is meaningless).
+
+use dom::bindings::callback::CallbackContainer;
+use dom::bindings::codegen::Bindings::PromiseBinding::AnyCallback;
+use dom::bindings::conversions::root_from_object;
+use dom::bindings::error::{Error, Fallible};
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::MutHeapJSVal;
+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, AddRawValueRoot, RemoveRawValueRoot};
+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,
+ /// Since Promise values are natively reference counted without the knowledge of
+ /// the SpiderMonkey GC, an explicit root for the reflector is stored while any
+ /// native instance exists. This ensures that the reflector will never be GCed
+ /// while native code could still interact with its native representation.
+ #[ignore_heap_size_of = "SM handles JS values"]
+ permanent_js_root: MutHeapJSVal,
+}
+
+/// Private helper to enable adding new methods to Rc<Promise>.
+trait PromiseHelper {
+ #[allow(unsafe_code)]
+ unsafe fn initialize(&self, cx: *mut JSContext);
+}
+
+impl PromiseHelper for Rc<Promise> {
+ #[allow(unsafe_code)]
+ unsafe fn initialize(&self, cx: *mut JSContext) {
+ let obj = self.reflector().get_jsobject();
+ self.permanent_js_root.set(ObjectValue(&**obj));
+ assert!(AddRawValueRoot(cx,
+ self.permanent_js_root.get_unsafe(),
+ b"Promise::root\0" as *const _ as *const _));
+ }
+}
+
+impl Drop for Promise {
+ #[allow(unsafe_code)]
+ fn drop(&mut self) {
+ let cx = self.global().r().get_cx();
+ unsafe {
+ RemoveRawValueRoot(cx, self.permanent_js_root.get_unsafe());
+ }
+ }
+}
+
+impl Promise {
+ #[allow(unsafe_code)]
+ pub fn new(global: GlobalRef) -> Rc<Promise> {
+ let cx = global.get_cx();
+ rooted!(in(cx) let mut obj = ptr::null_mut());
+ unsafe {
+ Promise::create_js_promise(cx, HandleObject::null(), obj.handle_mut());
+ Promise::new_with_js_promise(obj.handle(), cx)
+ }
+ }
+
+ #[allow(unsafe_code, unrooted_must_root)]
+ pub fn duplicate(&self) -> Rc<Promise> {
+ let cx = self.global().r().get_cx();
+ unsafe {
+ Promise::new_with_js_promise(self.reflector().get_jsobject(), cx)
+ }
+ }
+
+ #[allow(unsafe_code, unrooted_must_root)]
+ unsafe fn new_with_js_promise(obj: HandleObject, cx: *mut JSContext) -> Rc<Promise> {
+ assert!(IsPromiseObject(obj));
+ let mut promise = Promise {
+ reflector: Reflector::new(),
+ permanent_js_root: MutHeapJSVal::new(),
+ };
+ promise.init_reflector(obj.get());
+ let promise = Rc::new(promise);
+ promise.initialize(cx);
+ promise
+ }
+
+ #[allow(unsafe_code)]
+ 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());
+ 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());
+ }
+
+ #[allow(unrooted_must_root, unsafe_code)]
+ pub fn Resolve(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 { CallOriginalPromiseResolve(cx, value) });
+ assert!(!p.handle().is_null());
+ unsafe {
+ Ok(Promise::new_with_js_promise(p.handle(), cx))
+ }
+ }
+
+ #[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());
+ unsafe {
+ Ok(Promise::new_with_js_promise(p.handle(), cx))
+ }
+ }
+
+ #[allow(unsafe_code)]
+ pub fn resolve_native<T>(&self, cx: *mut JSContext, val: &T) where T: ToJSValConvertible {
+ rooted!(in(cx) let mut v = UndefinedValue());
+ unsafe {
+ val.to_jsval(cx, v.handle_mut());
+ }
+ self.resolve(cx, v.handle());
+ }
+
+ #[allow(unrooted_must_root, unsafe_code)]
+ pub fn resolve(&self, cx: *mut JSContext, value: HandleValue) {
+ unsafe {
+ if !ResolvePromise(cx, self.promise_obj(), value) {
+ JS_ClearPendingException(cx);
+ }
+ }
+ }
+
+ #[allow(unsafe_code)]
+ pub fn reject_native<T>(&self, cx: *mut JSContext, val: &T) where T: ToJSValConvertible {
+ rooted!(in(cx) let mut v = UndefinedValue());
+ unsafe {
+ val.to_jsval(cx, v.handle_mut());
+ }
+ self.reject(cx, v.handle());
+ }
+
+ #[allow(unsafe_code)]
+ pub fn reject_error(&self, cx: *mut JSContext, error: Error) {
+ rooted!(in(cx) let mut v = UndefinedValue());
+ unsafe {
+ error.to_jsval(cx, self.global().r(), v.handle_mut());
+ }
+ self.reject(cx, v.handle());
+ }
+
+ #[allow(unrooted_must_root, unsafe_code)]
+ pub fn 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)]
+ pub fn append_native_handler(&self, handler: &PromiseNativeHandler) {
+ let global = self.global();
+ let cx = global.r().get_cx();
+ rooted!(in(cx) let resolve_func =
+ create_native_handler_function(cx,
+ handler.reflector().get_jsobject(),
+ NativeHandlerTask::Resolve));
+
+ rooted!(in(cx) let reject_func =
+ create_native_handler_function(cx,
+ handler.reflector().get_jsobject(),
+ NativeHandlerTask::Reject));
+
+ unsafe {
+ let ok = AddPromiseReactions(cx,
+ self.promise_obj(),
+ resolve_func.handle(),
+ reject_func.handle());
+ assert!(ok);
+ }
+ }
+}
+
+#[allow(unsafe_code)]
+unsafe extern fn do_nothing_promise_executor(_cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
+ let args = CallArgs::from_vp(vp, argc);
+ *args.rval() = UndefinedValue();
+ true
+}
+
+const SLOT_NATIVEHANDLER: usize = 0;
+const SLOT_NATIVEHANDLER_TASK: usize = 1;
+
+#[derive(PartialEq)]
+enum NativeHandlerTask {
+ Resolve = 0,
+ Reject = 1,
+}
+
+#[allow(unsafe_code)]
+unsafe extern fn native_handler_callback(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
+ let args = CallArgs::from_vp(vp, argc);
+ rooted!(in(cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER));
+ assert!(v.get().is_object());
+
+ let handler = root_from_object::<PromiseNativeHandler>(v.to_object())
+ .ok().expect("unexpected value for native handler in promise native handler callback");
+
+ rooted!(in(cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK));
+ match v.to_int32() {
+ v if v == NativeHandlerTask::Resolve as i32 => handler.resolved_callback(cx, args.get(0)),
+ v if v == NativeHandlerTask::Reject as i32 => handler.rejected_callback(cx, args.get(0)),
+ _ => panic!("unexpected native handler task value"),
+ };
+
+ true
+}
+
+#[allow(unsafe_code)]
+fn create_native_handler_function(cx: *mut JSContext,
+ holder: HandleObject,
+ task: NativeHandlerTask) -> *mut JSObject {
+ unsafe {
+ let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
+ assert!(!func.is_null());
+
+ rooted!(in(cx) let obj = JS_GetFunctionObject(func));
+ assert!(!obj.is_null());
+ SetFunctionNativeReserved(obj.get(),
+ SLOT_NATIVEHANDLER,
+ &ObjectValue(&**holder));
+ SetFunctionNativeReserved(obj.get(),
+ SLOT_NATIVEHANDLER_TASK,
+ &Int32Value(task as i32));
+ obj.get()
+ }
+}