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::collections::hash_map::Entry::{Occupied, Vacant};
11use std::rc::Rc;
12
13use js::context::JSContext;
14use js::conversions::ToJSValConvertible;
15use js::jsapi::JSTracer;
16use rustc_hash::FxHashMap;
17use script_bindings::error::Error;
18pub(crate) use script_bindings::refcounted::Trusted;
19use script_bindings::reflector::DomObject;
20use script_bindings::trace::trace_reflector;
21
22use crate::dom::promise::{Promise, RootedPromise};
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/// The set of live, pinned DOM objects that are currently prevented
32/// from being garbage collected due to outstanding references.
33pub(crate) struct LivePromiseReferences {
34    // keyed on pointer to Rust DOM object
35    promise_table: RefCell<FxHashMap<*const Promise, Vec<Rc<Promise>>>>,
36}
37
38impl LivePromiseReferences {
39    pub(crate) fn destruct() {
40        LIVE_PROMISE_REFERENCES.with(|live_references| {
41            let _ = live_references.promise_table.take();
42        });
43    }
44
45    fn addref_promise(&self, promise: Rc<Promise>) {
46        let mut table = self.promise_table.borrow_mut();
47        table.entry(&*promise).or_default().push(promise)
48    }
49}
50
51/// A safe wrapper around a DOM Promise object that can be shared among threads for use
52/// in asynchronous operations. The underlying DOM object is guaranteed to live at least
53/// as long as the last outstanding `TrustedPromise` instance. These values cannot be cloned,
54/// only created from existing `Rc<Promise>` values.
55pub struct TrustedPromise {
56    dom_object: *const Promise,
57    owner_thread: *const libc::c_void,
58}
59
60unsafe impl Send for TrustedPromise {}
61
62impl TrustedPromise {
63    /// Create a new `TrustedPromise` instance from an existing DOM object. The object will
64    /// be prevented from being GCed for the duration of the resulting `TrustedPromise` object's
65    /// lifetime.
66    pub(crate) fn new(promise: Rc<Promise>) -> TrustedPromise {
67        LIVE_PROMISE_REFERENCES.with(|live_references| {
68            let ptr = &raw const *promise;
69            live_references.addref_promise(promise);
70            TrustedPromise {
71                dom_object: ptr,
72                owner_thread: (live_references) as *const _ as *const libc::c_void,
73            }
74        })
75    }
76
77    /// Obtain a usable DOM Promise from a pinned `TrustedPromise` value. Fails if used on
78    /// a different thread than the original value from which this `TrustedPromise` was
79    /// obtained.
80    pub(crate) fn root(self, cx: &JSContext) -> RootedPromise {
81        LIVE_PROMISE_REFERENCES.with(|live_references| {
82            assert_eq!(
83                self.owner_thread,
84                live_references as *const _ as *const libc::c_void
85            );
86            match live_references
87                .promise_table
88                .borrow_mut()
89                .entry(self.dom_object)
90            {
91                Occupied(mut entry) => {
92                    let promise = {
93                        let promises = entry.get_mut();
94                        promises
95                            .pop()
96                            .expect("rooted promise list unexpectedly empty")
97                            .duplicate(cx)
98                    };
99                    if entry.get().is_empty() {
100                        entry.remove();
101                    }
102                    promise
103                },
104                Vacant(_) => unreachable!(),
105            }
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
131/// A JSTraceDataOp for tracing reflectors held in LIVE_REFERENCES
132pub(crate) unsafe fn trace_refcounted_objects(tracer: *mut JSTracer) {
133    trace!("tracing live refcounted promise references");
134    LIVE_PROMISE_REFERENCES.with(|live_references| {
135        let table = live_references.promise_table.borrow_mut();
136        for promise in table.keys() {
137            unsafe {
138                trace_reflector(tracer, "refcounted", (**promise).reflector());
139            }
140        }
141    });
142    trace!("tracing live refcounted references");
143    unsafe {
144        script_bindings::refcounted::trace_live_domreferences(tracer);
145    }
146}