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::reflector::{Reflectable, Reflector};
use dom::bindings::trace::trace_reflector;
use dom::promise::Promise;
use js::jsapi::JSTracer;
use libc;
use std::cell::RefCell;
@ -34,6 +35,7 @@ use std::collections::hash_map::HashMap;
use std::hash::Hash;
use std::marker::PhantomData;
use std::os;
use std::rc::Rc;
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
/// shared among threads for use in asynchronous operations. The underlying
/// 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
/// from being garbage collected due to outstanding references.
#[allow(unrooted_must_root)]
pub struct LiveDOMReferences {
// 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 {
@ -127,13 +188,20 @@ impl LiveDOMReferences {
pub fn initialize() {
LIVE_REFERENCES.with(|ref r| {
*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> {
let mut table = self.table.borrow_mut();
let mut table = self.reflectable_table.borrow_mut();
let capacity = table.capacity();
let len = table.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
#[allow(unrooted_must_root)]
pub unsafe extern "C" fn trace_refcounted_objects(tracer: *mut JSTracer,
_data: *mut os::raw::c_void) {
info!("tracing live refcounted references");
LIVE_REFERENCES.with(|ref r| {
let r = r.borrow();
let live_references = r.as_ref().unwrap();
let mut table = live_references.table.borrow_mut();
{
let mut table = live_references.reflectable_table.borrow_mut();
remove_nulls(&mut table);
for obj in table.keys() {
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 dom::abstractworker::SharedRt;
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::str::{DOMString, USVString};
use dom::bindings::utils::WindowProxyHandler;
@ -303,6 +303,7 @@ no_jsmanaged_fields!(Metadata);
no_jsmanaged_fields!(NetworkError);
no_jsmanaged_fields!(Atom, Namespace, QualName);
no_jsmanaged_fields!(Trusted<T: Reflectable>);
no_jsmanaged_fields!(TrustedPromise);
no_jsmanaged_fields!(PropertyDeclarationBlock);
no_jsmanaged_fields!(HashSet<T>);
// 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)]
unsafe fn new_with_js_promise(obj: HandleObject, cx: *mut JSContext) -> Rc<Promise> {
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::mozmap::MozMap;
use dom::bindings::num::Finite;
use dom::bindings::refcounted::TrustedPromise;
use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object};
use dom::bindings::str::{ByteString, DOMString, USVString};
use dom::bindings::weakref::MutableWeakRef;
@ -32,12 +33,14 @@ use dom::blob::{Blob, BlobImpl};
use dom::promise::Promise;
use dom::promisenativehandler::{PromiseNativeHandler, Callback};
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::jsval::{JSVal, NullValue};
use script_traits::MsDuration;
use std::borrow::ToOwned;
use std::ptr;
use std::rc::Rc;
use timers::OneshotTimerCallback;
use util::prefs::PREFS;
#[dom_struct]
@ -672,6 +675,17 @@ impl TestBindingMethods for TestBinding {
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)]
fn PromiseNativeHandler(&self,
resolve: Option<Rc<SimpleCallback>>,
@ -760,3 +774,20 @@ impl TestBinding {
pub unsafe fn condition_satisfied(_: *mut JSContext, _: HandleObject) -> bool { true }
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 promiseRejectNative(Promise<any> p, any value);
void promiseRejectWithTypeError(Promise<any> p, USVString message);
void resolvePromiseDelayed(Promise<any> p, DOMString value, unsigned long long ms);
void panic();
};

View file

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

View file

@ -51,4 +51,17 @@
assert_equals(rejected, 'success')
});
}, '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>