base/
id.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//! Namespaces and ids shared by many crates in Servo.
6
7#![allow(clippy::new_without_default)]
8
9use std::cell::Cell;
10use std::fmt;
11use std::marker::PhantomData;
12use std::num::NonZeroU32;
13use std::sync::{Arc, LazyLock};
14
15use malloc_size_of::MallocSizeOfOps;
16use malloc_size_of_derive::MallocSizeOf;
17use parking_lot::Mutex;
18use serde::{Deserialize, Serialize};
19use webrender_api::{ExternalScrollId, PipelineId as WebRenderPipelineId};
20
21use crate::generic_channel::{self, GenericReceiver, GenericSender};
22
23/// Asserts the size of a type at compile time.
24macro_rules! size_of_test {
25    ($t: ty, $expected_size: expr) => {
26        const _: () = assert!(std::mem::size_of::<$t>() == $expected_size);
27    };
28}
29
30/// A type that implements this trait is expected to be used as part of
31/// the [NamespaceIndex] type.
32pub trait Indexable {
33    /// The string prefix to display when debug printing an instance of
34    /// this type.
35    const DISPLAY_PREFIX: &'static str;
36}
37
38#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
39/// A non-zero index, associated with a particular type.
40pub struct Index<T>(pub NonZeroU32, pub PhantomData<T>);
41
42#[derive(Debug)]
43/// An attempt to create a new [Index] value failed because the index value
44/// was zero.
45pub struct ZeroIndex;
46
47impl<T> Index<T> {
48    /// Creates a new instance of [Index] with the given value.
49    /// Returns an error if the value is zero.
50    pub fn new(value: u32) -> Result<Index<T>, ZeroIndex> {
51        Ok(Index(NonZeroU32::new(value).ok_or(ZeroIndex)?, PhantomData))
52    }
53}
54
55impl<T> malloc_size_of::MallocSizeOf for Index<T> {
56    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
57        0
58    }
59}
60
61#[derive(
62    Clone, Copy, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize,
63)]
64/// A pipeline-namespaced index associated with a particular type.
65pub struct NamespaceIndex<T> {
66    pub namespace_id: PipelineNamespaceId,
67    pub index: Index<T>,
68}
69
70impl<T> fmt::Debug for NamespaceIndex<T> {
71    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
72        let PipelineNamespaceId(namespace_id) = self.namespace_id;
73        let Index(index, _) = self.index;
74        write!(fmt, "({},{})", namespace_id, index.get())
75    }
76}
77
78impl<T: Indexable> fmt::Display for NamespaceIndex<T> {
79    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
80        write!(fmt, "{}{:?}", T::DISPLAY_PREFIX, self)
81    }
82}
83
84macro_rules! namespace_id {
85    ($id_name:ident, $index_name:ident, $display_prefix:literal) => {
86        #[derive(
87            Clone,
88            Copy,
89            Debug,
90            Deserialize,
91            Eq,
92            Hash,
93            Ord,
94            PartialEq,
95            PartialOrd,
96            Serialize,
97            MallocSizeOf,
98        )]
99        pub struct $index_name;
100        impl Indexable for $index_name {
101            const DISPLAY_PREFIX: &'static str = $display_prefix;
102        }
103        pub type $id_name = NamespaceIndex<$index_name>;
104        impl $id_name {
105            pub fn new() -> $id_name {
106                PIPELINE_NAMESPACE.with(|tls| {
107                    let mut namespace = tls.get().expect("No namespace set for this thread!");
108                    let next_id = namespace.next_namespace_index();
109                    tls.set(Some(namespace));
110                    next_id
111                })
112            }
113        }
114    };
115}
116
117#[derive(Debug, Deserialize, Serialize)]
118/// Request a pipeline-namespace id from the constellation.
119pub struct PipelineNamespaceRequest(pub GenericSender<PipelineNamespaceId>);
120
121/// A per-process installer of pipeline-namespaces.
122pub struct PipelineNamespaceInstaller {
123    request_sender: Option<GenericSender<PipelineNamespaceRequest>>,
124    namespace_sender: GenericSender<PipelineNamespaceId>,
125    namespace_receiver: GenericReceiver<PipelineNamespaceId>,
126}
127
128impl Default for PipelineNamespaceInstaller {
129    fn default() -> Self {
130        let (namespace_sender, namespace_receiver) =
131            generic_channel::channel().expect("PipelineNamespaceInstaller channel failure");
132        Self {
133            request_sender: None,
134            namespace_sender,
135            namespace_receiver,
136        }
137    }
138}
139
140impl PipelineNamespaceInstaller {
141    /// Provide a request sender to send requests to the constellation.
142    pub fn set_sender(&mut self, sender: GenericSender<PipelineNamespaceRequest>) {
143        self.request_sender = Some(sender);
144    }
145
146    /// Install a namespace, requesting a new Id from the constellation.
147    pub fn install_namespace(&self) {
148        match self.request_sender.as_ref() {
149            Some(sender) => {
150                let _ = sender.send(PipelineNamespaceRequest(self.namespace_sender.clone()));
151                let namespace_id = self
152                    .namespace_receiver
153                    .recv()
154                    .expect("The constellation to make a pipeline namespace id available");
155                PipelineNamespace::install(namespace_id);
156            },
157            None => unreachable!("PipelineNamespaceInstaller should have a request_sender setup"),
158        }
159    }
160}
161
162/// A per-process unique pipeline-namespace-installer.
163/// Accessible via PipelineNamespace.
164///
165/// Use PipelineNamespace::set_installer_sender to initiate with a sender to the constellation,
166/// when a new process has been created.
167///
168/// Use PipelineNamespace::fetch_install to install a unique pipeline-namespace from the calling thread.
169static PIPELINE_NAMESPACE_INSTALLER: LazyLock<Arc<Mutex<PipelineNamespaceInstaller>>> =
170    LazyLock::new(|| Arc::new(Mutex::new(PipelineNamespaceInstaller::default())));
171
172/// Each pipeline ID needs to be unique. However, it also needs to be possible to
173/// generate the pipeline ID from an iframe element (this simplifies a lot of other
174/// code that makes use of pipeline IDs).
175///
176/// To achieve this, each pipeline index belongs to a particular namespace. There is
177/// a namespace for the constellation thread, and also one for every script thread.
178///
179/// A namespace can be installed for any other thread in a process
180/// where an pipeline-installer has been initialized.
181///
182/// This allows pipeline IDs to be generated by any of those threads without conflicting
183/// with pipeline IDs created by other script threads or the constellation. The
184/// constellation is the only code that is responsible for creating new *namespaces*.
185/// This ensures that namespaces are always unique, even when using multi-process mode.
186///
187/// It may help conceptually to think of the namespace ID as an identifier for the
188/// thread that created this pipeline ID - however this is really an implementation
189/// detail so shouldn't be relied upon in code logic. It's best to think of the
190/// pipeline ID as a simple unique identifier that doesn't convey any more information.
191#[derive(Clone, Copy)]
192pub struct PipelineNamespace {
193    id: PipelineNamespaceId,
194    index: u32,
195}
196
197impl PipelineNamespace {
198    /// Install a namespace for a given Id.
199    pub fn install(namespace_id: PipelineNamespaceId) {
200        PIPELINE_NAMESPACE.with(|tls| {
201            assert!(tls.get().is_none());
202            tls.set(Some(PipelineNamespace {
203                id: namespace_id,
204                index: 0,
205            }));
206        });
207    }
208
209    /// Setup the pipeline-namespace-installer, by providing it with a sender to the constellation.
210    /// Idempotent in single-process mode.
211    pub fn set_installer_sender(sender: GenericSender<PipelineNamespaceRequest>) {
212        PIPELINE_NAMESPACE_INSTALLER.lock().set_sender(sender);
213    }
214
215    /// Install a namespace in the current thread, without requiring having a namespace Id ready.
216    /// Panics if called more than once per thread.
217    pub fn auto_install() {
218        // Note that holding the lock for the duration of the call is irrelevant to performance,
219        // since a thread would have to block on the ipc-response from the constellation,
220        // with the constellation already acting as a global lock on namespace ids,
221        // and only being able to handle one request at a time.
222        //
223        // Hence, any other thread attempting to concurrently install a namespace
224        // would have to wait for the current call to finish, regardless of the lock held here.
225        PIPELINE_NAMESPACE_INSTALLER.lock().install_namespace();
226    }
227
228    fn next_index(&mut self) -> NonZeroU32 {
229        self.index += 1;
230        NonZeroU32::new(self.index).expect("pipeline id index wrapped!")
231    }
232
233    fn next_namespace_index<T>(&mut self) -> NamespaceIndex<T> {
234        NamespaceIndex {
235            namespace_id: self.id,
236            index: Index(self.next_index(), PhantomData),
237        }
238    }
239}
240
241thread_local!(pub static PIPELINE_NAMESPACE: Cell<Option<PipelineNamespace>> = const { Cell::new(None) });
242
243#[derive(
244    Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize,
245)]
246pub struct PipelineNamespaceId(pub u32);
247
248namespace_id! {PipelineId, PipelineIndex, "Pipeline"}
249
250size_of_test!(PipelineId, 8);
251size_of_test!(Option<PipelineId>, 8);
252
253impl PipelineId {
254    pub fn root_scroll_id(&self) -> webrender_api::ExternalScrollId {
255        ExternalScrollId(0, self.into())
256    }
257}
258
259impl From<WebRenderPipelineId> for PipelineId {
260    #[allow(unsafe_code)]
261    fn from(pipeline: WebRenderPipelineId) -> Self {
262        let WebRenderPipelineId(namespace_id, index) = pipeline;
263        unsafe {
264            PipelineId {
265                namespace_id: PipelineNamespaceId(namespace_id),
266                index: Index(NonZeroU32::new_unchecked(index), PhantomData),
267            }
268        }
269    }
270}
271
272impl From<PipelineId> for WebRenderPipelineId {
273    fn from(value: PipelineId) -> Self {
274        let PipelineNamespaceId(namespace_id) = value.namespace_id;
275        let Index(index, _) = value.index;
276        WebRenderPipelineId(namespace_id, index.get())
277    }
278}
279
280impl From<&PipelineId> for WebRenderPipelineId {
281    fn from(value: &PipelineId) -> Self {
282        (*value).into()
283    }
284}
285
286namespace_id! {BrowsingContextId, BrowsingContextIndex, "BrowsingContext"}
287
288size_of_test!(BrowsingContextId, 8);
289size_of_test!(Option<BrowsingContextId>, 8);
290
291#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
292pub struct BrowsingContextGroupId(pub u32);
293impl fmt::Display for BrowsingContextGroupId {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        write!(f, "BrowsingContextGroup{:?}", self)
296    }
297}
298
299thread_local!(pub static WEBVIEW_ID: Cell<Option<WebViewId>> =
300    const { Cell::new(None) });
301
302#[derive(
303    Clone, Copy, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize,
304)]
305pub struct WebViewId(BrowsingContextId);
306
307size_of_test!(WebViewId, 8);
308size_of_test!(Option<WebViewId>, 8);
309
310impl fmt::Debug for WebViewId {
311    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312        write!(f, "TopLevel{:?}", self.0)
313    }
314}
315
316impl fmt::Display for WebViewId {
317    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318        write!(f, "TopLevel{}", self.0)
319    }
320}
321
322impl WebViewId {
323    pub fn new() -> WebViewId {
324        WebViewId(BrowsingContextId::new())
325    }
326
327    /// Each script and layout thread should have the top-level browsing context id installed,
328    /// since it is used by crash reporting.
329    pub fn install(id: WebViewId) {
330        WEBVIEW_ID.with(|tls| tls.set(Some(id)))
331    }
332
333    pub fn installed() -> Option<WebViewId> {
334        WEBVIEW_ID.with(|tls| tls.get())
335    }
336
337    pub fn mock_for_testing(browsing_context_id: BrowsingContextId) -> WebViewId {
338        WebViewId(browsing_context_id)
339    }
340}
341
342impl From<WebViewId> for BrowsingContextId {
343    fn from(id: WebViewId) -> BrowsingContextId {
344        id.0
345    }
346}
347
348impl PartialEq<WebViewId> for BrowsingContextId {
349    fn eq(&self, rhs: &WebViewId) -> bool {
350        self.eq(&rhs.0)
351    }
352}
353
354impl PartialEq<BrowsingContextId> for WebViewId {
355    fn eq(&self, rhs: &BrowsingContextId) -> bool {
356        self.0.eq(rhs)
357    }
358}
359
360namespace_id! {MessagePortId, MessagePortIndex, "MessagePort"}
361
362namespace_id! {MessagePortRouterId, MessagePortRouterIndex, "MessagePortRouter"}
363
364namespace_id! {BroadcastChannelRouterId, BroadcastChannelRouterIndex, "BroadcastChannelRouter"}
365
366namespace_id! {ServiceWorkerId, ServiceWorkerIndex, "ServiceWorker"}
367
368namespace_id! {ServiceWorkerRegistrationId, ServiceWorkerRegistrationIndex, "ServiceWorkerRegistration"}
369
370namespace_id! {BlobId, BlobIndex, "Blob"}
371
372namespace_id! {DomPointId, DomPointIndex, "DomPoint"}
373
374namespace_id! {DomRectId, DomRectIndex, "DomRect"}
375
376namespace_id! {DomQuadId, DomQuadIndex, "DomQuad"}
377
378namespace_id! {DomMatrixId, DomMatrixIndex, "DomMatrix"}
379
380namespace_id! {DomExceptionId, DomExceptionIndex, "DomException"}
381
382namespace_id! {QuotaExceededErrorId, QuotaExceededErrorIndex, "QuotaExceededError"}
383
384namespace_id! {HistoryStateId, HistoryStateIndex, "HistoryState"}
385
386namespace_id! {ImageBitmapId, ImageBitmapIndex, "ImageBitmap"}
387
388namespace_id! {OffscreenCanvasId, OffscreenCanvasIndex, "OffscreenCanvas"}
389
390namespace_id! {CookieStoreId, CookieStoreIndex, "CookieStore"}
391
392// We provide ids just for unit testing.
393pub const TEST_NAMESPACE: PipelineNamespaceId = PipelineNamespaceId(1234);
394pub const TEST_PIPELINE_INDEX: Index<PipelineIndex> =
395    Index(NonZeroU32::new(5678).unwrap(), PhantomData);
396pub const TEST_PIPELINE_ID: PipelineId = PipelineId {
397    namespace_id: TEST_NAMESPACE,
398    index: TEST_PIPELINE_INDEX,
399};
400pub const TEST_BROWSING_CONTEXT_INDEX: Index<BrowsingContextIndex> =
401    Index(NonZeroU32::new(8765).unwrap(), PhantomData);
402pub const TEST_BROWSING_CONTEXT_ID: BrowsingContextId = BrowsingContextId {
403    namespace_id: TEST_NAMESPACE,
404    index: TEST_BROWSING_CONTEXT_INDEX,
405};
406
407pub const TEST_WEBVIEW_ID: WebViewId = WebViewId(TEST_BROWSING_CONTEXT_ID);
408
409/// An id for a ScrollTreeNode in the ScrollTree. This contains both the index
410/// to the node in the tree's array of nodes as well as the corresponding SpatialId
411/// for the SpatialNode in the WebRender display list.
412#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
413pub struct ScrollTreeNodeId {
414    /// The index of this scroll tree node in the tree's array of nodes.
415    pub index: usize,
416}