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