1#![allow(non_snake_case)]
3
4use js_sys::Uint8Array;
5use pax_message::ImageLoadInterruptArgs;
6use pax_runtime::api::borrow;
7use pax_runtime::api::math::Point2;
8use pax_runtime::api::use_RefCell;
9use pax_runtime::api::ButtonClick;
10use pax_runtime::api::Platform;
11use pax_runtime::api::RenderContext;
12use pax_runtime::api::TextboxChange;
13use pax_runtime::api::OS;
14use pax_runtime::DefinitionToInstanceTraverser;
15use pax_runtime_api::borrow_mut;
16use pax_runtime_api::Event;
17use pax_runtime_api::Focus;
18use pax_runtime_api::SelectStart;
19use web_time::Instant;
20use_RefCell!();
21
22use std::rc::Rc;
23use wasm_bindgen::prelude::*;
24use wasm_bindgen::JsCast;
25use web_sys::{window, HtmlCanvasElement};
26
27use piet_web::WebRenderContext;
28
29use pax_runtime::{PaxEngine, Renderer};
30
31pub use {console_error_panic_hook, console_log};
32
33use pax_message::NativeInterrupt;
34use pax_runtime::api::{
35 Clap, Click, ContextMenu, DoubleClick, Drop, KeyDown, KeyPress, KeyUp, KeyboardEventArgs,
36 ModifierKey, MouseButton, MouseDown, MouseEventArgs, MouseMove, MouseUp, Touch, TouchEnd,
37 TouchMove, TouchStart, Wheel,
38};
39use serde_json;
40
41#[cfg(any(feature = "designtime", feature = "designer"))]
42use {pax_designtime::orm::ReloadType, pax_designtime::DesigntimeManager};
43
44const USERLAND_COMPONENT_ROOT: &str = "USERLAND_COMPONENT_ROOT";
45#[cfg(any(feature = "designtime", feature = "designer"))]
46const DESIGNER_COMPONENT_ROOT: &str = "DESIGNER_COMPONENT_ROOT";
47
48#[wasm_bindgen]
49pub fn wasm_memory() -> JsValue {
50 wasm_bindgen::memory()
51}
52
53#[wasm_bindgen]
54pub struct PaxChassisWeb {
55 drawing_contexts: Renderer<WebRenderContext<'static>>,
56 engine: Rc<RefCell<PaxEngine>>,
57 #[cfg(any(feature = "designtime", feature = "designer"))]
58 userland_definition_to_instance_traverser:
59 Box<dyn pax_runtime::cartridge::DefinitionToInstanceTraverser>,
60 #[cfg(any(feature = "designtime", feature = "designer"))]
61 designtime_manager: Rc<RefCell<DesigntimeManager>>,
62}
63
64#[wasm_bindgen]
65pub struct InterruptResult {
66 pub prevent_default: bool,
67}
68
69impl PaxChassisWeb {
73 #[cfg(any(feature = "designtime", feature = "designer"))]
74 pub async fn new(
75 userland_definition_to_instance_traverser: Box<dyn DefinitionToInstanceTraverser>,
76 designer_definition_to_instance_traverser: Box<dyn DefinitionToInstanceTraverser>,
77 ) -> Self {
78 let (width, height, os_info, get_elapsed_millis) = Self::init_common();
79 let query_string = window()
80 .unwrap()
81 .location()
82 .search()
83 .expect("no search exists");
84
85 let main_component_instance =
86 designer_definition_to_instance_traverser.get_main_component(DESIGNER_COMPONENT_ROOT);
87 let userland_main_component_instance =
88 userland_definition_to_instance_traverser.get_main_component(USERLAND_COMPONENT_ROOT);
89
90 let designtime_manager = userland_definition_to_instance_traverser
91 .get_designtime_manager(query_string)
92 .unwrap();
93 let engine = pax_runtime::PaxEngine::new_with_designtime(
94 main_component_instance,
95 userland_main_component_instance,
96 (width, height),
97 designtime_manager.clone(),
98 Platform::Web,
99 os_info,
100 get_elapsed_millis,
101 );
102 let engine_container: Rc<RefCell<PaxEngine>> = Rc::new(RefCell::new(engine));
103 Self {
104 engine: engine_container,
105 drawing_contexts: Renderer::new(),
106 userland_definition_to_instance_traverser,
107 designtime_manager,
108 }
109 }
110
111 #[cfg(not(any(feature = "designtime", feature = "designer")))]
112 pub async fn new(
113 definition_to_instance_traverser: Box<dyn DefinitionToInstanceTraverser>,
114 ) -> Self {
115 let (width, height, os_info, get_time) = Self::init_common();
116
117 let main_component_instance =
118 definition_to_instance_traverser.get_main_component(USERLAND_COMPONENT_ROOT);
119 let engine = pax_runtime::PaxEngine::new(
120 main_component_instance,
121 (width, height),
122 Platform::Web,
123 os_info,
124 get_time,
125 );
126
127 let engine_container: Rc<RefCell<PaxEngine>> = Rc::new(RefCell::new(engine));
128
129 Self {
130 engine: engine_container,
131 drawing_contexts: Renderer::new(),
132 }
133 }
134
135 fn init_common() -> (f64, f64, OS, Box<dyn Fn() -> u128>) {
136 let window = window().unwrap();
137 let user_agent_str = window.navigator().user_agent().ok();
138 let os_info = user_agent_str
139 .and_then(|s| parse_user_agent_str(&s))
140 .unwrap_or_default();
141
142 let width = window.inner_width().unwrap().as_f64().unwrap();
143 let height = window.inner_height().unwrap().as_f64().unwrap();
144 let start = Instant::now();
145 let get_time = Box::new(move || start.elapsed().as_millis());
146 (width, height, os_info, get_time)
147 }
148}
149
150#[wasm_bindgen]
151impl PaxChassisWeb {
152 pub fn add_context(&mut self, id: usize) {
153 let window = window().unwrap();
154 let dpr = window.device_pixel_ratio();
155 let document = window.document().unwrap();
156 let canvas = document
157 .get_element_by_id(id.to_string().as_str())
158 .unwrap()
159 .dyn_into::<HtmlCanvasElement>()
160 .unwrap();
161 let context: web_sys::CanvasRenderingContext2d = canvas
162 .get_context("2d")
163 .unwrap()
164 .unwrap()
165 .dyn_into::<web_sys::CanvasRenderingContext2d>()
166 .unwrap();
167
168 let width = canvas.offset_width() as f64 * dpr;
169 let height = canvas.offset_height() as f64 * dpr;
170
171 canvas.set_width(width as u32);
172 canvas.set_height(height as u32);
173 let _ = context.scale(dpr, dpr);
174 let render_context = WebRenderContext::new(context, window.clone());
175 self.drawing_contexts.add_context(id, render_context);
176 self.engine.borrow().runtime_context.add_canvas(id);
177 }
178
179 pub fn send_viewport_update(&mut self, width: f64, height: f64) {
180 self.engine
181 .borrow()
182 .runtime_context
183 .set_all_canvases_dirty();
184 borrow_mut!(self.engine).set_viewport_size((width, height));
185 }
186 pub fn remove_context(&mut self, id: usize) {
187 self.drawing_contexts.remove_context(id);
188 self.engine.borrow().runtime_context.remove_canvas(id);
189 }
190
191 pub fn get_dirty_canvases(&self) -> Vec<usize> {
192 let ret = self.engine.borrow().runtime_context.get_dirty_canvases();
193 ret
194 }
195
196 pub fn interrupt(
197 &mut self,
198 native_interrupt: String,
199 additional_payload: &JsValue,
200 ) -> InterruptResult {
201 let x: NativeInterrupt = serde_json::from_str(&native_interrupt).unwrap();
202
203 let engine = borrow_mut!(self.engine);
204 let ctx = &engine.runtime_context;
205 let globals = ctx.globals();
206 let prevent_default = match &x {
207 NativeInterrupt::Focus(_args) => engine.global_dispatch_focus(Focus {}),
208 NativeInterrupt::DropFile(args) => {
209 let data = Uint8Array::new(additional_payload).to_vec();
210 let topmost_node = engine
211 .runtime_context
212 .get_topmost_element_beneath_ray(Point2::new(args.x, args.y));
213 let args_drop = Drop {
214 x: args.x,
215 y: args.y,
216 name: args.name.clone(),
217 mime_type: args.mime_type.clone(),
218 data,
219 };
220 topmost_node.dispatch_drop(Event::new(args_drop), &globals, &engine.runtime_context)
221 }
222 NativeInterrupt::FormRadioSetChange(args) => {
223 let node = engine.get_expanded_node(pax_runtime::ExpandedNodeIdentifier(args.id));
224 if let Some(node) = node {
225 borrow!(node.instance_node).handle_native_interrupt(&node, &x);
226 }
227 false
228 }
229 NativeInterrupt::FormSliderChange(args) => {
230 let node = engine.get_expanded_node(pax_runtime::ExpandedNodeIdentifier(args.id));
231 if let Some(node) = node {
232 borrow!(node.instance_node).handle_native_interrupt(&node, &x);
233 }
234 false
235 }
236 NativeInterrupt::FormDropdownChange(args) => {
237 let node = engine.get_expanded_node(pax_runtime::ExpandedNodeIdentifier(args.id));
238 if let Some(node) = node {
239 borrow!(node.instance_node).handle_native_interrupt(&node, &x);
240 }
241 false
242 }
243 NativeInterrupt::ChassisResizeRequestCollection(collection) => {
244 for args in collection {
245 let node =
246 engine.get_expanded_node(pax_runtime::ExpandedNodeIdentifier(args.id));
247 if let Some(node) = node {
248 node.chassis_resize_request(args.width, args.height);
249 }
250 }
251 false
252 }
253 NativeInterrupt::Image(args) => match args {
254 ImageLoadInterruptArgs::Reference(_ref_args) => false,
255 ImageLoadInterruptArgs::Data(data_args) => {
256 let data = Uint8Array::new(additional_payload).to_vec();
257 self.drawing_contexts.load_image(
258 &data_args.path,
259 &data,
260 data_args.width,
261 data_args.height,
262 );
263 false
264 }
265 },
266 NativeInterrupt::FormButtonClick(args) => {
267 if let Some(node) =
268 engine.get_expanded_node(pax_runtime::ExpandedNodeIdentifier(args.id))
269 {
270 node.dispatch_button_click(
271 Event::new(ButtonClick {}),
272 &globals,
273 &engine.runtime_context,
274 )
275 } else {
276 log::warn!(
277 "tried to dispatch event for button click after node already removed"
278 );
279 false
280 }
281 }
282 NativeInterrupt::FormTextboxInput(args) => {
283 if let Some(node) =
284 engine.get_expanded_node(pax_runtime::ExpandedNodeIdentifier(args.id))
285 {
286 borrow!(node.instance_node).handle_native_interrupt(&node, &x);
287 } else {
288 log::warn!(
289 "tried to dispatch event for textbox input after node already removed"
290 );
291 }
292 false
293 }
294 NativeInterrupt::TextInput(args) => {
295 if let Some(node) =
296 engine.get_expanded_node(pax_runtime::ExpandedNodeIdentifier(args.id))
297 {
298 borrow!(node.instance_node).handle_native_interrupt(&node, &x);
299 } else {
300 log::warn!("tried to dispatch event for text input after node already removed");
301 }
302 false
303 }
304 NativeInterrupt::FormTextboxChange(args) => {
305 if let Some(node) =
306 engine.get_expanded_node(pax_runtime::ExpandedNodeIdentifier(args.id))
307 {
308 node.dispatch_textbox_change(
309 Event::new(TextboxChange {
310 text: args.text.clone(),
311 }),
312 &globals,
313 &engine.runtime_context,
314 )
315 } else {
316 log::warn!(
317 "tried to dispatch event for textbox change after node already removed"
318 );
319 false
320 }
321 }
322 NativeInterrupt::FormCheckboxToggle(args) => {
323 if let Some(node) =
324 engine.get_expanded_node(pax_runtime::ExpandedNodeIdentifier(args.id))
325 {
326 borrow!(node.instance_node).handle_native_interrupt(&node, &x);
327 } else {
328 log::warn!(
329 "tried to dispatch event for checkbox toggle after node already removed"
330 );
331 }
332 false
333 }
334
335 NativeInterrupt::AddedLayer(_args) => false,
336 NativeInterrupt::Click(args) => {
337 let topmost_node = engine
338 .runtime_context
339 .get_topmost_element_beneath_ray(Point2::new(args.x, args.y));
340 let args_click = Click {
341 mouse: MouseEventArgs {
342 x: args.x,
343 y: args.y,
344 button: MouseButton::from(args.button.clone()),
345 modifiers: args
346 .modifiers
347 .iter()
348 .map(|x| ModifierKey::from(x))
349 .collect(),
350 },
351 };
352 topmost_node.dispatch_click(
353 Event::new(args_click),
354 &globals,
355 &engine.runtime_context,
356 )
357 }
358 NativeInterrupt::Scrollbar(args) => {
359 let node = engine.get_expanded_node(pax_runtime::ExpandedNodeIdentifier(args.id));
360 if let Some(node) = node {
361 borrow!(node.instance_node).handle_native_interrupt(&node, &x);
362 }
363 false
364 }
365 NativeInterrupt::Scroll(_) => false,
366 NativeInterrupt::Clap(args) => {
367 let topmost_node = engine
368 .runtime_context
369 .get_topmost_element_beneath_ray(Point2::new(args.x, args.y));
370 let args_clap = Clap {
371 x: args.x,
372 y: args.y,
373 };
374 topmost_node.dispatch_clap(Event::new(args_clap), &globals, &engine.runtime_context)
375 }
376 NativeInterrupt::TouchStart(args) => {
377 let first_touch = args.touches.get(0).unwrap();
378 let topmost_node = engine
379 .runtime_context
380 .get_topmost_element_beneath_ray(Point2::new(first_touch.x, first_touch.y));
381 let touches = args.touches.iter().map(|x| Touch::from(x)).collect();
382 let args_touch_start = TouchStart { touches };
383 topmost_node.dispatch_touch_start(
384 Event::new(args_touch_start),
385 &globals,
386 &engine.runtime_context,
387 )
388 }
389 NativeInterrupt::TouchMove(args) => {
390 let first_touch = args.touches.get(0).unwrap();
391 let topmost_node = engine
392 .runtime_context
393 .get_topmost_element_beneath_ray(Point2::new(first_touch.x, first_touch.y));
394 let touches = args.touches.iter().map(|x| Touch::from(x)).collect();
395 let args_touch_move = TouchMove { touches };
396 topmost_node.dispatch_touch_move(
397 Event::new(args_touch_move),
398 &globals,
399 &engine.runtime_context,
400 )
401 }
402 NativeInterrupt::TouchEnd(args) => {
403 let first_touch = args.touches.get(0).unwrap();
404 let topmost_node = engine
405 .runtime_context
406 .get_topmost_element_beneath_ray(Point2::new(first_touch.x, first_touch.y));
407 let touches = args.touches.iter().map(|x| Touch::from(x)).collect();
408 let args_touch_end = TouchEnd { touches };
409 topmost_node.dispatch_touch_end(
410 Event::new(args_touch_end),
411 &globals,
412 &engine.runtime_context,
413 )
414 }
415 NativeInterrupt::KeyDown(args) => {
416 let modifiers = args
417 .modifiers
418 .iter()
419 .map(|x| ModifierKey::from(x))
420 .collect();
421 let args_key_down = KeyDown {
422 keyboard: KeyboardEventArgs {
423 key: args.key.clone(),
424 modifiers,
425 is_repeat: args.is_repeat,
426 },
427 };
428 engine.global_dispatch_key_down(args_key_down)
429 }
430 NativeInterrupt::KeyUp(args) => {
431 let modifiers = args
432 .modifiers
433 .iter()
434 .map(|x| ModifierKey::from(x))
435 .collect();
436 let args_key_up = KeyUp {
437 keyboard: KeyboardEventArgs {
438 key: args.key.clone(),
439 modifiers,
440 is_repeat: args.is_repeat,
441 },
442 };
443 engine.global_dispatch_key_up(args_key_up)
444 }
445 NativeInterrupt::KeyPress(args) => {
446 let modifiers = args
447 .modifiers
448 .iter()
449 .map(|x| ModifierKey::from(x))
450 .collect();
451 let args_key_press = KeyPress {
452 keyboard: KeyboardEventArgs {
453 key: args.key.clone(),
454 modifiers,
455 is_repeat: args.is_repeat,
456 },
457 };
458 engine.global_dispatch_key_press(args_key_press)
459 }
460 NativeInterrupt::DoubleClick(args) => {
461 let topmost_node = engine
462 .runtime_context
463 .get_topmost_element_beneath_ray(Point2::new(args.x, args.y));
464 let args_double_click = DoubleClick {
465 mouse: MouseEventArgs {
466 x: args.x,
467 y: args.y,
468 button: MouseButton::from(args.button.clone()),
469 modifiers: args
470 .modifiers
471 .iter()
472 .map(|x| ModifierKey::from(x))
473 .collect(),
474 },
475 };
476 topmost_node.dispatch_double_click(
477 Event::new(args_double_click),
478 &globals,
479 &engine.runtime_context,
480 )
481 }
482 NativeInterrupt::SelectStart(_args) => {
483 engine.global_dispatch_select_start(SelectStart {})
484 }
485 NativeInterrupt::MouseMove(args) => {
486 let topmost_node = engine
487 .runtime_context
488 .get_topmost_element_beneath_ray(Point2::new(args.x, args.y));
489 let args_mouse_move = MouseMove {
490 mouse: MouseEventArgs {
491 x: args.x,
492 y: args.y,
493 button: MouseButton::from(args.button.clone()),
494 modifiers: args
495 .modifiers
496 .iter()
497 .map(|x| ModifierKey::from(x))
498 .collect(),
499 },
500 };
501 topmost_node.dispatch_mouse_move(
502 Event::new(args_mouse_move),
503 &globals,
504 &engine.runtime_context,
505 )
506 }
507 NativeInterrupt::Wheel(args) => {
508 let topmost_node = engine
509 .runtime_context
510 .get_topmost_element_beneath_ray(Point2::new(args.x, args.y));
511 let modifiers = args
512 .modifiers
513 .iter()
514 .map(|x| ModifierKey::from(x))
515 .collect();
516 let args_wheel = Wheel {
517 x: args.x,
518 y: args.y,
519 delta_x: args.delta_x,
520 delta_y: args.delta_y,
521 modifiers,
522 };
523 topmost_node.dispatch_wheel(
524 Event::new(args_wheel),
525 &globals,
526 &engine.runtime_context,
527 )
528 }
529 NativeInterrupt::MouseDown(args) => {
530 let topmost_node = engine
531 .runtime_context
532 .get_topmost_element_beneath_ray(Point2::new(args.x, args.y));
533 let args_mouse_down = MouseDown {
534 mouse: MouseEventArgs {
535 x: args.x,
536 y: args.y,
537 button: MouseButton::from(args.button.clone()),
538 modifiers: args
539 .modifiers
540 .iter()
541 .map(|x| ModifierKey::from(x))
542 .collect(),
543 },
544 };
545 topmost_node.dispatch_mouse_down(
546 Event::new(args_mouse_down),
547 &globals,
548 &engine.runtime_context,
549 )
550 }
551 NativeInterrupt::MouseUp(args) => {
552 let topmost_node = engine
553 .runtime_context
554 .get_topmost_element_beneath_ray(Point2::new(args.x, args.y));
555 let args_mouse_up = MouseUp {
556 mouse: MouseEventArgs {
557 x: args.x,
558 y: args.y,
559 button: MouseButton::from(args.button.clone()),
560 modifiers: args
561 .modifiers
562 .iter()
563 .map(|x| ModifierKey::from(x))
564 .collect(),
565 },
566 };
567 topmost_node.dispatch_mouse_up(
568 Event::new(args_mouse_up),
569 &globals,
570 &engine.runtime_context,
571 )
572 }
573 NativeInterrupt::ContextMenu(args) => {
574 let topmost_node = engine
575 .runtime_context
576 .get_topmost_element_beneath_ray(Point2::new(args.x, args.y));
577 let args_context_menu = ContextMenu {
578 mouse: MouseEventArgs {
579 x: args.x,
580 y: args.y,
581 button: MouseButton::from(args.button.clone()),
582 modifiers: args
583 .modifiers
584 .iter()
585 .map(|x| ModifierKey::from(x))
586 .collect(),
587 },
588 };
589 topmost_node.dispatch_context_menu(
590 Event::new(args_context_menu),
591 &globals,
592 &engine.runtime_context,
593 )
594 }
595 };
596
597 InterruptResult { prevent_default }
598 }
599
600 pub fn deallocate(&mut self, slice: MemorySlice) {
601 let layout = std::alloc::Layout::from_size_align(slice.len(), 1).unwrap();
602 unsafe {
603 std::alloc::dealloc(slice.ptr() as *mut u8, layout);
604 }
605 }
606
607 #[cfg(any(feature = "designtime", feature = "designer"))]
608 pub fn update_userland_component(&mut self) {
609 let current_manifest_version =
610 borrow!(self.designtime_manager).get_last_written_manifest_version();
611 let reload_queue = borrow_mut!(self.designtime_manager).take_reload_queue();
612
613 if current_manifest_version.get()
614 != self
615 .designtime_manager
616 .borrow()
617 .get_last_rendered_manifest_version()
618 .get()
619 {
620 for reload_type in reload_queue {
621 match reload_type {
622 ReloadType::Tree => {
624 let mut engine = borrow_mut!(self.engine);
625 let root = self
626 .userland_definition_to_instance_traverser
627 .get_main_component(USERLAND_COMPONENT_ROOT)
628 as Rc<dyn pax_runtime::InstanceNode>;
629 engine.full_reload_userland(root);
630 }
631 ReloadType::Node(uni, _) => {
632 let manifest = self
633 .userland_definition_to_instance_traverser
634 .get_manifest();
635 let containing_component = manifest
636 .components
637 .get(&uni.get_containing_component_type_id())
638 .unwrap();
639 let containing_template = containing_component.template.as_ref().unwrap();
640 let tnd = containing_template
641 .get_node(&uni.get_template_node_id())
642 .unwrap();
643
644 let nodes = self
645 .engine
646 .borrow()
647 .runtime_context
648 .get_expanded_nodes_by_global_ids(&uni);
649
650 let prior_instance_node = nodes.get(0).map(|x| {
651 pax_runtime::ReusableInstanceNodeArgs::new(
652 x.instance_node.borrow().base(),
653 )
654 });
655
656 let pax_type = tnd.type_id.get_pax_type();
657 let instance_node = match pax_type {
658 pax_manifest::PaxType::If
659 | pax_manifest::PaxType::Slot
660 | pax_manifest::PaxType::Repeat => self
661 .userland_definition_to_instance_traverser
662 .build_control_flow(
663 &uni.get_containing_component_type_id(),
664 &uni.get_template_node_id(),
665 prior_instance_node,
666 ),
667 _ => self
668 .userland_definition_to_instance_traverser
669 .build_template_node(
670 &uni.get_containing_component_type_id(),
671 &uni.get_template_node_id(),
672 prior_instance_node,
673 ),
674 };
675 let mut engine = borrow_mut!(self.engine);
676 engine.partial_update_expanded_node(Rc::clone(&instance_node));
677 }
678 }
679 }
680 self.designtime_manager
681 .borrow_mut()
682 .set_last_rendered_manifest_version(current_manifest_version.get());
683 }
684 }
685
686 #[cfg(any(feature = "designtime", feature = "designer"))]
687 pub fn handle_recv_designtime(&mut self) {
688 borrow_mut!(self.designtime_manager)
689 .handle_recv()
690 .expect("couldn't handle recv");
691 }
692
693 #[cfg(any(feature = "designtime", feature = "designer"))]
694 pub fn designtime_tick(&mut self) {
695 self.handle_recv_designtime();
696 self.update_userland_component();
697 }
698
699 pub fn tick(&mut self) -> MemorySlice {
700 #[cfg(any(feature = "designtime", feature = "designer"))]
701 self.designtime_tick();
702
703 let message_queue = borrow_mut!(self.engine).tick();
704
705 let json_string = serde_json::to_string(&message_queue).unwrap();
707
708 let bytes = json_string.as_bytes();
710
711 let layout = std::alloc::Layout::from_size_align(bytes.len(), 1).unwrap();
713 let ptr = unsafe { std::alloc::alloc(layout) as *mut u8 };
714
715 unsafe {
717 std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, bytes.len());
718 }
719
720 MemorySlice {
721 ptr: ptr as *const u8,
722 len: bytes.len(),
723 }
724 }
725
726 pub fn render(&mut self) {
727 borrow_mut!(self.engine).render((&mut self.drawing_contexts) as &mut dyn RenderContext);
728 self.engine
729 .borrow()
730 .runtime_context
731 .clear_all_dirty_canvases();
732 }
733
734 pub fn image_loaded(&mut self, path: &str) -> bool {
735 self.drawing_contexts.image_loaded(path)
736 }
737}
738
739fn parse_user_agent_str(user_agent: &str) -> Option<OS> {
743 let platform_start = user_agent.find('(')?;
748 let platform_end = platform_start + user_agent[platform_start..].find(')')?;
749 let platform_str = user_agent.get(platform_start + 1..platform_end - 1)?;
750
751 const STR_PLATFORM_PAIRS: &[(&str, OS)] = &[
753 ("Android", OS::Android),
754 ("iPhone", OS::IPhone),
755 ("Windows", OS::Windows),
756 ("Mac", OS::Mac),
757 ("Linux", OS::Linux),
758 ];
759 for (needle, plat) in STR_PLATFORM_PAIRS {
760 if platform_str.contains(needle) {
761 return Some(*plat);
762 }
763 }
764 None
765}
766
767#[wasm_bindgen]
768pub struct MemorySlice {
769 ptr: *const u8,
770 len: usize,
771}
772
773#[wasm_bindgen]
774impl MemorySlice {
775 pub fn ptr(&self) -> *const u8 {
776 self.ptr
777 }
778
779 pub fn len(&self) -> usize {
780 self.len
781 }
782}