1use crate::browser_runtime_platform::BrowserRuntimePlatformServices;
2use crate::browser_single_view_lifetime::BrowserSingleViewLifetime;
3use crate::ferro_view::FerroView;
4use crate::interop::dom_helper;
5use crate::windowing_platform::BrowserWindowingPlatform;
6use ferroui_base::{FerroLocator, LocatorExtensions};
7use ferroui_controls::application_lifetimes::IApplicationLifetime;
8use ferroui_controls::AppBuilder;
9use ferroui_harfbuzz::HarfBuzzApplicationExtensions;
10use ferroui_skia::SkiaApplicationExtensions;
11use std::rc::Rc;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15#[repr(i32)]
16pub enum BrowserRenderingMode {
17 Software2D = 1,
19 WebGL1,
21 WebGL2,
23}
24
25impl BrowserRenderingMode {
26 pub fn parse(name: &str) -> Option<BrowserRenderingMode> {
28 [BrowserRenderingMode::Software2D, BrowserRenderingMode::WebGL1, BrowserRenderingMode::WebGL2]
29 .into_iter()
30 .find(|mode| mode.name().eq_ignore_ascii_case(name))
31 }
32
33 pub fn name(self) -> &'static str {
35 match self {
36 BrowserRenderingMode::Software2D => "Software2D",
37 BrowserRenderingMode::WebGL1 => "WebGL1",
38 BrowserRenderingMode::WebGL2 => "WebGL2",
39 }
40 }
41}
42
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
56pub enum BrowserRenderer {
57 #[default]
59 Skia,
60 Vello,
63}
64
65impl BrowserRenderer {
66 pub fn parse(name: &str) -> Option<BrowserRenderer> {
68 [BrowserRenderer::Skia, BrowserRenderer::Vello].into_iter().find(|renderer| renderer.name().eq_ignore_ascii_case(name))
69 }
70
71 pub fn name(self) -> &'static str {
73 match self {
74 BrowserRenderer::Skia => "Skia",
75 BrowserRenderer::Vello => "Vello",
76 }
77 }
78
79 pub fn is_available(self) -> bool {
81 match self {
82 BrowserRenderer::Skia => true,
83 BrowserRenderer::Vello => cfg!(feature = "vello"),
84 }
85 }
86
87 pub fn rendering_modes(self, modes: &[BrowserRenderingMode]) -> Vec<BrowserRenderingMode> {
91 match self {
92 BrowserRenderer::Skia => modes.to_vec(),
93 BrowserRenderer::Vello => {
94 let drawn: Vec<BrowserRenderingMode> =
95 modes.iter().copied().filter(|mode| *mode != BrowserRenderingMode::WebGL1).collect();
96 match drawn.is_empty() && !modes.is_empty() {
97 true => vec![BrowserRenderingMode::Software2D],
98 false => drawn,
99 }
100 }
101 }
102 }
103}
104
105#[derive(Clone, Debug, PartialEq, Eq)]
107pub struct BrowserPlatformOptions {
108 pub rendering_mode: Vec<BrowserRenderingMode>,
111
112 pub renderer: BrowserRenderer,
118
119 pub register_ferro_service_worker: bool,
127
128 pub ferro_service_worker_scope: Option<String>,
134
135 pub prefer_file_dialog_polyfill: bool,
140
141 pub render_thread: bool,
151}
152
153impl Default for BrowserPlatformOptions {
154 fn default() -> Self {
155 Self {
156 rendering_mode: vec![
157 BrowserRenderingMode::WebGL2,
158 BrowserRenderingMode::WebGL1,
159 BrowserRenderingMode::Software2D,
160 ],
161 renderer: BrowserRenderer::Skia,
162 register_ferro_service_worker: false,
163 ferro_service_worker_scope: None,
164 prefer_file_dialog_polyfill: false,
165 render_thread: true,
166 }
167 }
168}
169
170pub trait BrowserAppBuilder {
172 fn start_browser_app(&self, main_div_id: &str, options: Option<BrowserPlatformOptions>);
179
180 fn setup_browser_app(&self, options: Option<BrowserPlatformOptions>);
186
187 fn use_browser(&self) -> AppBuilder;
191}
192
193fn pre_setup_browser(builder: &AppBuilder, options: Option<BrowserPlatformOptions>) -> AppBuilder {
194 let options = match options {
195 Some(options) => Rc::new(options),
196 None => FerroLocator::current().get_service::<BrowserPlatformOptions>().unwrap_or_default(),
197 };
198
199 let renderer = options.renderer;
200 FerroLocator::current_mutable().bind_to_self(options);
201
202 BrowserWindowingPlatform::set_global_this(dom_helper::get_global_this());
203
204 let builder = if builder.windowing_subsystem_initializer().is_none() {
205 builder.use_browser()
206 } else {
207 builder.clone()
208 };
209
210 match renderer {
211 BrowserRenderer::Skia => builder,
212 BrowserRenderer::Vello => use_vello_renderer(&builder),
213 }
214}
215
216#[cfg(feature = "vello")]
224fn use_vello_renderer(builder: &AppBuilder) -> AppBuilder {
225 use ferroui_vello::{VelloApplicationExtensions, VelloOptions, VelloRenderingMode};
226
227 if FerroLocator::current().get_service::<VelloOptions>().is_none() {
228 FerroLocator::current_mutable().bind_to_self(Rc::new(VelloOptions {
229 rendering_modes: [Some(VelloRenderingMode::Hybrid), Some(VelloRenderingMode::Cpu), None],
230 ..VelloOptions::default()
231 }));
232 }
233
234 builder.use_vello()
235}
236
237#[cfg(not(feature = "vello"))]
240fn use_vello_renderer(_builder: &AppBuilder) -> AppBuilder {
241 panic!(
242 "BrowserPlatformOptions::renderer asks for the Vello backend, and the module was built without the feature \
243 `vello` of ferroui-browser (scripts/build-browser.sh <application> --features vello; \
244 docs/porting/vello-backend.md, section 12)"
245 );
246}
247
248impl BrowserAppBuilder for AppBuilder {
249 fn start_browser_app(&self, main_div_id: &str, options: Option<BrowserPlatformOptions>) {
250 let builder = pre_setup_browser(self, options);
251
252 let lifetime = BrowserSingleViewLifetime::new();
253 builder.after_application_setup({
254 let lifetime = lifetime.clone();
255 let main_div_id = main_div_id.to_string();
256 move |_| {
257 lifetime.set_view(Some(FerroView::new(&main_div_id)));
258 }
259 });
260
261 let lifetime: Rc<dyn IApplicationLifetime> = lifetime;
262 builder.setup_with_lifetime(lifetime);
263 }
264
265 fn setup_browser_app(&self, options: Option<BrowserPlatformOptions>) {
266 let builder = pre_setup_browser(self, options);
267
268 let lifetime: Rc<dyn IApplicationLifetime> = BrowserSingleViewLifetime::new();
269 builder.setup_with_lifetime(lifetime);
270 }
271
272 fn use_browser(&self) -> AppBuilder {
273 BrowserRuntimePlatformServices::use_browser_runtime_platform_subsystem(self)
274 .use_windowing_subsystem(BrowserWindowingPlatform::register, "")
275 .use_skia()
276 .use_harfbuzz()
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn the_modes_have_the_values_the_script_side_uses() {
286 assert_eq!(1, BrowserRenderingMode::Software2D as i32);
287 assert_eq!(2, BrowserRenderingMode::WebGL1 as i32);
288 assert_eq!(3, BrowserRenderingMode::WebGL2 as i32);
289 }
290
291 #[test]
292 fn the_default_modes_fall_back_from_webgl2_to_software() {
293 assert_eq!(
294 vec![BrowserRenderingMode::WebGL2, BrowserRenderingMode::WebGL1, BrowserRenderingMode::Software2D],
295 BrowserPlatformOptions::default().rendering_mode
296 );
297 }
298
299 #[test]
300 fn a_module_with_threads_renders_on_a_render_thread_unless_told_not_to() {
301 assert!(BrowserPlatformOptions::default().render_thread);
302 }
303
304 #[test]
305 fn skia_is_the_default_renderer_and_draws_every_mode() {
306 let options = BrowserPlatformOptions::default();
307
308 assert_eq!(BrowserRenderer::Skia, options.renderer);
309 assert!(BrowserRenderer::Skia.is_available());
310 assert_eq!(options.rendering_mode, BrowserRenderer::Skia.rendering_modes(&options.rendering_mode));
311 }
312
313 #[test]
314 fn renderers_are_parsed_by_name_ignoring_case() {
315 assert_eq!(Some(BrowserRenderer::Vello), BrowserRenderer::parse("vello"));
316 assert_eq!(Some(BrowserRenderer::Skia), BrowserRenderer::parse("SKIA"));
317 assert_eq!(None, BrowserRenderer::parse("Direct2D"));
318 assert_eq!(None, BrowserRenderer::parse(""));
319 assert_eq!(cfg!(feature = "vello"), BrowserRenderer::Vello.is_available());
320 }
321
322 #[test]
323 fn the_vello_renderer_draws_no_webgl1_canvas() {
324 use BrowserRenderingMode::{Software2D, WebGL1, WebGL2};
325
326 assert_eq!(vec![WebGL2, Software2D], BrowserRenderer::Vello.rendering_modes(&[WebGL2, WebGL1, Software2D]));
328 assert_eq!(vec![Software2D], BrowserRenderer::Vello.rendering_modes(&[Software2D]));
329 assert_eq!(vec![WebGL2], BrowserRenderer::Vello.rendering_modes(&[WebGL2]));
330 assert_eq!(vec![Software2D], BrowserRenderer::Vello.rendering_modes(&[WebGL1]));
333 assert!(BrowserRenderer::Vello.rendering_modes(&[]).is_empty());
334 }
335
336 #[test]
337 fn modes_are_parsed_by_name_ignoring_case() {
338 assert_eq!(Some(BrowserRenderingMode::WebGL2), BrowserRenderingMode::parse("WebGL2"));
339 assert_eq!(Some(BrowserRenderingMode::WebGL1), BrowserRenderingMode::parse("webgl1"));
340 assert_eq!(Some(BrowserRenderingMode::Software2D), BrowserRenderingMode::parse("SOFTWARE2D"));
341 assert_eq!(None, BrowserRenderingMode::parse("WebGPU"));
342 assert_eq!(None, BrowserRenderingMode::parse(""));
343 }
344}