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::sync::{Arc, Weak};
11use std::thread::{self, ThreadId};
12
13use js::context::JSContext;
14use js::conversions::ToJSValConvertible;
15use js::jsapi::JSTracer;
16use js::rust::Trace;
17use rustc_hash::FxHashMap;
18use script_bindings::error::Error;
19use script_bindings::interfaces::ThreadTrustedPromiseHelpers;
20pub(crate) use script_bindings::refcounted::Trusted;
21use script_bindings::reflector::DomObject;
22
23use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
24use crate::tasks::task::TaskOnce;
25
26thread_local!(pub(super) static LIVE_PROMISE_REFERENCES: LivePromiseReferences =
27    LivePromiseReferences {
28        promise_table: RefCell::new(FxHashMap::default()),
29    }
30);
31
32#[derive(Clone, Copy, Eq, Hash, PartialEq)]
33struct PromiseKey(*const Promise);
34
35/// # Safety
36/// PromiseKey is only ever used for comparisons between keys.
37/// Its value is never read.
38unsafe impl Send for PromiseKey {}
39unsafe impl Sync for PromiseKey {}
40
41/// The set of live, pinned DOM objects that are currently prevented
42/// from being garbage collected due to outstanding references.
43#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
44pub(crate) struct LivePromiseReferences {
45    // The table of promises and weak keys keyed on the address of the `Rc<Promise>` owned by each
46    // entry. The weak key tracks whether there are any live references to this entry remaining.
47    promise_table: RefCell<FxHashMap<PromiseKey, (TracedPromise, Weak<PromiseKey>)>>,
48}
49
50impl LivePromiseReferences {
51    /// Prune all `TrustedPromise` entries that no longer have live references. Since this walks
52    /// through every promise, it should not be called from hot code.
53    pub(crate) fn expensive_prune_dropped_promises() {
54        LIVE_PROMISE_REFERENCES.with(|live_promise_references| {
55            live_promise_references
56                .promise_table
57                .borrow_mut()
58                .retain(|_, (_, key)| key.strong_count() > 0);
59        })
60    }
61
62    pub(crate) fn destruct() {
63        LIVE_PROMISE_REFERENCES.with(|live_references| {
64            let _ = live_references.promise_table.take();
65        });
66    }
67
68    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
69    fn store(&self, promise: &RootedPromise) -> TrustedPromise {
70        // Since converting a RootedPromise to a TracedPromise allocates a new
71        // underlying Rc<Promise>, we are guaranteed that there is no prior entry
72        // in the hashtable for this particular promise object.
73        let traced_promise = promise.to_traced();
74
75        let mut promise_table = self.promise_table.borrow_mut();
76
77        // Prune any promises that have been dropped on other threads. This is
78        // done here so that they can be dropped on the right thread. This prunes
79        // using the same heuristic as `Trusted<...>`.
80        if promise_table.len() == promise_table.capacity() {
81            promise_table.retain(|_, (_, key)| key.strong_count() > 0);
82        }
83
84        let key = Arc::new(PromiseKey(&raw const *traced_promise));
85        let _exists = promise_table
86            .insert(*key, (traced_promise, Arc::downgrade(&key)))
87            .is_some();
88        debug_assert!(!_exists);
89
90        TrustedPromise {
91            key,
92            owner_thread: thread::current().id(),
93        }
94    }
95
96    fn remove(&self, key: &PromiseKey) -> Option<TracedPromise> {
97        self.promise_table
98            .borrow_mut()
99            .remove(key)
100            .map(|(promise, _)| promise)
101    }
102}
103
104/// A safe wrapper around a DOM Promise object that can be shared among threads for use
105/// in asynchronous operations. The underlying DOM object is guaranteed to live at least
106/// as long as the last outstanding `TrustedPromise` instance. These values cannot be cloned,
107/// only created from existing `Rc<Promise>` values.
108pub(crate) struct TrustedPromise {
109    key: Arc<PromiseKey>,
110    owner_thread: ThreadId,
111}
112
113impl TrustedPromise {
114    /// Create a new `TrustedPromise` instance from an existing DOM object. The object will
115    /// be prevented from being GCed for the duration of the resulting `TrustedPromise` object's
116    /// lifetime.
117    pub(crate) fn new(promise: &RootedPromise) -> TrustedPromise {
118        LIVE_PROMISE_REFERENCES
119            .with(|live_promise_references| live_promise_references.store(promise))
120    }
121
122    /// Obtain a usable DOM Promise from a pinned `TrustedPromise` value. Fails if used on
123    /// a different thread than the original value from which this `TrustedPromise` was
124    /// obtained.
125    pub(crate) fn root(self, cx: &JSContext) -> RootedPromise {
126        LIVE_PROMISE_REFERENCES.with(|live_references| {
127            debug_assert_eq!(self.owner_thread, thread::current().id());
128            live_references
129                .promise_table
130                .borrow()
131                .get(&self.key)
132                .expect("Must always have an entry matching a TrustedPromise")
133                .0
134                .root(cx)
135        })
136    }
137
138    /// A task which will reject the promise.
139    pub(crate) fn reject_task(self, error: Error) -> impl TaskOnce {
140        let this = self;
141        task!(reject_promise: move |cx| {
142            debug!("Rejecting promise.");
143            this.root(cx).reject_error(cx, error);
144        })
145    }
146
147    /// A task which will resolve the promise.
148    pub(crate) fn resolve_task<T>(self, value: T) -> impl TaskOnce
149    where
150        T: ToJSValConvertible + Send,
151    {
152        let this = self;
153        task!(resolve_promise: move |cx| {
154            debug!("Resolving promise.");
155            this.root(cx).resolve_native(cx, &value);
156        })
157    }
158}
159
160impl Drop for TrustedPromise {
161    fn drop(&mut self) {
162        // When dropped on a different thread, removal happens lazily.
163        if self.owner_thread != thread::current().id() {
164            return;
165        }
166        // This needs `try_with` because it can be called during teardown.
167        let _ = LIVE_PROMISE_REFERENCES
168            .try_with(|live_promise_references| live_promise_references.remove(&self.key));
169    }
170}
171
172impl ThreadTrustedPromiseHelpers<crate::DomTypeHolder> for TrustedPromise {
173    type StackRoot = RootedPromise;
174
175    fn root(self, cx: &JSContext) -> RootedPromise {
176        TrustedPromise::root(self, cx)
177    }
178}
179
180/// A JSTraceDataOp for tracing reflectors held in LIVE_REFERENCES
181pub(crate) unsafe fn trace_refcounted_objects(tracer: *mut JSTracer) {
182    trace!("tracing live refcounted promise references");
183    LIVE_PROMISE_REFERENCES.with(|live_references| {
184        let table = live_references.promise_table.borrow();
185        for (promise, _) in table.values() {
186            unsafe {
187                promise.reflector().trace(tracer);
188            }
189        }
190    });
191    trace!("tracing live refcounted references");
192    unsafe {
193        script_bindings::refcounted::trace_live_domreferences(tracer);
194    }
195}