⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/browser/
dom_bindings.rs

1//! DOM API Bindings for JavaScript
2//!
3//! Bridges the JS VM with the DOM tree, providing document.getElementById,
4//! element manipulation, event listener registration, timers (setTimeout/
5//! setInterval), and console output.
6
7#![allow(dead_code)]
8
9use alloc::{
10    collections::BTreeMap,
11    string::{String, ToString},
12    vec::Vec,
13};
14
15use super::events::{EventDispatcher, EventType, NodeId};
16
17// ---------------------------------------------------------------------------
18// Timer system
19// ---------------------------------------------------------------------------
20
21/// Timer entry for setTimeout/setInterval
22#[derive(Debug, Clone)]
23pub struct TimerEntry {
24    /// Unique timer ID
25    pub id: u32,
26    /// JS callback function ID
27    pub callback_id: usize,
28    /// Tick at which this timer fires
29    pub fire_at: u64,
30    /// Repeat interval (0 = one-shot)
31    pub interval: u64,
32    /// Whether this timer has been cancelled
33    pub cancelled: bool,
34}
35
36/// Timer queue managing setTimeout/setInterval
37pub struct TimerQueue {
38    /// All scheduled timers
39    timers: Vec<TimerEntry>,
40    /// Next timer ID
41    next_id: u32,
42    /// Current tick counter
43    current_tick: u64,
44}
45
46impl Default for TimerQueue {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl TimerQueue {
53    pub fn new() -> Self {
54        Self {
55            timers: Vec::new(),
56            next_id: 1,
57            current_tick: 0,
58        }
59    }
60
61    /// Schedule a one-shot timer (setTimeout).
62    /// Returns the timer ID.
63    pub fn set_timeout(&mut self, callback_id: usize, delay_ticks: u64) -> u32 {
64        let id = self.next_id;
65        self.next_id += 1;
66        self.timers.push(TimerEntry {
67            id,
68            callback_id,
69            fire_at: self.current_tick + delay_ticks,
70            interval: 0,
71            cancelled: false,
72        });
73        id
74    }
75
76    /// Schedule a repeating timer (setInterval).
77    /// Returns the timer ID.
78    pub fn set_interval(&mut self, callback_id: usize, interval_ticks: u64) -> u32 {
79        let id = self.next_id;
80        self.next_id += 1;
81        self.timers.push(TimerEntry {
82            id,
83            callback_id,
84            fire_at: self.current_tick + interval_ticks,
85            interval: interval_ticks,
86            cancelled: false,
87        });
88        id
89    }
90
91    /// Cancel a timer by ID
92    pub fn clear_timer(&mut self, timer_id: u32) -> bool {
93        for timer in &mut self.timers {
94            if timer.id == timer_id {
95                timer.cancelled = true;
96                return true;
97            }
98        }
99        false
100    }
101
102    /// Advance the tick counter and return expired callback IDs
103    pub fn tick(&mut self) -> Vec<usize> {
104        self.current_tick += 1;
105        let mut expired = Vec::new();
106
107        let mut i = 0;
108        while i < self.timers.len() {
109            if self.timers[i].cancelled {
110                self.timers.swap_remove(i);
111                continue;
112            }
113            if self.timers[i].fire_at <= self.current_tick {
114                expired.push(self.timers[i].callback_id);
115                if self.timers[i].interval > 0 {
116                    // Reschedule repeating timer
117                    self.timers[i].fire_at = self.current_tick + self.timers[i].interval;
118                    i += 1;
119                } else {
120                    self.timers.swap_remove(i);
121                }
122            } else {
123                i += 1;
124            }
125        }
126
127        expired
128    }
129
130    /// Check how many timers are pending
131    pub fn pending_count(&self) -> usize {
132        self.timers.iter().filter(|t| !t.cancelled).count()
133    }
134
135    /// Current tick value
136    pub fn current_tick(&self) -> u64 {
137        self.current_tick
138    }
139}
140
141// ---------------------------------------------------------------------------
142// DOM node reference (simplified)
143// ---------------------------------------------------------------------------
144
145/// Simplified DOM node for JS bindings
146#[derive(Debug, Clone, Default)]
147pub struct DomNode {
148    /// Node ID
149    pub id: NodeId,
150    /// Tag name (e.g., "div", "p", "span")
151    pub tag: String,
152    /// Element ID attribute
153    pub element_id: String,
154    /// Class names
155    pub class_list: Vec<String>,
156    /// Text content
157    pub text_content: String,
158    /// Inner HTML (raw text representation)
159    pub inner_html_content: String,
160    /// Attributes
161    pub attributes: BTreeMap<String, String>,
162    /// Child node IDs
163    pub children: Vec<NodeId>,
164    /// Parent node ID
165    pub parent: Option<NodeId>,
166    /// Inline style properties
167    pub style: BTreeMap<String, String>,
168}
169
170impl DomNode {
171    pub fn new(id: NodeId, tag: &str) -> Self {
172        Self {
173            id,
174            tag: tag.to_string(),
175            ..Default::default()
176        }
177    }
178}
179
180// ---------------------------------------------------------------------------
181// DOM API
182// ---------------------------------------------------------------------------
183
184/// Bridge between JS VM and DOM tree
185pub struct DomApi {
186    /// All DOM nodes (arena)
187    nodes: Vec<DomNode>,
188    /// ID-to-NodeId mapping
189    id_map: BTreeMap<String, NodeId>,
190    /// Event dispatcher
191    pub event_dispatcher: EventDispatcher,
192    /// Timer queue
193    pub timer_queue: TimerQueue,
194    /// Console output
195    pub console_output: Vec<String>,
196    /// Navigation requests (URLs to load)
197    pub navigation_requests: Vec<String>,
198}
199
200impl Default for DomApi {
201    fn default() -> Self {
202        Self::new()
203    }
204}
205
206impl DomApi {
207    pub fn new() -> Self {
208        // Create document root
209        let root = DomNode::new(0, "html");
210        Self {
211            nodes: alloc::vec![root],
212            id_map: BTreeMap::new(),
213            event_dispatcher: EventDispatcher::new(),
214            timer_queue: TimerQueue::new(),
215            console_output: Vec::new(),
216            navigation_requests: Vec::new(),
217        }
218    }
219
220    /// Get a node by ID
221    pub fn get_node(&self, id: NodeId) -> Option<&DomNode> {
222        self.nodes.get(id)
223    }
224
225    /// Get a mutable node by ID
226    pub fn get_node_mut(&mut self, id: NodeId) -> Option<&mut DomNode> {
227        self.nodes.get_mut(id)
228    }
229
230    /// Total number of nodes
231    pub fn node_count(&self) -> usize {
232        self.nodes.len()
233    }
234
235    // -- document methods --
236
237    /// document.getElementById(id) -> NodeId or None
238    pub fn get_element_by_id(&self, element_id: &str) -> Option<NodeId> {
239        self.id_map.get(element_id).copied()
240    }
241
242    /// document.createElement(tag) -> NodeId
243    pub fn create_element(&mut self, tag: &str) -> NodeId {
244        let id = self.nodes.len();
245        self.nodes.push(DomNode::new(id, tag));
246        id
247    }
248
249    /// document.createTextNode(text) -> NodeId
250    pub fn create_text_node(&mut self, text: &str) -> NodeId {
251        let id = self.nodes.len();
252        let mut node = DomNode::new(id, "#text");
253        node.text_content = text.to_string();
254        self.nodes.push(node);
255        id
256    }
257
258    /// Simple querySelector by tag name (first match)
259    pub fn query_selector(&self, selector: &str) -> Option<NodeId> {
260        if let Some(id_part) = selector.strip_prefix('#') {
261            return self.get_element_by_id(id_part);
262        }
263        for node in &self.nodes {
264            if node.tag == selector {
265                return Some(node.id);
266            }
267        }
268        None
269    }
270
271    // -- element methods --
272
273    /// element.getAttribute(name)
274    pub fn get_attribute(&self, node_id: NodeId, name: &str) -> Option<String> {
275        self.nodes
276            .get(node_id)
277            .and_then(|n| n.attributes.get(name).cloned())
278    }
279
280    /// element.setAttribute(name, value)
281    pub fn set_attribute(&mut self, node_id: NodeId, name: &str, value: &str) {
282        if let Some(node) = self.nodes.get_mut(node_id) {
283            node.attributes.insert(name.to_string(), value.to_string());
284            if name == "id" {
285                let old_id = node.element_id.clone();
286                if !old_id.is_empty() {
287                    self.id_map.remove(&old_id);
288                }
289                node.element_id = value.to_string();
290                self.id_map.insert(value.to_string(), node_id);
291            }
292        }
293    }
294
295    /// element.appendChild(child)
296    pub fn append_child(&mut self, parent_id: NodeId, child_id: NodeId) -> bool {
297        if parent_id >= self.nodes.len() || child_id >= self.nodes.len() {
298            return false;
299        }
300        if parent_id == child_id {
301            return false;
302        }
303
304        // Remove from old parent
305        if let Some(old_parent) = self.nodes[child_id].parent {
306            if let Some(parent_node) = self.nodes.get_mut(old_parent) {
307                parent_node.children.retain(|&c| c != child_id);
308            }
309        }
310
311        self.nodes[parent_id].children.push(child_id);
312        self.nodes[child_id].parent = Some(parent_id);
313
314        self.event_dispatcher
315            .node_tree_mut()
316            .set_parent(child_id, parent_id);
317
318        true
319    }
320
321    /// element.removeChild(child)
322    pub fn remove_child(&mut self, parent_id: NodeId, child_id: NodeId) -> bool {
323        if let Some(parent) = self.nodes.get_mut(parent_id) {
324            let before = parent.children.len();
325            parent.children.retain(|&c| c != child_id);
326            if parent.children.len() < before {
327                if let Some(child) = self.nodes.get_mut(child_id) {
328                    child.parent = None;
329                }
330                return true;
331            }
332        }
333        false
334    }
335
336    /// element.textContent (getter)
337    pub fn get_text_content(&self, node_id: NodeId) -> String {
338        self.nodes
339            .get(node_id)
340            .map(|n| n.text_content.clone())
341            .unwrap_or_default()
342    }
343
344    /// element.textContent = value (setter)
345    pub fn set_text_content(&mut self, node_id: NodeId, text: &str) {
346        if let Some(node) = self.nodes.get_mut(node_id) {
347            node.text_content = text.to_string();
348            node.children.clear();
349        }
350    }
351
352    /// element.innerHTML (getter)
353    pub fn get_inner_html(&self, node_id: NodeId) -> String {
354        self.nodes
355            .get(node_id)
356            .map(|n| n.inner_html_content.clone())
357            .unwrap_or_default()
358    }
359
360    /// element.innerHTML = value (setter)
361    pub fn set_inner_html(&mut self, node_id: NodeId, html: &str) {
362        if let Some(node) = self.nodes.get_mut(node_id) {
363            node.inner_html_content = html.to_string();
364            node.children.clear();
365        }
366    }
367
368    /// element.style.setProperty(name, value)
369    pub fn set_style_property(&mut self, node_id: NodeId, name: &str, value: &str) {
370        if let Some(node) = self.nodes.get_mut(node_id) {
371            node.style.insert(name.to_string(), value.to_string());
372        }
373    }
374
375    /// element.style.getPropertyValue(name)
376    pub fn get_style_property(&self, node_id: NodeId, name: &str) -> Option<String> {
377        self.nodes
378            .get(node_id)
379            .and_then(|n| n.style.get(name).cloned())
380    }
381
382    // -- Event listener bridge --
383
384    /// addEventListener(node, type, callback_id)
385    pub fn add_event_listener(
386        &mut self,
387        node_id: NodeId,
388        event_type: EventType,
389        callback_id: usize,
390    ) {
391        self.event_dispatcher
392            .add_event_listener(node_id, event_type, callback_id, false);
393    }
394
395    /// removeEventListener(node, type, callback_id)
396    pub fn remove_event_listener(
397        &mut self,
398        node_id: NodeId,
399        event_type: EventType,
400        callback_id: usize,
401    ) {
402        self.event_dispatcher
403            .remove_event_listener(node_id, event_type, callback_id, false);
404    }
405
406    // -- Console --
407
408    /// console.log(args...)
409    pub fn console_log(&mut self, message: &str) {
410        self.console_output.push(message.to_string());
411    }
412
413    /// console.error(args...)
414    pub fn console_error(&mut self, message: &str) {
415        self.console_output
416            .push(alloc::format!("[ERROR] {}", message));
417    }
418
419    // -- window stubs --
420
421    /// window.alert(message) -- stub
422    pub fn window_alert(&mut self, message: &str) {
423        self.console_output
424            .push(alloc::format!("[ALERT] {}", message));
425    }
426
427    /// Register a node's element ID in the lookup map
428    pub fn register_element_id(&mut self, node_id: NodeId, element_id: &str) {
429        if let Some(node) = self.nodes.get_mut(node_id) {
430            node.element_id = element_id.to_string();
431        }
432        self.id_map.insert(element_id.to_string(), node_id);
433    }
434}
435
436// ---------------------------------------------------------------------------
437// Tests
438// ---------------------------------------------------------------------------
439
440#[cfg(test)]
441mod tests {
442    use alloc::{string::ToString, vec};
443
444    use super::*;
445
446    #[test]
447    fn test_dom_api_new() {
448        let api = DomApi::new();
449        assert_eq!(api.node_count(), 1);
450        assert_eq!(api.get_node(0).unwrap().tag, "html");
451    }
452
453    #[test]
454    fn test_create_element() {
455        let mut api = DomApi::new();
456        let div = api.create_element("div");
457        assert_eq!(div, 1);
458        assert_eq!(api.get_node(div).unwrap().tag, "div");
459    }
460
461    #[test]
462    fn test_create_text_node() {
463        let mut api = DomApi::new();
464        let text = api.create_text_node("Hello");
465        assert_eq!(api.get_node(text).unwrap().text_content, "Hello");
466    }
467
468    #[test]
469    fn test_get_element_by_id() {
470        let mut api = DomApi::new();
471        let div = api.create_element("div");
472        api.register_element_id(div, "main");
473        assert_eq!(api.get_element_by_id("main"), Some(div));
474        assert_eq!(api.get_element_by_id("nonexistent"), None);
475    }
476
477    #[test]
478    fn test_set_attribute() {
479        let mut api = DomApi::new();
480        let div = api.create_element("div");
481        api.set_attribute(div, "class", "container");
482        assert_eq!(
483            api.get_attribute(div, "class"),
484            Some("container".to_string())
485        );
486    }
487
488    #[test]
489    fn test_set_attribute_id() {
490        let mut api = DomApi::new();
491        let div = api.create_element("div");
492        api.set_attribute(div, "id", "my-div");
493        assert_eq!(api.get_element_by_id("my-div"), Some(div));
494    }
495
496    #[test]
497    fn test_append_child() {
498        let mut api = DomApi::new();
499        let parent = api.create_element("div");
500        let child = api.create_element("span");
501        assert!(api.append_child(parent, child));
502        assert_eq!(api.get_node(parent).unwrap().children, vec![child]);
503        assert_eq!(api.get_node(child).unwrap().parent, Some(parent));
504    }
505
506    #[test]
507    fn test_remove_child() {
508        let mut api = DomApi::new();
509        let parent = api.create_element("div");
510        let child = api.create_element("span");
511        api.append_child(parent, child);
512        assert!(api.remove_child(parent, child));
513        assert!(api.get_node(parent).unwrap().children.is_empty());
514        assert_eq!(api.get_node(child).unwrap().parent, None);
515    }
516
517    #[test]
518    fn test_text_content() {
519        let mut api = DomApi::new();
520        let div = api.create_element("div");
521        api.set_text_content(div, "Hello World");
522        assert_eq!(api.get_text_content(div), "Hello World");
523    }
524
525    #[test]
526    fn test_inner_html() {
527        let mut api = DomApi::new();
528        let div = api.create_element("div");
529        api.set_inner_html(div, "<p>Test</p>");
530        assert_eq!(api.get_inner_html(div), "<p>Test</p>");
531    }
532
533    #[test]
534    fn test_style_property() {
535        let mut api = DomApi::new();
536        let div = api.create_element("div");
537        api.set_style_property(div, "color", "red");
538        assert_eq!(
539            api.get_style_property(div, "color"),
540            Some("red".to_string())
541        );
542        assert_eq!(api.get_style_property(div, "margin"), None);
543    }
544
545    #[test]
546    fn test_query_selector_tag() {
547        let mut api = DomApi::new();
548        let _div = api.create_element("div");
549        let p = api.create_element("p");
550        assert_eq!(api.query_selector("p"), Some(p));
551    }
552
553    #[test]
554    fn test_query_selector_id() {
555        let mut api = DomApi::new();
556        let div = api.create_element("div");
557        api.register_element_id(div, "main");
558        assert_eq!(api.query_selector("#main"), Some(div));
559    }
560
561    #[test]
562    fn test_console_log() {
563        let mut api = DomApi::new();
564        api.console_log("test message");
565        assert_eq!(api.console_output.len(), 1);
566        assert_eq!(api.console_output[0], "test message");
567    }
568
569    #[test]
570    fn test_console_error() {
571        let mut api = DomApi::new();
572        api.console_error("oh no");
573        assert_eq!(api.console_output[0], "[ERROR] oh no");
574    }
575
576    #[test]
577    fn test_window_alert() {
578        let mut api = DomApi::new();
579        api.window_alert("hello");
580        assert_eq!(api.console_output[0], "[ALERT] hello");
581    }
582
583    #[test]
584    fn test_timer_set_timeout() {
585        let mut tq = TimerQueue::new();
586        let id = tq.set_timeout(42, 5);
587        assert_eq!(id, 1);
588        assert_eq!(tq.pending_count(), 1);
589    }
590
591    #[test]
592    fn test_timer_fires() {
593        let mut tq = TimerQueue::new();
594        tq.set_timeout(42, 3);
595        assert!(tq.tick().is_empty());
596        assert!(tq.tick().is_empty());
597        let expired = tq.tick();
598        assert_eq!(expired, vec![42]);
599        assert_eq!(tq.pending_count(), 0);
600    }
601
602    #[test]
603    fn test_timer_interval() {
604        let mut tq = TimerQueue::new();
605        tq.set_interval(99, 2);
606        assert!(tq.tick().is_empty());
607        let e2 = tq.tick();
608        assert_eq!(e2, vec![99]);
609        assert!(tq.tick().is_empty());
610        let e4 = tq.tick();
611        assert_eq!(e4, vec![99]);
612    }
613
614    #[test]
615    fn test_timer_cancel() {
616        let mut tq = TimerQueue::new();
617        let id = tq.set_timeout(42, 5);
618        assert!(tq.clear_timer(id));
619        for _ in 0..10 {
620            assert!(tq.tick().is_empty());
621        }
622    }
623
624    #[test]
625    fn test_timer_cancel_nonexistent() {
626        let mut tq = TimerQueue::new();
627        assert!(!tq.clear_timer(999));
628    }
629
630    #[test]
631    fn test_timer_queue_default() {
632        let tq = TimerQueue::default();
633        assert_eq!(tq.pending_count(), 0);
634        assert_eq!(tq.current_tick(), 0);
635    }
636
637    #[test]
638    fn test_append_child_self() {
639        let mut api = DomApi::new();
640        let div = api.create_element("div");
641        assert!(!api.append_child(div, div));
642    }
643
644    #[test]
645    fn test_reparent_child() {
646        let mut api = DomApi::new();
647        let p1 = api.create_element("div");
648        let p2 = api.create_element("div");
649        let child = api.create_element("span");
650        api.append_child(p1, child);
651        api.append_child(p2, child);
652        assert!(api.get_node(p1).unwrap().children.is_empty());
653        assert_eq!(api.get_node(p2).unwrap().children, vec![child]);
654    }
655
656    #[test]
657    fn test_add_event_listener_via_api() {
658        let mut api = DomApi::new();
659        let div = api.create_element("div");
660        api.add_event_listener(div, EventType::Click, 100);
661        assert_eq!(api.event_dispatcher.listeners_for(div).len(), 1);
662    }
663
664    #[test]
665    fn test_remove_event_listener_via_api() {
666        let mut api = DomApi::new();
667        let div = api.create_element("div");
668        api.add_event_listener(div, EventType::Click, 100);
669        api.remove_event_listener(div, EventType::Click, 100);
670        assert_eq!(api.event_dispatcher.listeners_for(div).len(), 0);
671    }
672
673    #[test]
674    fn test_dom_node_default() {
675        let node = DomNode::default();
676        assert!(node.tag.is_empty());
677        assert!(node.children.is_empty());
678        assert!(node.parent.is_none());
679    }
680}