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}