orinium_browser/browser/core/webview/
mod.rs1use crate::engine::{
4 css::parser::Parser as CssParser,
5 html::parser::{DomTree, Parser as HtmlParser},
6 layouter::{
7 self,
8 types::{InfoNode, TextStyle},
9 },
10};
11use crate::platform::renderer::text_measurer::PlatformTextMeasurer;
12use ui_layout::LayoutNode;
13use url::Url;
14
15const USER_AGENT_CSS: &str = include_str!("../../../../resource/user-agent.css");
16
17pub enum WebViewTask {
18 AskTabHtml,
19 Fetch { url: Url, kind: FetchKind },
20}
21
22pub enum FetchKind {
28 Html,
29 Css,
30}
31
32#[derive(Debug, PartialEq)]
33enum PagePhase {
34 Init,
35 BeforeHtmlParsing,
36 HtmlParsed,
37 CssPending,
38 CssApplied,
39}
40
41pub struct WebView {
42 phase: PagePhase,
43
44 docment_info: Option<DocumentInfo>,
45
46 pending_css_urls: Vec<Url>,
47 loaded_css: Vec<String>,
48
49 resolved_styles: layouter::css_resolver::ResolvedStyles,
50 layout_and_info: Option<(LayoutNode, InfoNode)>,
51
52 needs_redraw: bool,
53}
54
55pub struct DocumentInfo {
63 document_url: Url,
64 base_url: Url,
65 title: String,
66 pub dom: DomTree,
67}
68
69struct ParsedDocument {
79 document_url: Url,
80 base_url: Url,
81 dom: DomTree,
82 title: String,
83 style_links: Vec<Url>,
84 inline_styles: Vec<String>,
85}
86
87impl Default for WebView {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93impl WebView {
94 pub fn new() -> Self {
95 Self {
96 phase: PagePhase::Init,
97
98 docment_info: None,
99
100 pending_css_urls: Vec::new(),
101 loaded_css: Vec::new(),
102
103 resolved_styles: layouter::css_resolver::ResolvedStyles::default(),
104 layout_and_info: None,
105
106 needs_redraw: false,
107 }
108 }
109
110 pub fn tick(&mut self) -> Vec<WebViewTask> {
111 let mut tasks = Vec::new();
112
113 match self.phase {
114 PagePhase::Init => {
115 self.resolved_styles
116 .extend(layouter::css_resolver::CssResolver::resolve(
117 &CssParser::new(USER_AGENT_CSS).parse().unwrap(),
118 ));
119
120 tasks.push(WebViewTask::AskTabHtml);
121
122 self.phase = PagePhase::BeforeHtmlParsing;
123 }
124
125 PagePhase::BeforeHtmlParsing => {}
126
127 PagePhase::HtmlParsed => {
128 let measurer = PlatformTextMeasurer::new().unwrap();
130
131 self.update_layout_and_info(measurer);
132
133 for url in &self.pending_css_urls {
135 log::info!("Fetch requested in WebView: url={}", url);
136 tasks.push(WebViewTask::Fetch {
137 url: url.clone(),
138 kind: FetchKind::Css,
139 });
140 }
141
142 self.phase = PagePhase::CssPending;
143 }
144
145 PagePhase::CssPending => {
146 }
148
149 PagePhase::CssApplied => {
150 }
152 }
153
154 tasks
155 }
156
157 pub fn on_html_fetched(&mut self, html: String, document_url: Url) {
158 log::info!("Fetched HTML: {}", document_url);
159 let parsed = parse_html(&html, document_url);
160
161 self.pending_css_urls = parsed.style_links;
162
163 let docment_info = DocumentInfo {
164 document_url: parsed.document_url,
165 base_url: parsed.base_url,
166 dom: parsed.dom,
167 title: parsed.title,
168 };
169 self.docment_info = Some(docment_info);
170
171 self.resolved_styles
172 .extend(resolve_all_css(&parsed.inline_styles));
173
174 self.phase = PagePhase::HtmlParsed;
175 }
176
177 pub fn on_css_fetched(&mut self, css: String) {
178 self.loaded_css.push(css);
179
180 if self.loaded_css.len() == self.pending_css_urls.len() {
181 print!("Apply");
182 self.apply_css_and_relayout();
183 self.phase = PagePhase::CssApplied;
184 self.needs_redraw = true;
185 }
186 }
187
188 pub fn update_page(&mut self) {
192 let measurer = PlatformTextMeasurer::new().unwrap();
193
194 self.update_layout_and_info(measurer);
195 }
196
197 fn apply_css_and_relayout(&mut self) {
198 self.resolved_styles
199 .extend(resolve_all_css(&self.loaded_css));
200
201 let measurer = PlatformTextMeasurer::new().unwrap();
202
203 self.update_layout_and_info(measurer);
204 }
205
206 fn update_layout_and_info(&mut self, measurer: PlatformTextMeasurer) {
207 self.layout_and_info = Some(layouter::build_layout_and_info(
208 &self.docment_info.as_ref().unwrap().dom.root,
209 &self.resolved_styles,
210 &measurer,
211 TextStyle {
212 font_size: 16.0,
213 ..Default::default()
214 },
215 Vec::new(),
216 ));
217 self.needs_redraw = true;
218 }
219
220 pub fn navigate(&mut self) {
221 self.reset_for_navigation();
222 }
223
224 fn reset_for_navigation(&mut self) {
225 if self.phase != PagePhase::Init {
226 self.phase = PagePhase::BeforeHtmlParsing;
227 }
228
229 self.docment_info = None;
230 self.pending_css_urls.clear();
231 self.loaded_css.clear();
232 self.resolved_styles.clear();
233 self.layout_and_info = None;
234
235 self.needs_redraw = false;
236 }
237
238 pub fn title(&self) -> Option<&String> {
239 self.docment_info.as_ref().map(|d| &d.title)
240 }
241
242 pub fn relayout(&mut self, viewport: (f32, f32)) {
243 let Some((layout, _info)) = self.layout_and_info.as_mut() else {
244 return;
245 };
246
247 ui_layout::LayoutEngine::layout(layout, viewport.0, viewport.1);
248 }
249
250 pub fn layout_and_info(&self) -> Option<(&LayoutNode, &InfoNode)> {
252 self.layout_and_info.as_ref().map(|(l, i)| (l, i))
253 }
254
255 pub fn layout_and_info_mut(&mut self) -> Option<(&LayoutNode, &mut InfoNode)> {
256 self.layout_and_info.as_mut().map(|(l, i)| (&*l, i))
257 }
258
259 pub fn document_info(&self) -> Option<&DocumentInfo> {
261 self.docment_info.as_ref()
262 }
263
264 pub fn document_url(&self) -> Option<&Url> {
265 self.docment_info.as_ref().map(|info| &info.document_url)
266 }
267
268 pub fn base_url(&self) -> Option<&Url> {
269 self.docment_info.as_ref().map(|info| &info.base_url)
270 }
271
272 pub fn needs_redraw(&self) -> bool {
273 self.needs_redraw
274 }
275
276 pub fn clear_redraw_flag(&mut self) {
277 self.needs_redraw = false;
278 }
279}
280
281fn parse_html(html: &str, document_url: Url) -> ParsedDocument {
282 let mut parser = HtmlParser::new(html);
284 let dom = parser.parse();
285
286 let base_url = dom
288 .find_all(|n| n.tag_name() == Some("base"))
289 .iter()
290 .filter_map(|node_ref| {
291 let html_node = &node_ref.borrow().value;
292 let href = html_node.get_attr("href")?;
293 document_url.join(href).ok()
294 })
295 .next()
296 .unwrap_or_else(|| document_url.clone());
297
298 let title = dom
300 .collect_text_by_tag("title")
301 .first()
302 .cloned()
303 .unwrap_or("".into());
304
305 let link_nodes = dom.find_all(|n| n.tag_name() == Some("link"));
308 let mut style_links = Vec::new();
309
310 for node in link_nodes {
311 let (rel, href) = {
312 let node_ref = node.borrow();
313 let html_node = &node_ref.value;
314
315 let rel = html_node.get_attr("rel").map(|s| s.to_string());
316 let href = html_node.get_attr("href").map(|s| s.to_string());
317 (rel, href)
318 };
319
320 if let (Some(rel), Some(href)) = (rel, href)
321 && rel == "stylesheet"
322 {
323 let css_url = match resolve_url(&base_url, &href) {
324 Ok(url) => url,
325 Err(_) => continue,
326 };
327 style_links.push(css_url);
328 }
329 }
330
331 let inline_styles = dom.collect_text_by_tag("style");
333
334 ParsedDocument {
335 document_url,
336 base_url,
337 dom,
338 title,
339 style_links,
340 inline_styles,
341 }
342}
343
344fn resolve_all_css(css_sources: &[String]) -> layouter::css_resolver::ResolvedStyles {
345 let mut resolved = layouter::css_resolver::ResolvedStyles::default();
346
347 for css in css_sources {
348 let sheet = match CssParser::new(css).parse() {
349 Ok(sheet) => sheet,
350 Err(err) => {
351 log::error!("Failed to parse CSS: {}", err);
352 continue;
353 }
354 };
355
356 resolved.extend(layouter::css_resolver::CssResolver::resolve(&sheet));
357 }
358
359 resolved
360}
361
362pub fn resolve_url(base_url: &Url, path: &str) -> Result<Url, url::ParseError> {
363 if let Ok(url) = Url::parse(path) {
365 return Ok(url);
366 }
367
368 base_url.join(path)
370}