script/
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 net_traits::request::RequestBuilder;
10use net_traits::{BoxedFetchCallback, ResourceThreads, fetch_async};
11use script_bindings::script_runtime::temp_cx;
12use servo_url::ServoUrl;
13
14use crate::dom::bindings::cell::DomRefCell;
15use crate::dom::bindings::root::Dom;
16use crate::dom::document::Document;
17use crate::fetch::FetchCanceller;
18
19#[derive(Clone, Debug, 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 (ie. ones that weren't
30/// created via DocumentLoader::fetch_async) are always removed by the time
31/// that the owner is destroyed.
32#[derive(JSTraceable, MallocSizeOf)]
33#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
34pub(crate) struct LoadBlocker {
35    /// The document whose load event is blocked by this object existing.
36    doc: Dom<Document>,
37    /// The load that is blocking the document's load event.
38    load: Option<LoadType>,
39}
40
41impl LoadBlocker {
42    /// Mark the document's load event as blocked on this new load.
43    pub(crate) fn new(doc: &Document, load: LoadType) -> LoadBlocker {
44        doc.loader_mut().add_blocking_load(load.clone());
45        LoadBlocker {
46            doc: Dom::from_ref(doc),
47            load: Some(load),
48        }
49    }
50
51    /// Remove this load from the associated document's list of blocking loads.
52    pub(crate) fn terminate(
53        blocker: &DomRefCell<Option<LoadBlocker>>,
54        cx: &mut js::context::JSContext,
55    ) {
56        let Some(load) = blocker
57            .borrow_mut()
58            .as_mut()
59            .and_then(|blocker| blocker.load.take())
60        else {
61            return;
62        };
63
64        if let Some(blocker) = blocker.borrow().as_ref() {
65            blocker.doc.finish_load(load, cx);
66        }
67
68        *blocker.borrow_mut() = None;
69    }
70}
71
72impl Drop for LoadBlocker {
73    #[expect(unsafe_code)]
74    fn drop(&mut self) {
75        if let Some(load) = self.load.take() {
76            let mut cx = unsafe { temp_cx() };
77            self.doc.finish_load(load, &mut cx);
78        }
79    }
80}
81
82#[derive(JSTraceable, MallocSizeOf)]
83pub(crate) struct DocumentLoader {
84    #[no_trace]
85    resource_threads: ResourceThreads,
86    blocking_loads: Vec<LoadType>,
87    events_inhibited: bool,
88    cancellers: Vec<FetchCanceller>,
89}
90
91impl DocumentLoader {
92    pub(crate) fn new(existing: &DocumentLoader) -> DocumentLoader {
93        DocumentLoader::new_with_threads(existing.resource_threads.clone(), None)
94    }
95
96    pub(crate) fn new_with_threads(
97        resource_threads: ResourceThreads,
98        initial_load: Option<ServoUrl>,
99    ) -> DocumentLoader {
100        debug!("Initial blocking load {:?}.", initial_load);
101        let initial_loads = initial_load.into_iter().map(LoadType::PageSource).collect();
102
103        DocumentLoader {
104            resource_threads,
105            blocking_loads: initial_loads,
106            events_inhibited: false,
107            cancellers: Vec::new(),
108        }
109    }
110
111    /// <https://fetch.spec.whatwg.org/#concept-fetch-group-terminate>
112    pub(crate) fn cancel_all_loads(&mut self) -> Vec<FetchCanceller> {
113        self.cancellers.drain(..).collect()
114    }
115
116    /// Add a load to the list of blocking loads.
117    fn add_blocking_load(&mut self, load: LoadType) {
118        debug!(
119            "Adding blocking load {:?} ({}).",
120            load,
121            self.blocking_loads.len()
122        );
123        self.blocking_loads.push(load);
124    }
125
126    /// Initiate a new fetch given a response callback.
127    pub(crate) fn fetch_async_with_callback(
128        &mut self,
129        load: LoadType,
130        request: RequestBuilder,
131        callback: BoxedFetchCallback,
132    ) {
133        self.add_blocking_load(load);
134        self.fetch_async_background(request, callback);
135    }
136
137    /// Initiate a new fetch that does not block the document load event.
138    pub(crate) fn fetch_async_background(
139        &mut self,
140        request: RequestBuilder,
141        callback: BoxedFetchCallback,
142    ) {
143        self.cancellers.push(FetchCanceller::new(
144            request.id,
145            request.keep_alive,
146            self.resource_threads.core_thread.clone(),
147        ));
148        fetch_async(&self.resource_threads.core_thread, request, None, callback);
149    }
150
151    /// Mark an in-progress network request complete.
152    pub(crate) fn finish_load(&mut self, load: &LoadType) {
153        debug!(
154            "Removing blocking load {:?} ({}).",
155            load,
156            self.blocking_loads.len()
157        );
158        let idx = self
159            .blocking_loads
160            .iter()
161            .position(|unfinished| *unfinished == *load);
162        match idx {
163            Some(i) => {
164                self.blocking_loads.remove(i);
165            },
166            None => warn!("unknown completed load {:?}", load),
167        }
168    }
169
170    pub(crate) fn is_blocked(&self) -> bool {
171        // TODO: Ensure that we report blocked if parsing is still ongoing.
172        !self.blocking_loads.is_empty()
173    }
174
175    pub(crate) fn is_only_blocked_by_iframes(&self) -> bool {
176        self.blocking_loads
177            .iter()
178            .all(|load| matches!(*load, LoadType::Subframe(_)))
179    }
180
181    pub(crate) fn inhibit_events(&mut self) {
182        self.events_inhibited = true;
183    }
184
185    pub(crate) fn events_inhibited(&self) -> bool {
186        self.events_inhibited
187    }
188
189    pub(crate) fn resource_threads(&self) -> &ResourceThreads {
190        &self.resource_threads
191    }
192}