forked from mirrors/gecko-dev
		
	Fixes https://github.com/servo/servo/issues/8473. Source-Repo: https://github.com/servo/servo Source-Revision: b192ae9db7082346a4a6a985c5557d4cea75d50e
		
			
				
	
	
		
			296 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			296 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
/* 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::js::MutHeapJSVal;
 | 
						|
use dom::bindings::reflector::{DomObject, MutDomObject, Reflector};
 | 
						|
use dom::globalscope::GlobalScope;
 | 
						|
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, PromiseState};
 | 
						|
use js::jsapi::{MutableHandleObject, NewPromiseObject, ResolvePromise, RejectPromise, GetPromiseState};
 | 
						|
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().get_cx();
 | 
						|
        unsafe {
 | 
						|
            RemoveRawValueRoot(cx, self.permanent_js_root.get_unsafe());
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
impl Promise {
 | 
						|
    #[allow(unsafe_code)]
 | 
						|
    pub fn new(global: &GlobalScope) -> 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().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: &GlobalScope,
 | 
						|
                   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: &GlobalScope,
 | 
						|
                  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(), 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)]
 | 
						|
    pub fn is_settled(&self) -> bool {
 | 
						|
        let state = unsafe { GetPromiseState(self.promise_obj()) };
 | 
						|
        match state {
 | 
						|
            PromiseState::Rejected | PromiseState::Fulfilled => true,
 | 
						|
            _ => false
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    #[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 cx = self.global().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()
 | 
						|
    }
 | 
						|
}
 |