Skip to main content

servo_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, AtomicUsize, Ordering};
14use std::sync::{Arc, LazyLock};
15
16use accesskit::{TreeId, Uuid};
17use malloc_size_of::MallocSizeOfOps;
18use malloc_size_of_derive::MallocSizeOf;
19use parking_lot::Mutex;
20use regex::Regex;
21use serde::{Deserialize, Serialize};
22use webrender_api::{
23    ExternalScrollId, FontInstanceKey, FontKey, IdNamespace, ImageKey,
24    PipelineId as WebRenderPipelineId,
25};
26
27use crate::generic_channel::{self, GenericReceiver, GenericSender};
28
29/// Asserts the size of a type at compile time.
30macro_rules! size_of_test {
31    ($t: ty, $expected_size: expr) => {
32        const _: () = assert!(std::mem::size_of::<$t>() == $expected_size);
33    };
34}
35
36/// A type that implements this trait is expected to be used as part of
37/// the [NamespaceIndex] type.
38pub trait Indexable {
39    /// The string prefix to display when debug printing an instance of
40    /// this type.
41    const DISPLAY_PREFIX: &'static str;
42}
43
44#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
45/// A non-zero index, associated with a particular type.
46pub struct Index<T>(pub NonZeroU32, pub PhantomData<T>);
47
48#[derive(Debug)]
49/// An attempt to create a new [Index] value failed because the index value
50/// was zero.
51pub struct ZeroIndex;
52
53impl<T> Index<T> {
54    /// Creates a new instance of [Index] with the given value.
55    /// Returns an error if the value is zero.
56    pub fn new(value: u32) -> Result<Index<T>, ZeroIndex> {
57        Ok(Index(NonZeroU32::new(value).ok_or(ZeroIndex)?, PhantomData))
58    }
59}
60
61impl<T> malloc_size_of::MallocSizeOf for Index<T> {
62    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
63        0
64    }
65}
66
67#[derive(
68    Clone, Copy, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize,
69)]
70/// A pipeline-namespaced index associated with a particular type.
71pub struct NamespaceIndex<T> {
72    pub namespace_id: PipelineNamespaceId,
73    pub index: Index<T>,
74}
75
76impl<T> fmt::Debug for NamespaceIndex<T> {
77    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
78        let PipelineNamespaceId(namespace_id) = self.namespace_id;
79        let Index(index, _) = self.index;
80        write!(fmt, "({},{})", namespace_id, index.get())
81    }
82}
83
84impl<T: Indexable> fmt::Display for NamespaceIndex<T> {
85    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
86        write!(fmt, "{}{:?}", T::DISPLAY_PREFIX, self)
87    }
88}
89
90macro_rules! namespace_id {
91    ($id_name:ident, $index_name:ident, $display_prefix:literal) => {
92        #[derive(
93            Clone,
94            Copy,
95            Debug,
96            Deserialize,
97            Eq,
98            Hash,
99            Ord,
100            PartialEq,
101            PartialOrd,
102            Serialize,
103            MallocSizeOf,
104        )]
105        pub struct $index_name;
106        impl Indexable for $index_name {
107            const DISPLAY_PREFIX: &'static str = $display_prefix;
108        }
109        pub type $id_name = NamespaceIndex<$index_name>;
110        impl $id_name {
111            pub fn new() -> $id_name {
112                PIPELINE_NAMESPACE.with(|tls| {
113                    let mut namespace = tls.get().expect("No namespace set for this thread!");
114                    let next_id = namespace.next_namespace_index();
115                    tls.set(Some(namespace));
116                    next_id
117                })
118            }
119        }
120    };
121}
122
123#[derive(Debug, Deserialize, Serialize)]
124/// Request a pipeline-namespace id from the constellation.
125pub struct PipelineNamespaceRequest(pub GenericSender<PipelineNamespaceId>);
126
127/// A per-process installer of pipeline-namespaces.
128pub struct PipelineNamespaceInstaller {
129    request_sender: Option<GenericSender<PipelineNamespaceRequest>>,
130    namespace_sender: GenericSender<PipelineNamespaceId>,
131    namespace_receiver: GenericReceiver<PipelineNamespaceId>,
132}
133
134impl Default for PipelineNamespaceInstaller {
135    fn default() -> Self {
136        let (namespace_sender, namespace_receiver) =
137            generic_channel::channel().expect("PipelineNamespaceInstaller channel failure");
138        Self {
139            request_sender: None,
140            namespace_sender,
141            namespace_receiver,
142        }
143    }
144}
145
146impl PipelineNamespaceInstaller {
147    /// Provide a request sender to send requests to the constellation.
148    pub fn set_sender(&mut self, sender: GenericSender<PipelineNamespaceRequest>) {
149        self.request_sender = Some(sender);
150    }
151
152    /// Install a namespace, requesting a new Id from the constellation.
153    pub fn install_namespace(&self) {
154        match self.request_sender.as_ref() {
155            Some(sender) => {
156                let _ = sender.send(PipelineNamespaceRequest(self.namespace_sender.clone()));
157                let namespace_id = self
158                    .namespace_receiver
159                    .recv()
160                    .expect("The constellation to make a pipeline namespace id available");
161                PipelineNamespace::install(namespace_id);
162            },
163            None => unreachable!("PipelineNamespaceInstaller should have a request_sender setup"),
164        }
165    }
166}
167
168/// A per-process unique pipeline-namespace-installer.
169/// Accessible via PipelineNamespace.
170///
171/// Use PipelineNamespace::set_installer_sender to initiate with a sender to the constellation,
172/// when a new process has been created.
173///
174/// Use PipelineNamespace::fetch_install to install a unique pipeline-namespace from the calling thread.
175static PIPELINE_NAMESPACE_INSTALLER: LazyLock<Arc<Mutex<PipelineNamespaceInstaller>>> =
176    LazyLock::new(|| Arc::new(Mutex::new(PipelineNamespaceInstaller::default())));
177
178/// Each pipeline ID needs to be unique. However, it also needs to be possible to
179/// generate the pipeline ID from an iframe element (this simplifies a lot of other
180/// code that makes use of pipeline IDs).
181///
182/// To achieve this, each pipeline index belongs to a particular namespace. There is
183/// a namespace for the constellation thread, and also one for every script thread.
184///
185/// A namespace can be installed for any other thread in a process
186/// where an pipeline-installer has been initialized.
187///
188/// This allows pipeline IDs to be generated by any of those threads without conflicting
189/// with pipeline IDs created by other script threads or the constellation. The
190/// constellation is the only code that is responsible for creating new *namespaces*.
191/// This ensures that namespaces are always unique, even when using multi-process mode.
192///
193/// It may help conceptually to think of the namespace ID as an identifier for the
194/// thread that created this pipeline ID - however this is really an implementation
195/// detail so shouldn't be relied upon in code logic. It's best to think of the
196/// pipeline ID as a simple unique identifier that doesn't convey any more information.
197#[derive(Clone, Copy)]
198pub struct PipelineNamespace {
199    id: PipelineNamespaceId,
200    index: u32,
201}
202
203impl PipelineNamespace {
204    /// Install a namespace for a given Id.
205    ///
206    /// BAO PATCH (BCE-20260627-009): Idempotent — if the TLS slot is already
207    /// populated (multiplex BaoRuntime instances), skip silently instead of
208    /// panicking. `Servo::new` (servo.rs:902) always calls this; concurrent
209    /// BaoRuntime instances would hit the `assert!(tls.get().is_none())` and
210    /// SIGABRT on the second init.
211    ///
212    /// Original servo: single-instance architecture — the assert guards against
213    /// accidental double-install in the same process. Bao preserves that guard
214    /// for the FIRST install per-thread, but allows subsequent install() calls
215    /// (from the SECOND BaoRuntime::new → Servo::new on the same thread) to be
216    /// no-ops.
217    pub fn install(namespace_id: PipelineNamespaceId) {
218        PIPELINE_NAMESPACE.with(|tls| {
219            if tls.get().is_some() {
220                // Already installed on this thread — idempotent skip for
221                // multi-BaoRuntime support.
222                return;
223            }
224            tls.set(Some(PipelineNamespace {
225                id: namespace_id,
226                index: 0,
227            }));
228        });
229    }
230
231    /// Setup the pipeline-namespace-installer, by providing it with a sender to the constellation.
232    /// Idempotent in single-process mode.
233    pub fn set_installer_sender(sender: GenericSender<PipelineNamespaceRequest>) {
234        PIPELINE_NAMESPACE_INSTALLER.lock().set_sender(sender);
235    }
236
237    /// Install a namespace in the current thread, without requiring having a namespace Id ready.
238    /// Panics if called more than once per thread.
239    pub fn auto_install() {
240        // Note that holding the lock for the duration of the call is irrelevant to performance,
241        // since a thread would have to block on the ipc-response from the constellation,
242        // with the constellation already acting as a global lock on namespace ids,
243        // and only being able to handle one request at a time.
244        //
245        // Hence, any other thread attempting to concurrently install a namespace
246        // would have to wait for the current call to finish, regardless of the lock held here.
247        PIPELINE_NAMESPACE_INSTALLER.lock().install_namespace();
248    }
249
250    fn next_index(&mut self) -> NonZeroU32 {
251        self.index += 1;
252        NonZeroU32::new(self.index).expect("pipeline id index wrapped!")
253    }
254
255    fn next_namespace_index<T>(&mut self) -> NamespaceIndex<T> {
256        NamespaceIndex {
257            namespace_id: self.id,
258            index: Index(self.next_index(), PhantomData),
259        }
260    }
261}
262
263thread_local!(pub static PIPELINE_NAMESPACE: Cell<Option<PipelineNamespace>> = const { Cell::new(None) });
264
265#[derive(
266    Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize,
267)]
268pub struct PipelineNamespaceId(pub u32);
269
270pub const EMBEDDER_PIPELINE_NAMESPACE_ID: PipelineNamespaceId = PipelineNamespaceId(0);
271pub const CONSTELLATION_PIPELINE_NAMESPACE_ID: PipelineNamespaceId = PipelineNamespaceId(1);
272/// The next available [`PipelineNamespaceId`] for the allocation in the constellation. Starting from 2,
273/// since we reserved namespace 0 for the embedder, and 1 for the constellation.
274pub const FIRST_CONTENT_PIPELINE_NAMESPACE_ID: PipelineNamespaceId = PipelineNamespaceId(2);
275
276namespace_id! {PipelineId, PipelineIndex, "Pipeline"}
277
278size_of_test!(PipelineId, 8);
279size_of_test!(Option<PipelineId>, 8);
280
281impl PipelineId {
282    pub fn root_scroll_id(&self) -> webrender_api::ExternalScrollId {
283        ExternalScrollId(0, self.into())
284    }
285}
286
287impl From<PipelineId> for TreeId {
288    /// Return the AccessKit [`TreeId`] for this [`PipelineId`], assuming it represents a document.
289    ///
290    /// This is a pure function of the namespace id and index values, allowing us to graft pipelines
291    /// into `WebView`s (or other pipelines) without IPC, but it also means you can’t have multiple
292    /// instances of [`Servo`] in a single application, because the tree ids would conflict.
293    ///
294    /// [`Servo`]: https://doc.servo.org/servo/struct.Servo.html
295    fn from(pipeline_id: PipelineId) -> TreeId {
296        const PIPELINE_IDS: Uuid = Uuid::from_u128(0x429419c0_3277_47eb_8d31_7573b97621ee);
297        let with_namespace_id =
298            Uuid::new_v5(&PIPELINE_IDS, &pipeline_id.namespace_id.0.to_be_bytes());
299        let with_index = Uuid::new_v5(&with_namespace_id, &pipeline_id.index.0.get().to_be_bytes());
300        TreeId(with_index)
301    }
302}
303
304impl From<PipelineId> for u64 {
305    fn from(pipeline_id: PipelineId) -> Self {
306        ((pipeline_id.namespace_id.0 as u64) << 32) + pipeline_id.index.0.get() as u64
307    }
308}
309
310#[cfg(test)]
311#[test]
312fn test_pipeline_id_to_accesskit_tree_id() {
313    let namespace_id = PipelineNamespaceId(1);
314    let index = Index::new(1).expect("Guaranteed by argument");
315    let pipeline_id = PipelineId {
316        namespace_id,
317        index,
318    };
319    assert_eq!(
320        TreeId::from(pipeline_id),
321        TreeId(Uuid::from_u128(0x879211fb_8799_5492_9a31_95a35c05a192))
322    );
323}
324
325impl From<WebRenderPipelineId> for PipelineId {
326    #[expect(unsafe_code)]
327    fn from(pipeline: WebRenderPipelineId) -> Self {
328        let WebRenderPipelineId(namespace_id, index) = pipeline;
329        unsafe {
330            PipelineId {
331                namespace_id: PipelineNamespaceId(namespace_id),
332                index: Index(NonZeroU32::new_unchecked(index), PhantomData),
333            }
334        }
335    }
336}
337
338impl From<PipelineId> for WebRenderPipelineId {
339    fn from(value: PipelineId) -> Self {
340        let PipelineNamespaceId(namespace_id) = value.namespace_id;
341        let Index(index, _) = value.index;
342        WebRenderPipelineId(namespace_id, index.get())
343    }
344}
345
346impl From<&PipelineId> for WebRenderPipelineId {
347    fn from(value: &PipelineId) -> Self {
348        (*value).into()
349    }
350}
351
352namespace_id! {BrowsingContextId, BrowsingContextIndex, "BrowsingContext"}
353
354size_of_test!(BrowsingContextId, 8);
355size_of_test!(Option<BrowsingContextId>, 8);
356
357#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
358pub struct BrowsingContextGroupId(pub u32);
359impl fmt::Display for BrowsingContextGroupId {
360    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361        write!(f, "BrowsingContextGroup{:?}", self)
362    }
363}
364
365impl BrowsingContextId {
366    pub fn from_string(str: &str) -> Option<BrowsingContextId> {
367        let re = Regex::new(r"^BrowsingContext\((\d+),(\d+)\)$").ok()?;
368        let caps = re.captures(str)?;
369        let namespace_id = caps.get(1)?.as_str().parse::<u32>().ok()?;
370        let index = caps.get(2)?.as_str().parse::<u32>().ok()?;
371
372        let result = BrowsingContextId {
373            namespace_id: PipelineNamespaceId(namespace_id),
374            index: Index::new(index).ok()?,
375        };
376        assert_eq!(result.to_string(), str.to_string());
377        Some(result)
378    }
379}
380
381#[derive(
382    Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize,
383)]
384pub struct WebViewId(PainterId, BrowsingContextId);
385
386size_of_test!(WebViewId, 12);
387size_of_test!(Option<WebViewId>, 12);
388
389impl fmt::Display for WebViewId {
390    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391        write!(f, "{}, TopLevel{}", self.0, self.1)
392    }
393}
394
395// BAO note: the previous `impl From<WebViewId> for SpatialTreeItemKey` was
396// removed — webrender 0.70 dropped the SpatialTreeItemKey type entirely and
397// no downstream consumer references the conversion.
398
399impl WebViewId {
400    pub fn new(painter_id: PainterId) -> WebViewId {
401        WebViewId(painter_id, BrowsingContextId::new())
402    }
403
404    pub fn mock_for_testing(browsing_context_id: BrowsingContextId) -> WebViewId {
405        WebViewId(TEST_PAINTER_ID, browsing_context_id)
406    }
407}
408
409impl From<WebViewId> for BrowsingContextId {
410    fn from(id: WebViewId) -> BrowsingContextId {
411        id.1
412    }
413}
414
415impl From<WebViewId> for PainterId {
416    fn from(id: WebViewId) -> PainterId {
417        id.0
418    }
419}
420
421impl PartialEq<WebViewId> for BrowsingContextId {
422    fn eq(&self, rhs: &WebViewId) -> bool {
423        self.eq(&rhs.1)
424    }
425}
426
427impl PartialEq<BrowsingContextId> for WebViewId {
428    fn eq(&self, rhs: &BrowsingContextId) -> bool {
429        self.1.eq(rhs)
430    }
431}
432
433namespace_id! {MessagePortId, MessagePortIndex, "MessagePort"}
434
435namespace_id! {MessagePortRouterId, MessagePortRouterIndex, "MessagePortRouter"}
436
437namespace_id! {BroadcastChannelRouterId, BroadcastChannelRouterIndex, "BroadcastChannelRouter"}
438
439namespace_id! {ServiceWorkerId, ServiceWorkerIndex, "ServiceWorker"}
440
441namespace_id! {ServiceWorkerRegistrationId, ServiceWorkerRegistrationIndex, "ServiceWorkerRegistration"}
442
443namespace_id! {BlobId, BlobIndex, "Blob"}
444
445namespace_id! {FileId, FileIndex, "File"}
446
447namespace_id! {FileListId, FileListIndex, "FileList"}
448
449namespace_id! {DomPointId, DomPointIndex, "DomPoint"}
450
451namespace_id! {DomRectId, DomRectIndex, "DomRect"}
452
453namespace_id! {DomQuadId, DomQuadIndex, "DomQuad"}
454
455namespace_id! {DomMatrixId, DomMatrixIndex, "DomMatrix"}
456
457namespace_id! {DomExceptionId, DomExceptionIndex, "DomException"}
458
459namespace_id! {QuotaExceededErrorId, QuotaExceededErrorIndex, "QuotaExceededError"}
460
461namespace_id! {HistoryStateId, HistoryStateIndex, "HistoryState"}
462
463namespace_id! {ImageBitmapId, ImageBitmapIndex, "ImageBitmap"}
464
465namespace_id! {OffscreenCanvasId, OffscreenCanvasIndex, "OffscreenCanvas"}
466
467namespace_id! {CookieStoreId, CookieStoreIndex, "CookieStore"}
468
469namespace_id! {ImageDataId, ImageDataIndex, "ImageData"}
470
471namespace_id! {CryptoKeyId, CryptoKeyIndex, "CryptoKey"}
472
473// We provide ids just for unit testing.
474pub const TEST_NAMESPACE: PipelineNamespaceId = PipelineNamespaceId(1234);
475pub const TEST_PIPELINE_INDEX: Index<PipelineIndex> =
476    Index(NonZeroU32::new(5678).unwrap(), PhantomData);
477pub const TEST_PIPELINE_ID: PipelineId = PipelineId {
478    namespace_id: TEST_NAMESPACE,
479    index: TEST_PIPELINE_INDEX,
480};
481pub const TEST_BROWSING_CONTEXT_INDEX: Index<BrowsingContextIndex> =
482    Index(NonZeroU32::new(8765).unwrap(), PhantomData);
483pub const TEST_BROWSING_CONTEXT_ID: BrowsingContextId = BrowsingContextId {
484    namespace_id: TEST_NAMESPACE,
485    index: TEST_BROWSING_CONTEXT_INDEX,
486};
487
488pub const TEST_PAINTER_ID: PainterId = PainterId(9999);
489pub const TEST_WEBVIEW_ID: WebViewId = WebViewId(TEST_PAINTER_ID, TEST_BROWSING_CONTEXT_ID);
490pub const TEST_SCRIPT_EVENT_LOOP_ID: ScriptEventLoopId = ScriptEventLoopId(1234);
491
492/// An id for a ScrollTreeNode in the ScrollTree. This contains both the index
493/// to the node in the tree's array of nodes as well as the corresponding SpatialId
494/// for the SpatialNode in the WebRender display list.
495#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
496pub struct ScrollTreeNodeId {
497    /// The index of this scroll tree node in the tree's array of nodes.
498    pub index: usize,
499}
500
501#[derive(MallocSizeOf)]
502pub struct AtomicOptionScrollTreeNodeId(AtomicUsize);
503
504impl AtomicOptionScrollTreeNodeId {
505    pub fn new(option_id: Option<ScrollTreeNodeId>) -> Self {
506        Self(AtomicUsize::new(Self::from_option(option_id)))
507    }
508
509    pub fn set(&self, option_id: Option<ScrollTreeNodeId>) {
510        self.0.store(Self::from_option(option_id), Ordering::Relaxed);
511    }
512
513    fn from_option(option_id: Option<ScrollTreeNodeId>) -> usize {
514        if let Some(ScrollTreeNodeId { index }) = option_id {
515            debug_assert_ne!(index, usize::MAX);
516            index
517        } else {
518            usize::MAX
519        }
520    }
521
522    pub fn get(&self) -> Option<ScrollTreeNodeId> {
523        match self.0.load(Ordering::Relaxed) {
524            usize::MAX => None,
525            index => Some(ScrollTreeNodeId { index }),
526        }
527    }
528}
529
530static PAINTER_ID: AtomicU32 = AtomicU32::new(1);
531
532#[derive(
533    Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Hash, Eq, Serialize, Deserialize, MallocSizeOf,
534)]
535pub struct PainterId(u32);
536
537impl fmt::Display for PainterId {
538    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
539        write!(f, "PainterId: {}", self.0)
540    }
541}
542
543impl PainterId {
544    pub fn next() -> Self {
545        Self(PAINTER_ID.fetch_add(1, Ordering::Relaxed))
546    }
547}
548
549impl From<PainterId> for IdNamespace {
550    fn from(painter_id: PainterId) -> Self {
551        IdNamespace(painter_id.0)
552    }
553}
554
555impl From<IdNamespace> for PainterId {
556    fn from(id_namespace: IdNamespace) -> Self {
557        PainterId(id_namespace.0)
558    }
559}
560
561impl From<FontKey> for PainterId {
562    fn from(font_key: FontKey) -> Self {
563        font_key.0.into()
564    }
565}
566
567impl From<FontInstanceKey> for PainterId {
568    fn from(font_instance_key: FontInstanceKey) -> Self {
569        font_instance_key.0.into()
570    }
571}
572
573impl From<ImageKey> for PainterId {
574    fn from(image_key: ImageKey) -> Self {
575        image_key.0.into()
576    }
577}
578
579static SCRIPT_EVENT_LOOP_ID: AtomicU32 = AtomicU32::new(1);
580thread_local!(pub static INSTALLED_SCRIPT_EVENT_LOOP_ID: Cell<Option<ScriptEventLoopId>> =
581    const { Cell::new(None) });
582
583#[derive(
584    Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Hash, Eq, Serialize, Deserialize, MallocSizeOf,
585)]
586pub struct ScriptEventLoopId(u32);
587
588impl ScriptEventLoopId {
589    pub fn new() -> Self {
590        Self(SCRIPT_EVENT_LOOP_ID.fetch_add(1, Ordering::Relaxed))
591    }
592
593    /// Each script and layout thread should have the [`ScriptEventLoopId`] installed,
594    /// since it is used by crash reporting.
595    pub fn install(id: Self) {
596        INSTALLED_SCRIPT_EVENT_LOOP_ID.with(|tls| tls.set(Some(id)))
597    }
598
599    pub fn installed() -> Option<Self> {
600        INSTALLED_SCRIPT_EVENT_LOOP_ID.with(|tls| tls.get())
601    }
602}
603
604impl fmt::Display for ScriptEventLoopId {
605    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
606        write!(f, "{}", self.0)
607    }
608}