Skip to main content

ferroui_browser/storage/
browser_launcher.rs

1use crate::interop::navigation_helper;
2use ferroui_base::input::LocalBoxFuture;
3use ferroui_base::platform::storage::{ILauncher, IStorageItem};
4use ferroui_base::utilities::Uri;
5use std::rc::Rc;
6
7/// The call the launcher makes into the page; replaced by a recorder in the
8/// tests.
9trait ILauncherPage {
10    fn window_open(&self, uri: &str, target: &str) -> bool;
11}
12
13struct LauncherPage;
14
15impl ILauncherPage for LauncherPage {
16    fn window_open(&self, uri: &str, target: &str) -> bool {
17        navigation_helper::window_open(uri, target)
18    }
19}
20
21/// The launcher of the page: opens URIs in a new browsing context. Files
22/// cannot be launched.
23pub struct BrowserLauncher {
24    page: Box<dyn ILauncherPage>,
25}
26
27impl Default for BrowserLauncher {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl BrowserLauncher {
34    /// Creates the launcher.
35    pub fn new() -> Self {
36        Self { page: Box::new(LauncherPage) }
37    }
38}
39
40impl ILauncher for BrowserLauncher {
41    fn launch_uri_async(&self, uri: &Uri) -> LocalBoxFuture<bool> {
42        if uri.is_absolute_uri() {
43            return Box::pin(std::future::ready(self.page.window_open(uri.absolute_uri(), "_blank")));
44        }
45        Box::pin(std::future::ready(false))
46    }
47
48    fn launch_file_async(&self, _storage_item: Rc<dyn IStorageItem>) -> LocalBoxFuture<bool> {
49        Box::pin(std::future::ready(false))
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use ferroui_base::utilities::UriKind;
57    use std::cell::RefCell;
58    use std::task::{Context, Poll, Waker};
59
60    struct RecordingPage {
61        opened: Rc<RefCell<Vec<(String, String)>>>,
62        answer: bool,
63    }
64
65    impl ILauncherPage for RecordingPage {
66        fn window_open(&self, uri: &str, target: &str) -> bool {
67            self.opened.borrow_mut().push((uri.to_string(), target.to_string()));
68            self.answer
69        }
70    }
71
72    fn run(future: LocalBoxFuture<bool>) -> bool {
73        let mut future = future;
74        match future.as_mut().poll(&mut Context::from_waker(Waker::noop())) {
75            Poll::Ready(value) => value,
76            Poll::Pending => panic!("the launcher waits"),
77        }
78    }
79
80    fn launcher(answer: bool) -> (BrowserLauncher, Rc<RefCell<Vec<(String, String)>>>) {
81        let opened = Rc::new(RefCell::new(Vec::new()));
82        (BrowserLauncher { page: Box::new(RecordingPage { opened: opened.clone(), answer }) }, opened)
83    }
84
85    #[test]
86    fn an_absolute_uri_is_opened_in_a_new_browsing_context() {
87        let (launcher, opened) = launcher(true);
88        let uri = Uri::new("https://example.com/a?b=c", UriKind::Absolute).unwrap();
89        assert!(run(launcher.launch_uri_async(&uri)));
90        assert_eq!(vec![(uri.absolute_uri().to_string(), "_blank".to_string())], *opened.borrow());
91    }
92
93    #[test]
94    fn the_answer_is_whether_the_page_opened_a_context() {
95        let (launcher, _) = launcher(false);
96        assert!(!run(launcher.launch_uri_async(&Uri::new("https://example.com", UriKind::Absolute).unwrap())));
97    }
98
99    #[test]
100    fn a_relative_uri_is_not_opened() {
101        let (launcher, opened) = launcher(true);
102        let uri = Uri::new("docs/index.html", UriKind::RelativeOrAbsolute).unwrap();
103        assert!(!run(launcher.launch_uri_async(&uri)));
104        assert!(opened.borrow().is_empty());
105    }
106}