Skip to main content

script/dom/bindings/
refcounted.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! A generic, safe mechanism by which Promise objects can be pinned and transferred
6//! between threads (or intra-thread for asynchronous events). See more information in
7//! script_bindings::refcounted
8
9use std::cell::RefCell;
10use std::thread::{self, ThreadId};
11
12use js::context::JSContext;
13use js::conversions::ToJSValConvertible;
14use js::jsapi::JSTracer;
15use js::rust::Trace;
16use rustc_hash::FxHashMap;
17use script_bindings::error::Error;
18use script_bindings::interfaces::ThreadTrustedPromiseHelpers;
19pub(crate) use script_bindings::refcounted::Trusted;
20use script_bindings::reflector::DomObject;
21
22use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
23use crate::tasks::task::TaskOnce;
24
25thread_local!(pub(super) static LIVE_PROMISE_REFERENCES: LivePromiseReferences =
26    LivePromiseReferences {
27        promise_table: RefCell::new(FxHashMap::default()),
28    }
29);
30
31#[derive(Clone, Copy, Eq, Hash, PartialEq)]
32struct PromiseKey(*const Promise);
33
34/// # Safety
35/// PromiseKey is only ever used for comparisons between keys.
36/// Its value is never read.
37unsafe impl Send for PromiseKey {}
38
39/// The set of live, pinned DOM objects that are currently prevented
40/// from being garbage collected due to outstanding references.
41#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
42pub(crate) struct LivePromiseReferences {
43    // keyed on pointer to Rust DOM object
44    promise_table: RefCell<FxHashMap<PromiseKey, TracedPromise>>,
45}
46
47impl LivePromiseReferences {
48    pub(crate) fn destruct() {
49        LIVE_PROMISE_REFERENCES.with(|live_references| {
50            let _ = live_references.promise_table.take();
51        });
52    }
53
54    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
55    fn store_promise(&self, promise: &RootedPromise) -> PromiseKey {
56        // Since converting a RootedPromise to a TracedPromise allocates a new
57        // underlying Rc<Promise>, we are guaranteed that there is no prior entry
58        // in the hashtable for this particular promise object.
59        let traced_promise = promise.to_traced();
60        let key = PromiseKey(&raw const *traced_promise);
61        let _exists = self
62            .promise_table
63            .borrow_mut()
64            .insert(key, traced_promise)
65            .is_some();
66        debug_assert!(!_exists);
67        key
68    }
69}
70
71/// A safe wrapper around a DOM Promise object that can be shared among threads for use
72/// in asynchronous operations. The underlying DOM object is guaranteed to live at least
73/// as long as the last outstanding `TrustedPromise` instance. These values cannot be cloned,
74/// only created from existing `Rc<Promise>` values.
75pub(crate) struct TrustedPromise {
76    dom_object: PromiseKey,
77    owner_thread: ThreadId,
78}
79
80impl TrustedPromise {
81    /// Create a new `TrustedPromise` instance from an existing DOM object. The object will
82    /// be prevented from being GCed for the duration of the resulting `TrustedPromise` object's
83    /// lifetime.
84    pub(crate) fn new(promise: &RootedPromise) -> TrustedPromise {
85        LIVE_PROMISE_REFERENCES.with(|live_references| {
86            let ptr = live_references.store_promise(promise);
87            TrustedPromise {
88                dom_object: ptr,
89                owner_thread: thread::current().id(),
90            }
91        })
92    }
93
94    /// Obtain a usable DOM Promise from a pinned `TrustedPromise` value. Fails if used on
95    /// a different thread than the original value from which this `TrustedPromise` was
96    /// obtained.
97    pub(crate) fn root(self, cx: &JSContext) -> RootedPromise {
98        LIVE_PROMISE_REFERENCES.with(|live_references| {
99            debug_assert_eq!(self.owner_thread, thread::current().id());
100            live_references
101                .promise_table
102                .borrow_mut()
103                .remove(&self.dom_object)
104                .expect("Must always have an entry matching a TrustedPromise")
105                .root(cx)
106        })
107    }
108
109    /// A task which will reject the promise.
110    pub(crate) fn reject_task(self, error: Error) -> impl TaskOnce {
111        let this = self;
112        task!(reject_promise: move |cx| {
113            debug!("Rejecting promise.");
114            this.root(cx).reject_error(cx, error);
115        })
116    }
117
118    /// A task which will resolve the promise.
119    pub(crate) fn resolve_task<T>(self, value: T) -> impl TaskOnce
120    where
121        T: ToJSValConvertible + Send,
122    {
123        let this = self;
124        task!(resolve_promise: move |cx| {
125            debug!("Resolving promise.");
126            this.root(cx).resolve_native(cx, &value);
127        })
128    }
129}
130
131impl ThreadTrustedPromiseHelpers<crate::DomTypeHolder> for TrustedPromise {
132    type StackRoot = RootedPromise;
133
134    fn root(self, cx: &JSContext) -> RootedPromise {
135        TrustedPromise::root(self, cx)
136    }
137}
138
139/// A JSTraceDataOp for tracing reflectors held in LIVE_REFERENCES
140pub(crate) unsafe fn trace_refcounted_objects(tracer: *mut JSTracer) {
141    trace!("tracing live refcounted promise references");
142    LIVE_PROMISE_REFERENCES.with(|live_references| {
143        let table = live_references.promise_table.borrow_mut();
144        for promise in table.values() {
145            unsafe {
146                promise.reflector().trace(tracer);
147            }
148        }
149    });
150    trace!("tracing live refcounted references");
151    unsafe {
152        script_bindings::refcounted::trace_live_domreferences(tracer);
153    }
154}