orinium_browser/browser/core/
tab.rs

1//! ブラウザのタブ機能。WebView を保持し、ページのタイトルや URL などのメタ情報を管理する。
2
3use crate::{
4    browser::core::resource_loader::BrowserNetworkError,
5    engine::{html::HtmlNodeType, layouter::types::InfoNode, tree::TreeNode},
6};
7use ui_layout::LayoutNode;
8use url::Url;
9
10pub use super::webview::{FetchKind, WebView, WebViewTask};
11
12pub enum TabTask {
13    Fetch { url: Url, kind: FetchKind },
14    NeedsRedraw,
15}
16
17enum TabError {
18    NetworkError(BrowserNetworkError),
19}
20
21enum TabState {
22    Loading,
23    Loaded,
24    Error(TabError, Option<Url>), // エラーの種類と、失敗した URL(ある場合)
25}
26
27/// Tab はブラウザで開かれた 1 つのページを表す構造体です。
28///
29/// 主な責務:
30/// - 現在表示しているページのタイトルの保持
31/// - ページ内容を扱う WebView の保持
32///
33/// WebView が「ページそのもの」の状態を管理するのに対し、
34/// Tab は UI 上のタブとしてのメタ情報(タイトルなど)を管理します。
35///
36/// TODO:
37/// - ページの状態(Error、loading)の管理を追加
38pub struct Tab {
39    title: Option<String>,
40    base_url: Option<Url>,
41    docment_url: Option<Url>,
42    webview: Option<WebView>,
43    state: TabState,
44}
45
46impl Default for Tab {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl Tab {
53    pub fn new() -> Self {
54        Self {
55            title: None,
56            base_url: None,
57            docment_url: None,
58            webview: None,
59            state: TabState::Loading,
60        }
61    }
62
63    /// Tab 内の状態を 1 ステップ進める
64    ///
65    /// - WebView.tick() を呼び出す
66    /// - 発生した Task を BrowserApp に返す
67    pub fn tick(&mut self) -> Vec<TabTask> {
68        let mut tasks = Vec::new();
69        let Some(wv) = self.webview.as_mut() else {
70            return tasks;
71        };
72
73        for task in wv.tick() {
74            match task {
75                WebViewTask::Fetch { url, kind } => {
76                    log::info!("Fetch requested in Tab: url={}", url);
77                    tasks.push(TabTask::Fetch { url, kind });
78                }
79                WebViewTask::AskTabHtml => {
80                    tasks.push(TabTask::Fetch {
81                        url: self.docment_url.as_ref().unwrap().clone(),
82                        kind: FetchKind::Html,
83                    });
84                }
85            }
86        }
87
88        if wv.needs_redraw() {
89            tasks.push(TabTask::NeedsRedraw);
90        }
91
92        tasks
93    }
94
95    /// BrowserApp から CSS fetch 完了を通知
96    pub fn on_css_fetched(&mut self, css: String) {
97        log::info!("CSS fetched in Tab");
98        if let Some(webview) = self.webview.as_mut() {
99            webview.on_css_fetched(css);
100        }
101    }
102
103    /// BrowserApp からの HTML fetch 完了を通知
104    pub fn on_fetch_succeeded_html(&mut self, html: String) {
105        let Some(wv) = self.webview.as_mut() else {
106            return;
107        };
108
109        wv.on_html_fetched(html, self.docment_url.as_ref().unwrap().clone());
110        self.title = wv.title().cloned();
111        let base_url = wv.base_url().unwrap().clone();
112        log::info!("HTML fetched, base_url={}", base_url);
113        self.base_url = Some(base_url);
114
115        if let TabState::Error(TabError::NetworkError(err), url_opt) = &self.state {
116            let error_message = match url_opt {
117                Some(url) => format!("Failed to load {}: {}", url, err),
118                None => format!("Failed to load page: {}", err),
119            };
120
121            let error_message_element = wv
122                .document_info()
123                .unwrap()
124                .dom
125                .get_elements_by_class_name("error-message");
126            let error_message_element = error_message_element.first().unwrap();
127            let new_child = TreeNode::new(HtmlNodeType::Text(error_message));
128            TreeNode::replace_child(error_message_element, 0, new_child);
129
130            // Update page to show error message
131            // This is a stub implementation for now as you can see in WebView.update_page().
132            wv.update_page();
133        } else {
134            self.state = TabState::Loaded;
135        }
136    }
137
138    pub fn on_fetch_succeeded_css(&mut self, css: String) {
139        let Some(wv) = self.webview.as_mut() else {
140            return;
141        };
142
143        wv.on_css_fetched(css);
144    }
145
146    /// Display error page on fetch failure
147    pub fn on_fetch_failed(&mut self, err: BrowserNetworkError, failed_url: Url) {
148        self.navigate("resource:///error.html".parse().unwrap());
149        self.state = TabState::Error(TabError::NetworkError(err), Some(failed_url));
150    }
151
152    pub fn navigate(&mut self, url: Url) {
153        self.docment_url = Some(url.clone());
154        let mut webview = WebView::new();
155        webview.navigate();
156        self.webview = Some(webview);
157        self.state = TabState::Loading;
158    }
159
160    pub fn move_to(&mut self, href: &str) {
161        let base_url = match self.base_url.as_ref() {
162            Some(u) => u,
163            None => return,
164        };
165
166        let url = super::webview::resolve_url(base_url, href).unwrap();
167
168        // navigate と同じ扱い
169        self.navigate(url)
170    }
171
172    pub fn relayout(&mut self, viewport: (f32, f32)) {
173        if let Some(wv) = self.webview.as_mut() {
174            wv.relayout(viewport);
175        }
176    }
177
178    /// Returns layout_and_info
179    /// Only InfoNode will be mutable.
180    pub fn layout_and_info_mut(&mut self) -> Option<(&LayoutNode, &mut InfoNode)> {
181        self.webview
182            .as_mut()
183            .and_then(|wv| wv.layout_and_info_mut())
184    }
185
186    /// Returns title of the document
187    pub fn title(&self) -> Option<String> {
188        self.title.clone()
189    }
190
191    /// Returns document url
192    pub fn document_url(&self) -> Option<Url> {
193        self.docment_url.clone()
194    }
195
196    pub fn layout_and_info(&self) -> Option<(&LayoutNode, &InfoNode)> {
197        self.webview.as_ref().and_then(|wv| wv.layout_and_info())
198    }
199
200    pub fn needs_redraw(&self) -> bool {
201        self.webview
202            .as_ref()
203            .map(|wv| wv.needs_redraw())
204            .unwrap_or(false)
205    }
206
207    pub fn clear_redraw_flag(&mut self) {
208        if let Some(wv) = self.webview.as_mut() {
209            wv.clear_redraw_flag();
210        }
211    }
212}