Skip to main content

blitz_traits/
net.rs

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