Skip to main content

blitz_traits/
net.rs

1//! Abstractions of networking so that custom networking implementations can be provided
2
3pub use bytes::Bytes;
4pub use http::{self, HeaderMap, HeaderValue, Method};
5use serde::{
6    Serialize,
7    ser::{SerializeSeq, SerializeTuple},
8};
9use std::sync::{Arc, Mutex};
10use std::{ops::Deref, path::PathBuf};
11pub use url::Url;
12
13/// A cookie jar used by network providers to supply request cookies and store
14/// cookies received in response headers.
15pub trait CookieJar: Send + Sync + 'static {
16    /// Returns the `Cookie` header to send with a request to `url`, if present.
17    fn cookies_header(&self, url: &Url) -> Option<HeaderValue>;
18
19    /// Stores cookies from the response headers received from `url`.
20    fn store_from_response(&self, url: &Url, headers: &HeaderMap);
21}
22
23/// A type that fetches resources for a Document.
24///
25/// This may be over the network via http(s), via the filesystem, or some other method.
26pub trait NetProvider: Send + Sync + 'static {
27    fn fetch(&self, doc_id: usize, request: Request, handler: Box<dyn NetHandler>);
28
29    /// Whether this provider is a no-op (e.g. `DummyNetProvider`) that will never
30    /// deliver resources. When true, callers must NOT register resources as
31    /// "pending critical" — doing so blocks painting forever, since the
32    /// completion callback never fires. Used by integrations that feed a
33    /// pre-rendered DOM and perform no sub-fetches (e.g. aginxbrowser).
34    fn is_noop(&self) -> bool {
35        false
36    }
37}
38
39/// A type that parses raw bytes from a network request into a Data and then calls
40/// the NetCallack with the result.
41pub trait NetHandler: Send + Sync + 'static {
42    fn bytes(self: Box<Self>, resolved_url: String, bytes: Bytes);
43}
44
45/// A callback which gets called every time a network request completes
46// Q: Should we use std::task::Waker for this?
47pub trait NetWaker: Send + Sync + 'static {
48    fn wake(&self, client_id: usize);
49}
50
51impl<F: Fn(usize) + Send + Sync + 'static> NetWaker for F {
52    fn wake(&self, doc_id: usize) {
53        self(doc_id)
54    }
55}
56
57/// The destination of a request, as a Fetch metadata token.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
59pub enum RequestDestination {
60    #[default]
61    Empty,
62    Document,
63    Iframe,
64    Style,
65    Script,
66    Image,
67    Font,
68    Audio,
69    Video,
70    Track,
71    Manifest,
72}
73
74impl RequestDestination {
75    pub fn as_str(self) -> &'static str {
76        match self {
77            Self::Empty => "empty",
78            Self::Document => "document",
79            Self::Iframe => "iframe",
80            Self::Style => "style",
81            Self::Script => "script",
82            Self::Image => "image",
83            Self::Font => "font",
84            Self::Audio => "audio",
85            Self::Video => "video",
86            Self::Track => "track",
87            Self::Manifest => "manifest",
88        }
89    }
90}
91
92#[non_exhaustive]
93#[derive(Debug, Clone)]
94/// A request type loosely representing <https://fetch.spec.whatwg.org/#requests>
95pub struct Request {
96    pub url: Url,
97    pub method: Method,
98    pub content_type: Option<String>,
99    pub headers: HeaderMap,
100    pub body: Body,
101    pub signal: Option<AbortSignal>,
102    pub destination: RequestDestination,
103}
104impl Request {
105    /// A get request to the specified Url and an empty body
106    pub fn get(url: Url) -> Self {
107        Self {
108            url,
109            method: Method::GET,
110            content_type: None,
111            headers: HeaderMap::new(),
112            body: Body::Empty,
113            signal: None,
114            destination: RequestDestination::default(),
115        }
116    }
117
118    pub fn destination(mut self, d: RequestDestination) -> Self {
119        self.destination = d;
120        self
121    }
122
123    pub fn signal(mut self, signal: AbortSignal) -> Self {
124        self.signal = Some(signal);
125        self
126    }
127}
128
129#[derive(Debug, Clone)]
130pub enum Body {
131    Bytes(Bytes),
132    Form(FormData),
133    Empty,
134}
135
136/// A list of form entries used for form submission
137#[derive(Debug, Clone, PartialEq, Default)]
138pub struct FormData(pub Vec<Entry>);
139impl FormData {
140    /// Creates a new empty FormData
141    pub fn new() -> Self {
142        FormData(Vec::new())
143    }
144}
145impl Serialize for FormData {
146    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
147    where
148        S: serde::Serializer,
149    {
150        let mut seq_serializer = serializer.serialize_seq(Some(self.len()))?;
151        for entry in &self.0 {
152            seq_serializer.serialize_element(entry)?;
153        }
154        seq_serializer.end()
155    }
156}
157impl Deref for FormData {
158    type Target = Vec<Entry>;
159
160    fn deref(&self) -> &Self::Target {
161        &self.0
162    }
163}
164
165/// A single form entry consisting of a name and value
166#[derive(Debug, Clone, PartialEq)]
167pub struct Entry {
168    pub name: String,
169    pub value: EntryValue,
170}
171impl Serialize for Entry {
172    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
173    where
174        S: serde::Serializer,
175    {
176        let mut serializer = serializer.serialize_tuple(2)?;
177        serializer.serialize_element(&self.name)?;
178        match &self.value {
179            EntryValue::String(s) => serializer.serialize_element(s)?,
180            EntryValue::File(p) => serializer.serialize_element(p.to_str().unwrap_or_default())?,
181            EntryValue::EmptyFile => serializer.serialize_element("")?,
182        }
183        serializer.end()
184    }
185}
186
187#[derive(Debug, Clone, PartialEq)]
188pub enum EntryValue {
189    String(String),
190    File(PathBuf),
191    EmptyFile,
192}
193impl AsRef<str> for EntryValue {
194    fn as_ref(&self) -> &str {
195        match self {
196            EntryValue::String(s) => s,
197            EntryValue::File(p) => p.to_str().unwrap_or_default(),
198            EntryValue::EmptyFile => "",
199        }
200    }
201}
202
203impl From<&str> for EntryValue {
204    fn from(value: &str) -> Self {
205        EntryValue::String(value.to_string())
206    }
207}
208impl From<PathBuf> for EntryValue {
209    fn from(value: PathBuf) -> Self {
210        EntryValue::File(value)
211    }
212}
213
214/// A default noop NetProvider
215#[derive(Default)]
216pub struct DummyNetProvider;
217impl NetProvider for DummyNetProvider {
218    fn fetch(&self, _doc_id: usize, _request: Request, _handler: Box<dyn NetHandler>) {}
219    fn is_noop(&self) -> bool {
220        true
221    }
222}
223
224/// The AbortController interface represents a controller object that
225/// allows you to abort one or more Web requests as and when desired via
226/// `AbortController`.
227///
228/// <https://developer.mozilla.org/en-US/docs/Web/API/AbortController>
229#[derive(Debug, Default)]
230pub struct AbortController {
231    pub signal: AbortSignal,
232}
233
234impl AbortController {
235    /// The abort() method of the AbortController interface aborts
236    /// an asynchronous operation before it has completed.
237    /// This is able to abort fetch requests.
238    ///
239    /// <https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort>
240    pub fn abort(self) {
241        let waiters = {
242            let mut state = self
243                .signal
244                .0
245                .lock()
246                .unwrap_or_else(|error| error.into_inner());
247            state.aborted = true;
248            std::mem::take(&mut state.waiters)
249        };
250        for (_, waker) in waiters {
251            waker.wake();
252        }
253    }
254}
255
256/// The AbortSignal interface represents a signal object that allows you to
257/// communicate that an operation should be aborted via `AbortController`.
258///
259/// <https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal>
260#[derive(Debug, Default, Clone)]
261pub struct AbortSignal(Arc<Mutex<AbortState>>);
262
263#[derive(Debug, Default)]
264struct AbortState {
265    aborted: bool,
266    next: u64,
267    waiters: std::collections::BTreeMap<u64, std::task::Waker>,
268}
269
270/// A reactive cancellation future, with one registration per operation.
271///
272/// Dropping this future unregisters its waker. It does not abort the signal.
273pub struct AbortWaiter {
274    signal: AbortSignal,
275    id: u64,
276}
277
278impl std::future::Future for AbortWaiter {
279    type Output = ();
280
281    fn poll(
282        self: std::pin::Pin<&mut Self>,
283        cx: &mut std::task::Context<'_>,
284    ) -> std::task::Poll<()> {
285        let mut state = self
286            .signal
287            .0
288            .lock()
289            .unwrap_or_else(|error| error.into_inner());
290        if state.aborted {
291            std::task::Poll::Ready(())
292        } else {
293            state.waiters.insert(self.id, cx.waker().clone());
294            std::task::Poll::Pending
295        }
296    }
297}
298
299impl Drop for AbortWaiter {
300    fn drop(&mut self) {
301        self.signal
302            .0
303            .lock()
304            .unwrap_or_else(|error| error.into_inner())
305            .waiters
306            .remove(&self.id);
307    }
308}
309
310impl AbortSignal {
311    /// The aborted read-only property returns a value that indicates whether
312    /// the asynchronous operations the signal is communicating with has been aborted.
313    pub fn aborted(&self) -> bool {
314        self.0
315            .lock()
316            .unwrap_or_else(|error| error.into_inner())
317            .aborted
318    }
319
320    /// Completes when the issuer aborts, waking every pending waiter.
321    ///
322    /// A signal that is already aborted completes on the first poll. A
323    /// signal can be shared by requests and WebSocket upgrade attempts.
324    pub fn cancelled(&self) -> AbortWaiter {
325        let mut state = self.0.lock().unwrap_or_else(|error| error.into_inner());
326        let id = state.next;
327        state.next = state
328            .next
329            .checked_add(1)
330            .expect("abort waiter identifier exhausted");
331        AbortWaiter {
332            signal: self.clone(),
333            id,
334        }
335    }
336}