use std::cell::RefCell;
use std::thread::{self, ThreadId};
use js::context::JSContext;
use js::conversions::ToJSValConvertible;
use js::jsapi::JSTracer;
use js::rust::Trace;
use rustc_hash::FxHashMap;
use script_bindings::error::Error;
pub(crate) use script_bindings::refcounted::Trusted;
use script_bindings::reflector::DomObject;
use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
use crate::tasks::task::TaskOnce;
thread_local!(pub(super) static LIVE_PROMISE_REFERENCES: LivePromiseReferences =
LivePromiseReferences {
promise_table: RefCell::new(FxHashMap::default()),
}
);
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
struct PromiseKey(*const Promise);
unsafe impl Send for PromiseKey {}
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) struct LivePromiseReferences {
promise_table: RefCell<FxHashMap<PromiseKey, TracedPromise>>,
}
impl LivePromiseReferences {
pub(crate) fn destruct() {
LIVE_PROMISE_REFERENCES.with(|live_references| {
let _ = live_references.promise_table.take();
});
}
#[cfg_attr(crown, expect(crown::unrooted_must_root))]
fn store_promise(&self, promise: &RootedPromise) -> PromiseKey {
let traced_promise = promise.to_traced();
let key = PromiseKey(&raw const *traced_promise);
let _exists = self
.promise_table
.borrow_mut()
.insert(key, traced_promise)
.is_some();
debug_assert!(!_exists);
key
}
}
pub struct TrustedPromise {
dom_object: PromiseKey,
owner_thread: ThreadId,
}
impl TrustedPromise {
pub(crate) fn new(promise: &RootedPromise) -> TrustedPromise {
LIVE_PROMISE_REFERENCES.with(|live_references| {
let ptr = live_references.store_promise(promise);
TrustedPromise {
dom_object: ptr,
owner_thread: thread::current().id(),
}
})
}
pub(crate) fn root(self, cx: &JSContext) -> RootedPromise {
LIVE_PROMISE_REFERENCES.with(|live_references| {
debug_assert_eq!(self.owner_thread, thread::current().id());
live_references
.promise_table
.borrow_mut()
.remove(&self.dom_object)
.expect("Must always have an entry matching a TrustedPromise")
.root(cx)
})
}
pub(crate) fn reject_task(self, error: Error) -> impl TaskOnce {
let this = self;
task!(reject_promise: move |cx| {
debug!("Rejecting promise.");
this.root(cx).reject_error(cx, error);
})
}
pub(crate) fn resolve_task<T>(self, value: T) -> impl TaskOnce
where
T: ToJSValConvertible + Send,
{
let this = self;
task!(resolve_promise: move |cx| {
debug!("Resolving promise.");
this.root(cx).resolve_native(cx, &value);
})
}
}
pub(crate) unsafe fn trace_refcounted_objects(tracer: *mut JSTracer) {
trace!("tracing live refcounted promise references");
LIVE_PROMISE_REFERENCES.with(|live_references| {
let table = live_references.promise_table.borrow_mut();
for promise in table.values() {
unsafe {
promise.reflector().trace(tracer);
}
}
});
trace!("tracing live refcounted references");
unsafe {
script_bindings::refcounted::trace_live_domreferences(tracer);
}
}