mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
Support an equivalent of Trusted<T> for Rc<Promise> objects named TrustedPromise.
This commit is contained in:
parent
27d44c8d10
commit
498ccd41e8
7 changed files with 145 additions and 10 deletions
|
@ -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();
|
{
|
||||||
|
let mut table = live_references.reflectable_table.borrow_mut();
|
||||||
remove_nulls(&mut table);
|
remove_nulls(&mut table);
|
||||||
for obj in table.keys() {
|
for obj in table.keys() {
|
||||||
let reflectable = &*(*obj as *const Reflector);
|
let reflectable = &*(*obj as *const Reflector);
|
||||||
trace_reflector(tracer, "refcounted", reflectable);
|
trace_reflector(tracer, "refcounted", reflectable);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let table = live_references.promise_table.borrow_mut();
|
||||||
|
for promise in table.keys() {
|
||||||
|
trace_reflector(tracer, "refcounted", (**promise).reflector());
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue