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