1#![no_std]
46#![forbid(unsafe_op_in_unsafe_fn)]
47
48#[cfg(test)]
49extern crate std;
50
51extern crate alloc;
52
53mod arena;
54mod conformance;
55mod constants;
56mod events;
57pub mod fit;
58mod memory;
59mod opcode;
60mod ring;
61pub mod tokens;
62
63pub use arena::ArenaError;
64pub use constants::*;
65pub use events::{Event, EventError};
66pub use memory::MemoryLayout;
67pub use opcode::{Opcode, OPCODE_SIZE};
68pub use ring::RingError;
69
70use alloc::vec;
71use alloc::vec::Vec;
72use arena as arena_fn;
73use memory::header;
74use memory::{OFF_FRAME_COUNT, OFF_READ_CURSOR, OFF_SLOT_COUNT, OFF_WRITE_CURSOR};
75use ring as ring_fn;
76
77pub fn init_memory(mem: &mut [u8], layout: &MemoryLayout) {
80 header::init(mem, layout);
81}
82
83pub struct SharedHeader<'a> {
90 mem: &'a [u8],
91}
92
93impl<'a> SharedHeader<'a> {
94 pub fn new(mem: &'a [u8]) -> Self {
96 Self { mem }
97 }
98
99 fn u32(&self, off: usize) -> u32 {
100 u32::from_le_bytes(self.mem[off..off + 4].try_into().unwrap())
101 }
102
103 pub fn frame_count(&self) -> u32 {
105 self.u32(memory::OFF_FRAME_COUNT)
106 }
107
108 pub fn read_cursor(&self) -> u32 {
110 self.u32(memory::OFF_READ_CURSOR)
111 }
112
113 pub fn write_cursor(&self) -> u32 {
115 self.u32(memory::OFF_WRITE_CURSOR)
116 }
117
118 pub fn slot_count(&self) -> u32 {
120 self.u32(memory::OFF_SLOT_COUNT)
121 }
122
123 pub fn ring_offset(&self) -> usize {
125 self.u32(memory::OFF_RING_OFFSET) as usize
126 }
127
128 pub fn ring_bytes(&self) -> usize {
130 self.u32(memory::OFF_RING_BYTES) as usize
131 }
132
133 pub fn arena_offset(&self) -> usize {
135 self.u32(memory::OFF_ARENA_OFFSET) as usize
136 }
137
138 pub fn arena_cursor(&self) -> u32 {
140 self.u32(memory::OFF_ARENA_CURSOR)
141 }
142}
143
144fn slot_count(header: &[u8]) -> usize {
146 header::get_u32(header, OFF_SLOT_COUNT) as usize
147}
148
149fn slot_mask(header: &[u8]) -> usize {
151 let slots = slot_count(header);
152 debug_assert!(slots.is_power_of_two());
153 slots - 1
154}
155
156pub struct Guest<'a> {
163 header: &'a mut [u8],
164 slots: &'a mut [u8],
165 event_slots: &'a mut [u8],
167 arena: &'a mut [u8],
168 event_arena: &'a mut [u8],
171 frame_start: u32,
173}
174
175impl<'a> Guest<'a> {
176 pub fn new(mem: &'a mut [u8], layout: &MemoryLayout) -> Self {
179 let (header, rest) = mem.split_at_mut(memory::HEADER_SIZE);
180 let (slots, rest) = rest.split_at_mut(layout.ring_bytes());
181 let (event_slots, rest) = rest.split_at_mut(layout.event_ring_bytes());
182 let (arena, event_arena) = rest.split_at_mut(layout.arena_bytes);
183 let frame_start = header::get_u32(header, OFF_WRITE_CURSOR);
184 Self {
185 header,
186 slots,
187 event_slots,
188 arena,
189 event_arena,
190 frame_start,
191 }
192 }
193
194 fn mask(&self) -> usize {
195 slot_mask(self.header)
196 }
197
198 pub fn begin_frame(&mut self) {
200 self.frame_start = header::get_u32(self.header, OFF_WRITE_CURSOR);
201 }
202
203 pub fn pending(&self) -> usize {
205 let write = header::get_u32(self.header, OFF_WRITE_CURSOR);
206 let mask = self.mask() as u32;
207 (write.wrapping_sub(self.frame_start) & mask) as usize
208 }
209
210 pub fn end_frame(&mut self) {
212 let count = header::get_u32(self.header, OFF_FRAME_COUNT);
213 header::set_u32(self.header, OFF_FRAME_COUNT, count.wrapping_add(1));
214 }
215
216 pub fn frame_count(&self) -> u32 {
218 header::get_u32(self.header, OFF_FRAME_COUNT)
219 }
220
221 pub fn create_node(&mut self, node_id: u32, component_type: u16) -> Result<(), RingError> {
224 ring_fn::push_create_node(self.slots, self.header, self.mask(), node_id, component_type)
225 }
226
227 pub fn delete_node(&mut self, node_id: u32) -> Result<(), RingError> {
228 ring_fn::push_delete_node(self.slots, self.header, self.mask(), node_id)
229 }
230
231 pub fn insert_child(&mut self, parent: u32, child: u32, index: u32) -> Result<(), RingError> {
232 ring_fn::push_insert_child(self.slots, self.header, self.mask(), parent, child, index)
233 }
234
235 pub fn remove_child(&mut self, parent: u32, child: u32) -> Result<(), RingError> {
236 ring_fn::push_remove_child(self.slots, self.header, self.mask(), parent, child)
237 }
238
239 pub fn move_child(
240 &mut self,
241 parent: u32,
242 child: u32,
243 new_index: u32,
244 ) -> Result<(), RingError> {
245 ring_fn::push_move_child(self.slots, self.header, self.mask(), parent, child, new_index)
246 }
247
248 pub fn push_opcode(&mut self, op: Opcode) -> Result<(), RingError> {
254 ring_fn::push(self.slots, self.header, self.mask(), &op)
255 }
256
257 pub fn set_property(
260 &mut self,
261 node_id: u32,
262 property_id: u16,
263 value_type: u8,
264 value: u32,
265 ) -> Result<(), RingError> {
266 ring_fn::push_set_property(
267 self.slots,
268 self.header,
269 self.mask(),
270 node_id,
271 property_id,
272 value_type,
273 value,
274 )
275 }
276
277 pub fn set_text(&mut self, node_id: u32, text: &str) -> Result<u32, RingError> {
279 let arena_ref = arena_fn::alloc_str(self.arena, self.header, text).map_err(|_| RingError::Full)?;
280 ring_fn::push_set_text(self.slots, self.header, self.mask(), node_id, arena_ref)?;
281 Ok(arena_ref)
282 }
283
284 pub fn set_date(&mut self, node_id: u32, days: i32, millis: u32) -> Result<(), RingError> {
287 let op = Opcode::new(
288 category::PARAMETER,
289 crate::parameter::SET_DATE,
290 0,
291 node_id,
292 days as u32,
293 millis,
294 );
295 ring_fn::push(self.slots, self.header, self.mask(), &op)
296 }
297
298 pub fn set_design_token(
303 &mut self,
304 path: &str,
305 value_type: u8,
306 value: u32,
307 ) -> Result<u32, RingError> {
308 let arena_ref = arena_fn::alloc_str(self.arena, self.header, path)
309 .map_err(|_| RingError::Full)?;
310 let op = Opcode::new(
311 category::PARAMETER,
312 crate::parameter::SET_DESIGN_TOKEN,
313 0,
314 arena_ref,
315 u32::from(value_type),
316 value,
317 );
318 ring_fn::push(self.slots, self.header, self.mask(), &op)?;
319 Ok(arena_ref)
320 }
321
322 pub fn set_design_token_string(
327 &mut self,
328 path: &str,
329 value: &str,
330 ) -> Result<u32, RingError> {
331 let arena_ref = arena_fn::alloc_str(self.arena, self.header, path)
332 .map_err(|_| RingError::Full)?;
333 let value_ref = arena_fn::alloc_str(self.arena, self.header, value)
334 .map_err(|_| RingError::Full)?;
335 let op = Opcode::new(
336 category::PARAMETER,
337 crate::parameter::SET_DESIGN_TOKEN,
338 0,
339 arena_ref,
340 u32::from(crate::value_type::STRING),
341 value_ref,
342 );
343 ring_fn::push(self.slots, self.header, self.mask(), &op)?;
344 Ok(arena_ref)
345 }
346
347 pub fn alloc(&mut self, bytes: &[u8]) -> Result<u32, ArenaError> {
349 arena_fn::alloc(self.arena, self.header, bytes)
350 }
351
352 pub fn alloc_str(&mut self, s: &str) -> Result<u32, ArenaError> {
354 arena_fn::alloc_str(self.arena, self.header, s)
355 }
356
357 pub fn reset(&mut self) -> Result<(), RingError> {
360 ring_fn::push_reset(self.slots, self.header, self.mask())
361 }
362
363 pub fn drain_events(&mut self) -> Vec<Event> {
370 let mask = self.mask();
371 let ops = ring_fn::read_events(&self.event_slots, self.header, mask, usize::MAX);
372 ops.into_iter()
373 .filter_map(|op| crate::events::decode_event(&op, self.event_arena))
374 .collect()
375 }
376
377 pub fn drain_event_opcodes(&mut self) -> Vec<Opcode> {
381 let mask = self.mask();
382 ring_fn::read_events(&self.event_slots, self.header, mask, usize::MAX)
383 }
384
385 pub fn drain_event_opcodes_max(&mut self, max: usize) -> Vec<Opcode> {
389 let mask = self.mask();
390 ring_fn::read_events(&self.event_slots, self.header, mask, max)
391 }
392
393 pub fn event_arena_cursor(&self) -> u32 {
395 header::get_u32(self.header, memory::OFF_EVENT_ARENA_CURSOR)
396 }
397
398 pub fn push_event_opcode(&mut self, op: Opcode) -> Result<(), RingError> {
404 ring_fn::push_event(self.event_slots, self.header, self.mask(), &op)
405 }
406}
407
408pub struct Frame<'a> {
419 slots: &'a [u8],
420 arena: &'a [u8],
421 start: usize,
422 end: usize,
423}
424
425impl<'a> Frame<'a> {
426 pub fn from_parts(slots: &'a [u8], arena: &'a [u8], start: usize, end: usize) -> Self {
432 Self {
433 slots,
434 arena,
435 start,
436 end,
437 }
438 }
439
440 pub fn opcodes(&self) -> FrameIter<'a> {
442 FrameIter {
443 slots: self.slots,
444 cursor: self.start,
445 end: self.end,
446 }
447 }
448
449 pub fn len(&self) -> usize {
451 (self.end - self.start) / Opcode::SIZE
452 }
453
454 pub fn is_empty(&self) -> bool {
455 self.len() == 0
456 }
457
458 pub fn arena_get(&self, offset: u32) -> Result<&'a [u8], ArenaError> {
460 arena_fn::get(self.arena, offset)
461 }
462
463 pub fn arena_str(&self, offset: u32) -> Result<&'a str, ArenaError> {
465 arena_fn::get_str(self.arena, offset)
466 }
467
468 pub fn arena(&self) -> &'a [u8] {
470 self.arena
471 }
472}
473
474pub struct FrameIter<'a> {
476 slots: &'a [u8],
477 cursor: usize,
478 end: usize,
479}
480
481impl<'a> Iterator for FrameIter<'a> {
482 type Item = Opcode;
483
484 fn next(&mut self) -> Option<Opcode> {
485 if self.cursor >= self.end {
486 return None;
487 }
488 let bytes: [u8; 16] = self.slots[self.cursor..self.cursor + 16].try_into().unwrap();
489 self.cursor += 16;
490 Some(Opcode::from_bytes(&bytes))
491 }
492}
493
494pub struct Host<'a> {
501 header: &'a mut [u8],
502 slots: &'a [u8],
503 event_slots: &'a mut [u8],
505 arena: &'a [u8],
506 event_arena: &'a mut [u8],
509 consumed: u32,
511}
512
513impl<'a> Host<'a> {
514 pub fn new(mem: &'a mut [u8], layout: &MemoryLayout) -> Self {
516 let (header, rest) = mem.split_at_mut(memory::HEADER_SIZE);
517 let (slots, rest) = rest.split_at_mut(layout.ring_bytes());
518 let (event_slots, rest) = rest.split_at_mut(layout.event_ring_bytes());
519 let (arena, event_arena) = rest.split_at_mut(layout.arena_bytes);
520 let slots: &'a [u8] = slots;
521 let arena: &'a [u8] = arena;
522 Self {
523 header,
524 slots,
525 event_slots,
526 arena,
527 event_arena,
528 consumed: 0,
529 }
530 }
531
532 pub fn frame_count(&self) -> u32 {
534 header::get_u32(self.header, OFF_FRAME_COUNT)
535 }
536
537 pub fn frames(&mut self) -> Vec<Frame<'a>> {
542 let frame_count = header::get_u32(self.header, OFF_FRAME_COUNT);
543 if self.consumed >= frame_count {
544 return Vec::new();
545 }
546 let mask = slot_mask(self.header);
547 let start = ring_fn::cursor(self.header, OFF_READ_CURSOR, mask);
548 let end = ring_fn::cursor(self.header, OFF_WRITE_CURSOR, mask);
549
550 let count = end.wrapping_sub(start) & mask;
558 if count == 0 {
559 return Vec::new();
560 }
561 let run_end = (start + count) & mask; let (drain_to_slot, frame_byte_end) = if run_end > start {
563 (run_end, run_end * Opcode::SIZE)
564 } else {
565 (0, self.slots.len()) };
567
568 let frames = vec![Frame {
569 slots: self.slots,
570 arena: self.arena,
571 start: start * Opcode::SIZE,
572 end: frame_byte_end,
573 }];
574
575 header::set_u32(self.header, OFF_READ_CURSOR, drain_to_slot as u32);
576 self.consumed = frame_count;
577
578 let drained_end_byte = if drain_to_slot > start {
582 drain_to_slot * Opcode::SIZE
583 } else {
584 self.slots.len() };
586 let reset_seen = self.slots[start * Opcode::SIZE..drained_end_byte]
587 .chunks_exact(Opcode::SIZE)
588 .any(|slot| {
589 let bytes: [u8; 16] = slot.try_into().unwrap();
590 let op = Opcode::from_bytes(&bytes);
591 op.category() == category::META && op.command() == crate::meta::RESET
592 });
593 if reset_seen {
594 memory::header::set_u32(self.header, memory::OFF_EVENT_ARENA_CURSOR, 0);
595 }
596
597 frames
598 }
599
600 pub fn send_event(&mut self, event: &Event) -> Result<(), RingError> {
610 let mask = slot_mask(self.header);
611 let op = match event {
612 Event::TextChanged { target, value } => {
613 let offset = crate::arena::alloc_event(&mut self.event_arena, self.header, value.as_bytes())
614 .map_err(|_| RingError::Full)?;
615 Opcode::new(category::EVENT, crate::event::TEXT_CHANGED, 0, *target, offset, 0)
616 }
617 Event::Navigate { url: Some(url) } => {
618 let offset = crate::arena::alloc_event(&mut self.event_arena, self.header, url.as_bytes())
619 .map_err(|_| RingError::Full)?;
620 Opcode::new(
621 category::EVENT,
622 crate::event::NAVIGATE,
623 crate::flag::NAVIGATE_URL,
624 0,
625 offset,
626 0,
627 )
628 }
629 _ => event.encode(),
630 };
631 ring_fn::push_event(self.event_slots, self.header, mask, &op)
632 }
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638 use crate::constants::{category, component_type, property_id, value_type};
639 use alloc::vec;
640
641 #[test]
642 fn guest_host_round_trip() {
643 let layout = MemoryLayout::default();
644 let mut mem = vec![0u8; layout.total_bytes()];
645 init_memory(&mut mem, &layout);
646
647 let mut guest = Guest::new(&mut mem, &layout);
648 guest.begin_frame();
649 guest.create_node(1, component_type::VSTACK).unwrap();
650 guest.insert_child(0, 1, APPEND).unwrap();
651 guest.set_property(1, property_id::SPACING, value_type::F32, 4.0f32.to_bits()).unwrap();
652 guest
653 .set_property(1, property_id::PADDING, value_type::F32, 8.0f32.to_bits())
654 .unwrap();
655 let arena_ref = guest.set_text(1, "Hello").unwrap();
656 assert_eq!(guest.pending(), 5);
657 guest.end_frame();
658 assert_eq!(guest.frame_count(), 1);
659
660 let mut host = Host::new(&mut mem, &layout);
661 assert_eq!(host.frame_count(), 1);
662 let frames = host.frames();
663 assert_eq!(frames.len(), 1);
664 let frame = &frames[0];
665 let ops: Vec<Opcode> = frame.opcodes().collect();
666 assert_eq!(ops.len(), 5);
667 assert_eq!(ops[0].category(), category::TREE);
668 assert_eq!(ops[0].a(), 1);
669 assert_eq!(ops[1].a(), 0); assert_eq!(ops[2].b() as u16, property_id::SPACING);
671 assert_eq!(ops[2].c_f32(), 4.0);
672 assert_eq!(frame.arena_str(arena_ref).unwrap(), "Hello");
673 assert!(host.frames().is_empty());
675 }
676
677 #[test]
678 fn set_design_token_round_trips() {
679 let layout = MemoryLayout::default();
680 let mut mem = vec![0u8; layout.total_bytes()];
681 init_memory(&mut mem, &layout);
682
683 let mut guest = Guest::new(&mut mem, &layout);
684 guest.begin_frame();
685 let arena_ref = guest
686 .set_design_token("color.primary", value_type::COLOR, 0xFF_2563EB)
687 .unwrap();
688 let dark_ref = guest
689 .set_design_token("dark.color.primary", value_type::COLOR, 0xFF_60A5FA)
690 .unwrap();
691 guest.end_frame();
692
693 let mut host = Host::new(&mut mem, &layout);
694 let frame = &host.frames()[0];
695 let ops: Vec<Opcode> = frame.opcodes().collect();
696 assert_eq!(ops.len(), 2);
697 assert_eq!(ops[0].category(), category::PARAMETER);
698 assert_eq!(ops[0].command(), parameter::SET_DESIGN_TOKEN);
699 assert_eq!(ops[0].b(), u32::from(value_type::COLOR));
700 assert_eq!(ops[0].c(), 0xFF_2563EB);
701 assert_eq!(frame.arena_str(arena_ref).unwrap(), "color.primary");
702 assert_eq!(frame.arena_str(dark_ref).unwrap(), "dark.color.primary");
703 }
704
705 #[test]
706 fn set_design_token_string_round_trips() {
707 let layout = MemoryLayout::default();
708 let mut mem = vec![0u8; layout.total_bytes()];
709 init_memory(&mut mem, &layout);
710
711 let mut guest = Guest::new(&mut mem, &layout);
712 guest.begin_frame();
713 let path_ref = guest.set_design_token_string("font.body.family", "Inter").unwrap();
714 guest.end_frame();
715
716 let mut host = Host::new(&mut mem, &layout);
717 let frame = &host.frames()[0];
718 let ops: Vec<Opcode> = frame.opcodes().collect();
719 assert_eq!(ops.len(), 1);
720 assert_eq!(ops[0].category(), category::PARAMETER);
721 assert_eq!(ops[0].command(), parameter::SET_DESIGN_TOKEN);
722 assert_eq!(ops[0].b(), u32::from(value_type::STRING));
723 assert_eq!(frame.arena_str(ops[0].a()).unwrap(), "font.body.family");
724 assert_eq!(frame.arena_str(ops[0].c()).unwrap(), "Inter");
725 assert_eq!(path_ref, ops[0].a());
726 }
727
728 #[test]
729 fn ring_full_is_reported() {
730 let mut layout = MemoryLayout::default();
731 layout.slot_count = 4; let mut mem = vec![0u8; layout.total_bytes()];
733 init_memory(&mut mem, &layout);
734
735 let mut guest = Guest::new(&mut mem, &layout);
736 assert!(guest.create_node(1, component_type::VSTACK).is_ok());
738 assert!(guest.create_node(2, component_type::HSTACK).is_ok());
739 assert!(guest.create_node(3, component_type::TEXT).is_ok());
740 assert_eq!(
741 guest.create_node(4, component_type::TEXT).unwrap_err(),
742 RingError::Full
743 );
744 }
745
746 #[test]
747 fn host_event_round_trips_to_guest() {
748 let layout = MemoryLayout::default();
749 let mut mem = vec![0u8; layout.total_bytes()];
750 init_memory(&mut mem, &layout);
751
752 let mut host = Host::new(&mut mem, &layout);
754 host.send_event(&Event::PointerUp {
755 target: 4,
756 x: 10.0,
757 y: 20.0,
758 secondary: false,
759 })
760 .unwrap();
761 drop(host);
762
763 let mut guest = Guest::new(&mut mem, &layout);
765 let events = guest.drain_events();
766 assert_eq!(
767 events,
768 vec![Event::PointerUp {
769 target: 4,
770 x: 10.0,
771 y: 20.0,
772 secondary: false,
773 }]
774 );
775 assert!(guest.drain_events().is_empty());
777 }
778
779 #[test]
780 fn drain_events_skips_unknown_event_commands() {
781 let layout = MemoryLayout::default();
782 let mut mem = vec![0u8; layout.total_bytes()];
783 init_memory(&mut mem, &layout);
784
785 {
787 let mut host = Host::new(&mut mem, &layout);
788 host.send_event(&Event::PointerUp {
789 target: 4,
790 x: 10.0,
791 y: 20.0,
792 secondary: false,
793 })
794 .unwrap();
795 }
796
797 let mask = slot_mask(&mem[..memory::HEADER_SIZE]);
799 let (header, rest) = mem.split_at_mut(memory::HEADER_SIZE);
800 let (_slots, rest) = rest.split_at_mut(layout.ring_bytes());
801 let (event_slots, _arena) = rest.split_at_mut(layout.event_ring_bytes());
802 let bogus = Opcode::new(crate::category::EVENT, 0xFF, 0, 1, 0, 0);
803 ring_fn::push_event(event_slots, header, mask, &bogus).unwrap();
804
805 let mut guest = Guest::new(&mut mem, &layout);
807 let events = guest.drain_events();
808 assert_eq!(events.len(), 1);
809 assert_eq!(
810 events[0],
811 Event::PointerUp {
812 target: 4,
813 x: 10.0,
814 y: 20.0,
815 secondary: false,
816 }
817 );
818 }
819
820 #[test]
821 fn host_frames_survives_a_wrapped_write_cursor() {
822 let layout = MemoryLayout {
823 slot_count: 4,
824 arena_bytes: MemoryLayout::default().arena_bytes,
825 event_arena_bytes: MemoryLayout::default().event_arena_bytes,
826 };
827 let mut mem = vec![0u8; layout.total_bytes()];
828 init_memory(&mut mem, &layout);
829
830 {
832 let mut guest = Guest::new(&mut mem, &layout);
833 guest.begin_frame();
834 guest.create_node(1, component_type::VSTACK).unwrap();
835 guest.create_node(2, component_type::HSTACK).unwrap();
836 guest.create_node(3, component_type::TEXT).unwrap();
837 guest.end_frame();
838 }
839 {
841 let mut host = Host::new(&mut mem, &layout);
842 assert_eq!(host.frames().len(), 1);
843 }
844 {
848 let mut guest = Guest::new(&mut mem, &layout);
849 guest.begin_frame();
850 guest.create_node(4, component_type::TEXT).unwrap();
851 guest.end_frame();
852 }
853
854 let mut host = Host::new(&mut mem, &layout);
857 let frames = host.frames();
858 assert_eq!(frames.len(), 1);
859 assert_eq!(frames[0].opcodes().count(), 1);
860 assert_eq!(frames[0].opcodes().next().unwrap().a(), 4);
861 assert!(host.frames().is_empty());
863 {
865 let mut guest = Guest::new(&mut mem, &layout);
866 guest.begin_frame();
867 guest.create_node(5, component_type::TEXT).unwrap();
868 guest.end_frame();
869 }
870 let mut host = Host::new(&mut mem, &layout);
871 assert_eq!(host.frames()[0].opcodes().next().unwrap().a(), 5);
872 }
873
874 #[test]
875 fn icon_vocabulary_names_are_canonical_and_unique() {
876 use crate::constants::icon;
877 use alloc::collections::BTreeSet;
878
879 let mut seen = BTreeSet::new();
880 for name in icon::NAMES {
881 assert!(icon::is_canonical(name), "{} not canonical", name);
882 assert!(seen.insert(name), "duplicate canonical icon {}", name);
883 }
884 for name in [
886 icon::HOME,
887 icon::SETTINGS,
888 icon::PLAY,
889 icon::SKIP_FORWARD,
890 icon::VOLUME_MUTE,
891 icon::FILTER,
892 ] {
893 assert!(icon::is_canonical(name));
894 }
895 assert!(!icon::is_canonical("definitely-not-an-icon"));
896 }
897}