Skip to main content

script/event_loop/
document_loader.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//! Tracking of pending loads in a document.
6//!
7//! <https://html.spec.whatwg.org/multipage/#the-end>
8
9use std::collections::HashMap;
10
11use net_traits::ResourceThreads;
12use script_bindings::cell::DomRefCell;
13use script_bindings::script_runtime::{during_gc_collection, runtime_is_alive};
14use servo_url::ServoUrl;
15
16use crate::dom::bindings::root::Dom;
17use crate::dom::document::Document;
18
19#[derive(Clone, Debug, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
20pub(crate) enum LoadType {
21    Image(#[no_trace] ServoUrl),
22    Script(#[no_trace] ServoUrl),
23    Subframe(#[no_trace] ServoUrl),
24    Stylesheet(#[no_trace] ServoUrl),
25    PageSource(#[no_trace] ServoUrl),
26    Media,
27}
28
29/// Canary value ensuring that manually added blocking loads are always removed by the
30/// time that the owner is destroyed.
31#[derive(JSTraceable, MallocSizeOf)]
32#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
33pub(crate) struct LoadBlocker {
34    /// The document whose load event is blocked by this object existing.
35    doc: Dom<Document>,
36    /// The load that is blocking the document's load event.
37    load: Option<LoadType>,
38}
39
40impl LoadBlocker {
41    /// Mark the document's load event as blocked on this new load.
42    pub(crate) fn new(doc: &Document, load: LoadType) -> LoadBlocker {
43        doc.loader_mut().add_blocking_load(load.clone());
44        LoadBlocker {
45            doc: Dom::from_ref(doc),
46            load: Some(load),
47        }
48    }
49
50    /// Remove this load from the associated document's list of blocking loads.
51    pub(crate) fn terminate(
52        blocker: &DomRefCell<Option<LoadBlocker>>,
53        cx: &mut js::context::JSContext,
54    ) {
55        let Some(load) = blocker
56            .safe_borrow_mut(cx.no_gc())
57            .as_mut()
58            .and_then(|blocker| blocker.load.take())
59        else {
60            return;
61        };
62
63        if let Some(blocker) = blocker.borrow().as_ref() {
64            blocker.doc.finish_load(load, cx);
65        }
66
67        *blocker.safe_borrow_mut(cx.no_gc()) = None;
68    }
69}
70
71impl Drop for LoadBlocker {
72    fn drop(&mut self) {
73        // We need to check here if the whole runtime is alive, otherwise
74        // we interact with a document that is also being dropped. That
75        // would panic, since we should no longer schedule a task for
76        // a dropped runtime. Therefore, we should only run the drop logic
77        // in case this element is dropped, but its containing document
78        // is still alive.
79        //
80        // This destructor can also run from during GC (see #46207), which can lead to
81        // accessing already freed memory if we run `finish_load_for_dropped_blocker`.
82        if runtime_is_alive() &&
83            !during_gc_collection() &&
84            let Some(load) = self.load.take()
85        {
86            self.doc.finish_load_for_dropped_blocker(load);
87        }
88    }
89}
90
91#[derive(JSTraceable, MallocSizeOf)]
92pub(crate) struct DocumentLoader {
93    #[no_trace]
94    resource_threads: ResourceThreads,
95    /// A map from [`LoadType`] to the number of blocking loads. When a particular [`LoadType`]
96    /// reaches zero, it is removed from the map and no longer blocks the load.
97    blocking_loads: HashMap<LoadType, u32>,
98    events_inhibited: bool,
99}
100
101impl DocumentLoader {
102    pub(crate) fn new(existing: &DocumentLoader) -> DocumentLoader {
103        DocumentLoader::new_with_threads(existing.resource_threads.clone(), None)
104    }
105
106    pub(crate) fn new_with_threads(
107        resource_threads: ResourceThreads,
108        initial_load: Option<ServoUrl>,
109    ) -> DocumentLoader {
110        debug!("Initial blocking load {:?}.", initial_load);
111
112        let initial_loads = initial_load
113            .into_iter()
114            .map(|url| (LoadType::PageSource(url), 1))
115            .collect();
116
117        DocumentLoader {
118            resource_threads,
119            blocking_loads: initial_loads,
120            events_inhibited: false,
121        }
122    }
123
124    /// Add a load to the list of blocking loads.
125    pub(crate) fn add_blocking_load(&mut self, load: LoadType) {
126        debug!(
127            "Adding blocking load {:?} ({}).",
128            load,
129            self.blocking_loads.len()
130        );
131        self.blocking_loads
132            .entry(load)
133            .and_modify(|load_number| *load_number += 1)
134            .or_insert(1);
135    }
136
137    /// Mark an in-progress network request complete.
138    pub(crate) fn finish_load(&mut self, load: &LoadType) {
139        debug!(
140            "Removing blocking load {:?} ({}).",
141            load,
142            self.blocking_loads.len()
143        );
144
145        let Some(entry) = self.blocking_loads.get_mut(load) else {
146            warn!("unknown completed load {load:?}");
147            return;
148        };
149
150        *entry = entry.saturating_sub(1);
151        if *entry == 0 {
152            self.blocking_loads.remove(load);
153        }
154    }
155
156    pub(crate) fn is_blocked(&self) -> bool {
157        // TODO: Ensure that we report blocked if parsing is still ongoing.
158        !self.blocking_loads.is_empty()
159    }
160
161    pub(crate) fn is_only_blocked_by_iframes(&self) -> bool {
162        self.blocking_loads
163            .keys()
164            .all(|load| matches!(*load, LoadType::Subframe(_)))
165    }
166
167    pub(crate) fn inhibit_events(&mut self) {
168        self.events_inhibited = true;
169    }
170
171    pub(crate) fn events_inhibited(&self) -> bool {
172        self.events_inhibited
173    }
174
175    pub(crate) fn resource_threads(&self) -> &ResourceThreads {
176        &self.resource_threads
177    }
178}