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