Support an equivalent of Trusted<T> for Rc<Promise> objects named TrustedPromise.

This commit is contained in:
Josh Matthews 2016-09-10 16:50:52 -04:00
parent 27d44c8d10
commit 498ccd41e8
7 changed files with 145 additions and 10 deletions

View file

@ -26,6 +26,7 @@ use core::nonzero::NonZero;
use dom::bindings::js::Root; use dom::bindings::js::Root;
use dom::bindings::reflector::{Reflectable, Reflector}; use dom::bindings::reflector::{Reflectable, Reflector};
use dom::bindings::trace::trace_reflector; use dom::bindings::trace::trace_reflector;
use dom::promise::Promise;
use js::jsapi::JSTracer; use js::jsapi::JSTracer;
use libc; use libc;
use std::cell::RefCell; use std::cell::RefCell;
@ -34,6 +35,7 @@ use std::collections::hash_map::HashMap;
use std::hash::Hash; use std::hash::Hash;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::os; use std::os;
use std::rc::Rc;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
@ -58,6 +60,63 @@ impl TrustedReference {
} }
} }
/// A safe wrapper around a DOM Promise object that can be shared among threads for use
/// in asynchronous operations. The underlying DOM object is guaranteed to live at least
/// as long as the last outstanding `TrustedPromise` instance. These values cannot be cloned,
/// only created from existing Rc<Promise> values.
pub struct TrustedPromise {
dom_object: *const Promise,
owner_thread: *const libc::c_void,
}
unsafe impl Send for TrustedPromise {}
impl TrustedPromise {
/// Create a new `TrustedPromise` instance from an existing DOM object. The object will
/// be prevented from being GCed for the duration of the resulting `TrustedPromise` object's
/// lifetime.
#[allow(unrooted_must_root)]
pub fn new(promise: Rc<Promise>) -> TrustedPromise {
LIVE_REFERENCES.with(|ref r| {
let r = r.borrow();
let live_references = r.as_ref().unwrap();
let ptr = &*promise as *const Promise;
live_references.addref_promise(promise);
TrustedPromise {
dom_object: ptr,
owner_thread: (&*live_references) as *const _ as *const libc::c_void,
}
})
}
/// Obtain a usable DOM Promise from a pinned `TrustedPromise` value. Fails if used on
/// a different thread than the original value from which this `TrustedPromise` was
/// obtained.
#[allow(unrooted_must_root)]
pub fn root(self) -> Rc<Promise> {
LIVE_REFERENCES.with(|ref r| {
let r = r.borrow();
let live_references = r.as_ref().unwrap();
assert!(self.owner_thread == (&*live_references) as *const _ as *const libc::c_void);
// Borrow-check error requires the redundant `let promise = ...; promise` here.
let promise = match live_references.promise_table.borrow_mut().entry(self.dom_object) {
Occupied(mut entry) => {
let promise = {
let promises = entry.get_mut();
promises.pop().expect("rooted promise list unexpectedly empty")
};
if entry.get().is_empty() {
entry.remove();
}
promise
}
Vacant(_) => unreachable!(),
};
promise
})
}
}
/// A safe wrapper around a raw pointer to a DOM object that can be /// A safe wrapper around a raw pointer to a DOM object that can be
/// shared among threads for use in asynchronous operations. The underlying /// shared among threads for use in asynchronous operations. The underlying
/// DOM object is guaranteed to live at least as long as the last outstanding /// DOM object is guaranteed to live at least as long as the last outstanding
@ -117,9 +176,11 @@ impl<T: Reflectable> Clone for Trusted<T> {
/// The set of live, pinned DOM objects that are currently prevented /// The set of live, pinned DOM objects that are currently prevented
/// from being garbage collected due to outstanding references. /// from being garbage collected due to outstanding references.
#[allow(unrooted_must_root)]
pub struct LiveDOMReferences { pub struct LiveDOMReferences {
// keyed on pointer to Rust DOM object // keyed on pointer to Rust DOM object
table: RefCell<HashMap<*const libc::c_void, Weak<TrustedReference>>>, reflectable_table: RefCell<HashMap<*const libc::c_void, Weak<TrustedReference>>>,
promise_table: RefCell<HashMap<*const Promise, Vec<Rc<Promise>>>>,
} }
impl LiveDOMReferences { impl LiveDOMReferences {
@ -127,13 +188,20 @@ impl LiveDOMReferences {
pub fn initialize() { pub fn initialize() {
LIVE_REFERENCES.with(|ref r| { LIVE_REFERENCES.with(|ref r| {
*r.borrow_mut() = Some(LiveDOMReferences { *r.borrow_mut() = Some(LiveDOMReferences {
table: RefCell::new(HashMap::new()), reflectable_table: RefCell::new(HashMap::new()),
promise_table: RefCell::new(HashMap::new()),
}) })
}); });
} }
#[allow(unrooted_must_root)]
fn addref_promise(&self, promise: Rc<Promise>) {
let mut table = self.promise_table.borrow_mut();
table.entry(&*promise).or_insert(vec![]).push(promise)
}
fn addref<T: Reflectable>(&self, ptr: *const T) -> Arc<TrustedReference> { fn addref<T: Reflectable>(&self, ptr: *const T) -> Arc<TrustedReference> {
let mut table = self.table.borrow_mut(); let mut table = self.reflectable_table.borrow_mut();
let capacity = table.capacity(); let capacity = table.capacity();
let len = table.len(); let len = table.len();
if (0 < capacity) && (capacity <= len) { if (0 < capacity) && (capacity <= len) {
@ -173,17 +241,27 @@ fn remove_nulls<K: Eq + Hash + Clone, V> (table: &mut HashMap<K, Weak<V>>) {
} }
/// A JSTraceDataOp for tracing reflectors held in LIVE_REFERENCES /// A JSTraceDataOp for tracing reflectors held in LIVE_REFERENCES
#[allow(unrooted_must_root)]
pub unsafe extern "C" fn trace_refcounted_objects(tracer: *mut JSTracer, pub unsafe extern "C" fn trace_refcounted_objects(tracer: *mut JSTracer,
_data: *mut os::raw::c_void) { _data: *mut os::raw::c_void) {
info!("tracing live refcounted references"); info!("tracing live refcounted references");
LIVE_REFERENCES.with(|ref r| { LIVE_REFERENCES.with(|ref r| {
let r = r.borrow(); let r = r.borrow();
let live_references = r.as_ref().unwrap(); let live_references = r.as_ref().unwrap();
let mut table = live_references.table.borrow_mut(); {
remove_nulls(&mut table); let mut table = live_references.reflectable_table.borrow_mut();
for obj in table.keys() { remove_nulls(&mut table);
let reflectable = &*(*obj as *const Reflector); for obj in table.keys() {
trace_reflector(tracer, "refcounted", reflectable); let reflectable = &*(*obj as *const Reflector);
trace_reflector(tracer, "refcounted", reflectable);
}
}
{
let table = live_references.promise_table.borrow_mut();
for promise in table.keys() {
trace_reflector(tracer, "refcounted", (**promise).reflector());
}
} }
}); });
} }

View file

@ -36,7 +36,7 @@ use devtools_traits::CSSError;
use devtools_traits::WorkerId; use devtools_traits::WorkerId;
use dom::abstractworker::SharedRt; use dom::abstractworker::SharedRt;
use dom::bindings::js::{JS, Root}; use dom::bindings::js::{JS, Root};
use dom::bindings::refcounted::Trusted; use dom::bindings::refcounted::{Trusted, TrustedPromise};
use dom::bindings::reflector::{Reflectable, Reflector}; use dom::bindings::reflector::{Reflectable, Reflector};
use dom::bindings::str::{DOMString, USVString}; use dom::bindings::str::{DOMString, USVString};
use dom::bindings::utils::WindowProxyHandler; use dom::bindings::utils::WindowProxyHandler;
@ -303,6 +303,7 @@ no_jsmanaged_fields!(Metadata);
no_jsmanaged_fields!(NetworkError); no_jsmanaged_fields!(NetworkError);
no_jsmanaged_fields!(Atom, Namespace, QualName); no_jsmanaged_fields!(Atom, Namespace, QualName);
no_jsmanaged_fields!(Trusted<T: Reflectable>); no_jsmanaged_fields!(Trusted<T: Reflectable>);
no_jsmanaged_fields!(TrustedPromise);
no_jsmanaged_fields!(PropertyDeclarationBlock); no_jsmanaged_fields!(PropertyDeclarationBlock);
no_jsmanaged_fields!(HashSet<T>); no_jsmanaged_fields!(HashSet<T>);
// These three are interdependent, if you plan to put jsmanaged data // These three are interdependent, if you plan to put jsmanaged data

View file

@ -79,6 +79,14 @@ impl Promise {
} }
} }
#[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)] #[allow(unsafe_code, unrooted_must_root)]
unsafe fn new_with_js_promise(obj: HandleObject, cx: *mut JSContext) -> Rc<Promise> { unsafe fn new_with_js_promise(obj: HandleObject, cx: *mut JSContext) -> Rc<Promise> {
assert!(IsPromiseObject(obj)); assert!(IsPromiseObject(obj));

View file

@ -25,6 +25,7 @@ use dom::bindings::global::{GlobalRef, global_root_from_context};
use dom::bindings::js::Root; use dom::bindings::js::Root;
use dom::bindings::mozmap::MozMap; use dom::bindings::mozmap::MozMap;
use dom::bindings::num::Finite; use dom::bindings::num::Finite;
use dom::bindings::refcounted::TrustedPromise;
use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object}; use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object};
use dom::bindings::str::{ByteString, DOMString, USVString}; use dom::bindings::str::{ByteString, DOMString, USVString};
use dom::bindings::weakref::MutableWeakRef; use dom::bindings::weakref::MutableWeakRef;
@ -32,12 +33,14 @@ use dom::blob::{Blob, BlobImpl};
use dom::promise::Promise; use dom::promise::Promise;
use dom::promisenativehandler::{PromiseNativeHandler, Callback}; use dom::promisenativehandler::{PromiseNativeHandler, Callback};
use dom::url::URL; use dom::url::URL;
use js::jsapi::{HandleObject, HandleValue, JSContext, JSObject}; use js::jsapi::{HandleObject, HandleValue, JSContext, JSObject, JSAutoCompartment};
use js::jsapi::{JS_NewPlainObject, JS_NewUint8ClampedArray}; use js::jsapi::{JS_NewPlainObject, JS_NewUint8ClampedArray};
use js::jsval::{JSVal, NullValue}; use js::jsval::{JSVal, NullValue};
use script_traits::MsDuration;
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::ptr; use std::ptr;
use std::rc::Rc; use std::rc::Rc;
use timers::OneshotTimerCallback;
use util::prefs::PREFS; use util::prefs::PREFS;
#[dom_struct] #[dom_struct]
@ -672,6 +675,17 @@ impl TestBindingMethods for TestBinding {
p.maybe_reject_error(self.global().r().get_cx(), Error::Type(s.0)); p.maybe_reject_error(self.global().r().get_cx(), Error::Type(s.0));
} }
#[allow(unrooted_must_root)]
fn ResolvePromiseDelayed(&self, p: &Promise, value: DOMString, delay: u64) {
let promise = p.duplicate();
let cb = TestBindingCallback {
promise: TrustedPromise::new(promise),
value: value,
};
let _ = self.global().r().schedule_callback(OneshotTimerCallback::TestBindingCallback(cb),
MsDuration::new(delay));
}
#[allow(unrooted_must_root)] #[allow(unrooted_must_root)]
fn PromiseNativeHandler(&self, fn PromiseNativeHandler(&self,
resolve: Option<Rc<SimpleCallback>>, resolve: Option<Rc<SimpleCallback>>,
@ -760,3 +774,20 @@ impl TestBinding {
pub unsafe fn condition_satisfied(_: *mut JSContext, _: HandleObject) -> bool { true } pub unsafe fn condition_satisfied(_: *mut JSContext, _: HandleObject) -> bool { true }
pub unsafe fn condition_unsatisfied(_: *mut JSContext, _: HandleObject) -> bool { false } pub unsafe fn condition_unsatisfied(_: *mut JSContext, _: HandleObject) -> bool { false }
} }
#[derive(JSTraceable, HeapSizeOf)]
pub struct TestBindingCallback {
#[ignore_heap_size_of = "unclear ownership semantics"]
promise: TrustedPromise,
value: DOMString,
}
impl TestBindingCallback {
#[allow(unrooted_must_root)]
pub fn invoke(self) {
let p = self.promise.root();
let cx = p.global().r().get_cx();
let _ac = JSAutoCompartment::new(cx, p.reflector().get_jsobject().get());
p.maybe_resolve_native(cx, &self.value);
}
}

View file

@ -519,6 +519,7 @@ interface TestBinding {
void promiseResolveNative(Promise<any> p, any value); void promiseResolveNative(Promise<any> p, any value);
void promiseRejectNative(Promise<any> p, any value); void promiseRejectNative(Promise<any> p, any value);
void promiseRejectWithTypeError(Promise<any> p, USVString message); void promiseRejectWithTypeError(Promise<any> p, USVString message);
void resolvePromiseDelayed(Promise<any> p, DOMString value, unsigned long long ms);
void panic(); void panic();
}; };

View file

@ -8,6 +8,7 @@ use dom::bindings::codegen::Bindings::FunctionBinding::Function;
use dom::bindings::global::GlobalRef; use dom::bindings::global::GlobalRef;
use dom::bindings::reflector::Reflectable; use dom::bindings::reflector::Reflectable;
use dom::bindings::str::DOMString; use dom::bindings::str::DOMString;
use dom::testbinding::TestBindingCallback;
use dom::window::ScriptHelpers; use dom::window::ScriptHelpers;
use dom::xmlhttprequest::XHRTimeoutCallback; use dom::xmlhttprequest::XHRTimeoutCallback;
use euclid::length::Length; use euclid::length::Length;
@ -68,6 +69,7 @@ struct OneshotTimer {
pub enum OneshotTimerCallback { pub enum OneshotTimerCallback {
XhrTimeout(XHRTimeoutCallback), XhrTimeout(XHRTimeoutCallback),
JsTimer(JsTimerTask), JsTimer(JsTimerTask),
TestBindingCallback(TestBindingCallback),
} }
impl OneshotTimerCallback { impl OneshotTimerCallback {
@ -75,6 +77,7 @@ impl OneshotTimerCallback {
match self { match self {
OneshotTimerCallback::XhrTimeout(callback) => callback.invoke(), OneshotTimerCallback::XhrTimeout(callback) => callback.invoke(),
OneshotTimerCallback::JsTimer(task) => task.invoke(this, js_timers), OneshotTimerCallback::JsTimer(task) => task.invoke(this, js_timers),
OneshotTimerCallback::TestBindingCallback(callback) => callback.invoke(),
} }
} }
} }

View file

@ -51,4 +51,17 @@
assert_equals(rejected, 'success') assert_equals(rejected, 'success')
}); });
}, 'Native reject callback gets argument'); }, 'Native reject callback gets argument');
promise_test(function(test) {
var t = new TestBinding;
var resolved;
var p = new Promise(function() {});
var start = Date.now();
t.resolvePromiseDelayed(p, 'success', 100);
return p.then(function(v) {
var end = Date.now();
assert_true(end - start > 100);
assert_equals(v, 'success');
});
}, 'Native promise from async callback can be resolved');
</script> </script>