use blitz_dom::NodeId;
use std::cell::Cell;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use blitz_dom::BaseDocument;
use boa_engine::object::JsObject;
use boa_engine::{Finalize, JsData, Trace};
use rustc_hash::FxHashMap;
use crate::timers::TimerQueue;
pub(crate) struct DomProtos {
pub node: JsObject,
pub element: JsObject,
pub character_data: JsObject,
pub document: JsObject,
pub event: JsObject,
pub style: JsObject,
}
#[derive(Clone)]
pub(crate) struct Listener {
pub callback: JsObject,
pub capture: bool,
pub once: bool,
}
pub(crate) type ListenerMap = HashMap<String, Vec<Listener>>;
pub(crate) type IpcHandler = std::rc::Rc<dyn Fn(String)>;
#[derive(Default)]
pub(crate) struct RuntimeState {
pub protos: Option<DomProtos>,
pub node_wrappers: FxHashMap<NodeId, JsObject>,
pub dataset_wrappers: FxHashMap<NodeId, JsObject>,
pub class_list_wrappers: FxHashMap<NodeId, JsObject>,
pub node_listeners: FxHashMap<NodeId, ListenerMap>,
pub window_listeners: ListenerMap,
pub pointer_capture: FxHashMap<u64, NodeId>,
pub ipc_handler: Option<IpcHandler>,
pub timers: TimerQueue,
pub custom_element_definitions: FxHashMap<String, JsObject>,
}
impl RuntimeState {
pub fn protos(&self) -> &DomProtos {
self.protos
.as_ref()
.expect("DOM prototypes not initialised")
}
}
#[derive(Clone, Trace, Finalize, JsData)]
pub(crate) struct DomCtx {
#[unsafe_ignore_trace]
pub doc: Rc<RefCell<BaseDocument>>,
#[unsafe_ignore_trace]
pub state: Rc<RefCell<RuntimeState>>,
#[unsafe_ignore_trace]
pub layout_dirty: Rc<std::cell::Cell<bool>>,
#[unsafe_ignore_trace]
deep_profiling: Rc<Cell<bool>>,
#[unsafe_ignore_trace]
profiling_boundary_depth: Rc<Cell<u32>>,
}
impl DomCtx {
pub fn new(doc: Rc<RefCell<BaseDocument>>) -> Self {
Self {
doc,
state: Rc::new(RefCell::new(RuntimeState::default())),
layout_dirty: Rc::new(std::cell::Cell::new(true)),
deep_profiling: Rc::new(Cell::new(false)),
profiling_boundary_depth: Rc::new(Cell::new(0)),
}
}
pub(crate) fn enter_profiling_boundary(&self) -> ProfilingBoundary {
let depth = self.profiling_boundary_depth.get();
if depth == 0 {
self.deep_profiling
.set(blitz_traits::profiling::deep_profiling_enabled());
}
self.profiling_boundary_depth.set(depth + 1);
ProfilingBoundary {
deep_profiling: Rc::clone(&self.deep_profiling),
depth: Rc::clone(&self.profiling_boundary_depth),
}
}
#[cfg(feature = "dom-stats")]
pub(crate) fn deep_profiling_enabled(&self) -> bool {
self.deep_profiling.get()
}
}
pub(crate) struct ProfilingBoundary {
deep_profiling: Rc<Cell<bool>>,
depth: Rc<Cell<u32>>,
}
impl ProfilingBoundary {
pub(crate) fn enabled(&self) -> bool {
self.deep_profiling.get()
}
}
impl Drop for ProfilingBoundary {
fn drop(&mut self) {
let depth = self.depth.get().saturating_sub(1);
self.depth.set(depth);
if depth == 0 {
self.deep_profiling.set(false);
}
}
}
impl DomCtx {
pub fn mark_layout_dirty(&self) {
self.layout_dirty.set(true);
}
pub fn mutate_doc(&self) -> std::cell::RefMut<'_, BaseDocument> {
self.layout_dirty.set(true);
let doc = self.doc.borrow_mut();
doc.shell_provider.request_redraw();
doc
}
pub fn flush_layout(&self) {
if !self.layout_dirty.replace(false) {
return;
}
let _t = crate::script_stats::Timed::new(self, "layout:flush_from_script");
if let Ok(mut doc) = self.doc.try_borrow_mut() {
doc.resolve(0.0);
} else {
self.layout_dirty.set(true);
}
}
}