Implements the event dispatch infrastructure enabling JS scripts to register event listeners and respond to click events with DOM-spec-compliant bubbling. Key additions: - Function expression parsing (anonymous and named) in js_parser - Function identity via monotonic u64 IDs on JsFunction for removeEventListener - call_function_with_host on JsVm that isolates handler errors (stays Primed, never Failed) - EventListenerRegistry with addEventListener/removeEventListener deduplication - DomEvent model with preventDefault/stopPropagation support - Event dispatch algorithm: target phase + bubble phase with listener snapshots - 11 integration tests covering click handlers, bubbling, error recovery, and event properties Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
127 lines
3.4 KiB
Rust
127 lines
3.4 KiB
Rust
use crate::event_target::EventTargetId;
|
|
|
|
/// Phase of event dispatch.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum EventPhase {
|
|
None = 0,
|
|
Capture = 1,
|
|
Target = 2,
|
|
Bubble = 3,
|
|
}
|
|
|
|
/// Result of dispatching an event.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct DispatchResult {
|
|
pub default_prevented: bool,
|
|
}
|
|
|
|
/// A DOM event being dispatched.
|
|
#[derive(Debug, Clone)]
|
|
pub struct DomEvent {
|
|
pub event_type: String,
|
|
pub target: EventTargetId,
|
|
pub current_target: EventTargetId,
|
|
pub phase: EventPhase,
|
|
pub bubbles: bool,
|
|
pub cancelable: bool,
|
|
pub default_prevented: bool,
|
|
pub propagation_stopped: bool,
|
|
}
|
|
|
|
impl DomEvent {
|
|
/// Create a click event targeting the given node.
|
|
pub fn click(target: EventTargetId) -> Self {
|
|
Self {
|
|
event_type: "click".into(),
|
|
target,
|
|
current_target: target,
|
|
phase: EventPhase::None,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
default_prevented: false,
|
|
propagation_stopped: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use shared::NodeId;
|
|
|
|
#[test]
|
|
fn click_event_defaults() {
|
|
let target = EventTargetId::Node(NodeId::new(1));
|
|
let event = DomEvent::click(target);
|
|
assert_eq!(event.event_type, "click");
|
|
assert_eq!(event.target, target);
|
|
assert!(event.bubbles);
|
|
assert!(event.cancelable);
|
|
assert!(!event.default_prevented);
|
|
assert!(!event.propagation_stopped);
|
|
assert_eq!(event.phase, EventPhase::None);
|
|
}
|
|
|
|
#[test]
|
|
fn prevent_default_and_stop_propagation() {
|
|
let target = EventTargetId::Node(NodeId::new(1));
|
|
let mut event = DomEvent::click(target);
|
|
event.default_prevented = true;
|
|
event.propagation_stopped = true;
|
|
assert!(event.default_prevented);
|
|
assert!(event.propagation_stopped);
|
|
}
|
|
|
|
// --- EventPhase discriminant values match the DOM spec ---
|
|
|
|
#[test]
|
|
fn event_phase_discriminant_values() {
|
|
assert_eq!(EventPhase::None as u8, 0);
|
|
assert_eq!(EventPhase::Capture as u8, 1);
|
|
assert_eq!(EventPhase::Target as u8, 2);
|
|
assert_eq!(EventPhase::Bubble as u8, 3);
|
|
}
|
|
|
|
// --- EventPhase equality ---
|
|
|
|
#[test]
|
|
fn event_phase_equality() {
|
|
assert_eq!(EventPhase::Target, EventPhase::Target);
|
|
assert_ne!(EventPhase::Capture, EventPhase::Bubble);
|
|
}
|
|
|
|
// --- DomEvent::click sets current_target == target initially ---
|
|
|
|
#[test]
|
|
fn click_event_current_target_equals_target() {
|
|
let target = EventTargetId::Node(NodeId::new(5));
|
|
let event = DomEvent::click(target);
|
|
assert_eq!(event.current_target, event.target);
|
|
}
|
|
|
|
// --- DispatchResult default_prevented field ---
|
|
|
|
#[test]
|
|
fn dispatch_result_fields() {
|
|
let r = super::DispatchResult {
|
|
default_prevented: true,
|
|
};
|
|
assert!(r.default_prevented);
|
|
let r2 = super::DispatchResult {
|
|
default_prevented: false,
|
|
};
|
|
assert!(!r2.default_prevented);
|
|
}
|
|
|
|
// --- DomEvent for a Document target ---
|
|
|
|
#[test]
|
|
fn click_event_for_document_target() {
|
|
let target = EventTargetId::Document;
|
|
let event = DomEvent::click(target);
|
|
assert_eq!(event.target, EventTargetId::Document);
|
|
assert_eq!(event.current_target, EventTargetId::Document);
|
|
assert_eq!(event.event_type, "click");
|
|
}
|
|
}
|