Skip to main content

orinium_browser/browser/core/
resource_loader.rs

1//! Browser resource loading process, supports HTTP and resource:/// schemes.
2
3use crate::platform::network::{NetworkCore, NetworkError, StatusCode};
4use anyhow::{Result, anyhow};
5use std::{fmt, rc::Rc};
6use url::Url;
7
8/// BrowserResourceLoader
9///
10/// High-level resource loading abstraction used by the browser core to obtain
11/// content for tabs and internal resources.
12///
13/// Responsibilities:
14/// - Resolve and fetch resources from `resource:///` scheme (bundled/local) and
15///   from standard HTTP/HTTPS URLs.
16/// - Provide a small synchronous/queuing abstraction over the platform network
17///   core so callers in the engine/browser can request resources without dealing
18///   with the network implementation details.
19///
20/// Processing flow (overview):
21/// 1. Caller requests a URL (either `resource:///...` or `http(s)://...`).
22/// 2. If the URL scheme is `resource`, loader resolves it to a local path or
23///    embedded asset and returns the bytes immediately when available.
24/// 3. For HTTP/HTTPS, loader forwards the request to `NetworkCore` and manages
25///    request ids / pending responses. When the network reply is ready, the
26///    loader hands the response back to the browser/tab via the expected
27///    callback or message path.
28///
29/// Example usage:
30/// ```no_run
31/// use orinium_browser::browser::core::resource_loader::BrowserResourceLoader;
32/// use std::rc::Rc;
33/// use orinium_browser::platform::network::NetworkCore;
34///
35/// let network = Some(Rc::new(NetworkCore::new().unwrap()));
36/// let loader = BrowserResourceLoader::new(network);
37///
38/// // Typical call (pseudocode):
39/// // let body = loader.fetch(&url)?;
40/// // process body...
41/// ```
42///
43/// Notes for contributors:
44/// - Keep the loader focused on scheme resolution, simple caching/pooling,
45///   and delegation to `NetworkCore`. Avoid adding heavy parsing logic here.
46/// - Unit tests should validate `resource:///` resolution and HTTP request
47///   delegation semantics (e.g. mapping of request IDs to responses).
48pub struct BrowserResourceLoader {
49    /// Optional platform network core used for HTTP/HTTPS requests.
50    pub network: Option<Rc<NetworkCore>>,
51
52    /// Immediate pool / internal queue for messages produced by the loader.
53    /// The concrete type `BrowserNetworkMessage` represents internal network
54    /// events; see the network module for details.
55    pub immediate_pool: Vec<BrowserNetworkMessage>,
56}
57
58// NOTE: The actual fetch and handling methods are implemented below in this
59// file. When adding methods, prefer small, testable units:
60// - `resolve_resource_url(&self, url: &Url) -> ResourceLocation`
61// - `fetch_http(&self, url: Url) -> Result<Vec<u8>>`
62// - `fetch_resource_scheme(&self, url: Url) -> Result<Vec<u8>>`
63//
64// Keep the public API ergonomic for the engine (sync or async facade as
65// appropriate for how NetworkCore exposes requests).
66
67impl BrowserResourceLoader {
68    /// Construct a new resource loader.
69    ///
70    /// `network` is optional to allow operating in environments where the
71    /// network stack is not available (tests, limited examples, or when only
72    /// `resource:///` is needed).
73    pub fn new(network: Option<Rc<NetworkCore>>) -> Self {
74        Self {
75            network,
76            immediate_pool: vec![],
77        }
78    }
79
80    /// 非同期 fetch: URL と ID を送信するだけ
81    pub fn fetch_async(&mut self, url: Url, id: usize) {
82        if url.scheme() == ("resource") {
83            let data = ResourceURI::load(url.as_ref());
84            let msg = BrowserNetworkMessage {
85                id,
86                response: data
87                    .map(|data| BrowserResponse {
88                        url: url.to_string(),
89                        status: hyper::StatusCode::OK.into(),
90                        body: data,
91                        headers: vec![],
92                    })
93                    .map_err(BrowserNetworkError::AnyhowError),
94            };
95            self.immediate_pool.push(msg);
96        } else if let Some(net) = &self.network {
97            net.fetch_async(url.to_string(), id);
98        }
99    }
100
101    pub fn fetch_blocking(&self, url: Url) -> Result<BrowserResponse> {
102        if url.scheme() == ("resource") {
103            let data = ResourceURI::load(url.as_ref());
104            data.map(|data| BrowserResponse {
105                url: url.to_string(),
106                status: hyper::StatusCode::OK.into(),
107                body: data,
108                headers: vec![],
109            })
110        } else if let Some(net) = &self.network {
111            net.fetch_blocking(url.as_str())
112                .map(|resp| BrowserResponse {
113                    url: resp.url,
114                    status: resp.status,
115                    body: resp.body,
116                    headers: resp.headers,
117                })
118                .map_err(|e| anyhow!("NetworkError: {}", e))
119        } else {
120            Err(anyhow!("NetworkCore not available"))
121        }
122    }
123
124    /// UIスレッドから呼ぶ: 受信済みネットワーク結果を取り込む
125    pub fn try_receive(&mut self) -> Vec<BrowserNetworkMessage> {
126        let mut msgs = if let Some(net) = &self.network {
127            net.try_receive()
128                .into_iter()
129                .map(|msg| BrowserNetworkMessage {
130                    id: msg.msg_id,
131                    response: msg
132                        .response
133                        .map(|resp| BrowserResponse {
134                            url: resp.url,
135                            status: resp.status,
136                            body: resp.body,
137                            headers: resp.headers,
138                        })
139                        .map_err(BrowserNetworkError::NetworkError),
140                })
141                .collect()
142        } else {
143            Vec::new()
144        };
145        msgs.extend(std::mem::take(&mut self.immediate_pool));
146
147        msgs
148    }
149}
150
151/// 統一レスポンス
152pub struct BrowserResponse {
153    pub url: String,
154    pub status: StatusCode,
155    pub body: Vec<u8>,
156    pub headers: Vec<(String, String)>,
157}
158
159/// ネットワーク結果を UI スレッドで受け取るためのラッパー
160pub struct BrowserNetworkMessage {
161    pub id: usize,
162    pub response: Result<BrowserResponse, BrowserNetworkError>,
163}
164
165#[derive(Debug)]
166pub enum BrowserNetworkError {
167    NetworkError(NetworkError),
168    AnyhowError(anyhow::Error),
169}
170
171impl fmt::Display for BrowserNetworkError {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        match self {
174            Self::NetworkError(ne) => write!(f, "{ne}"),
175            Self::AnyhowError(ae) => write!(f, "{ae}"),
176        }
177    }
178}
179
180/// resource:/// 専用
181pub struct ResourceURI;
182
183impl ResourceURI {
184    pub fn load(url: &str) -> Result<Vec<u8>, anyhow::Error> {
185        use crate::platform::io;
186        if let Some(path) = url.strip_prefix("resource:///") {
187            io::load_resource(path)
188        } else {
189            Err(anyhow!("Unsupported scheme: {}", url))
190        }
191    }
192}