ferroui_browser/storage/
browser_launcher.rs1use 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
7trait 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
21pub 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 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}