1use super::*;
4use crate::os_lib::layout::intersection_observer::{
5 compute_intersection, should_trigger_callback, IntersectionRect, RootMargin,
6};
7use crate::os_lib::layout::resize_observer::{
8 compute_resize_boxes, has_size_changed, ResizeObserverBoxOptions,
9};
10
11pub(crate) fn make_dom_rect_f64(r: &IntersectionRect) -> ObjRef {
14 let o = Obj::plain();
15 {
16 let mut b = o.borrow_mut();
17 b.props.insert("x".into(), Value::Number(r.x));
18 b.props.insert("left".into(), Value::Number(r.x));
19 b.props.insert("y".into(), Value::Number(r.y));
20 b.props.insert("top".into(), Value::Number(r.y));
21 b.props.insert("width".into(), Value::Number(r.width));
22 b.props.insert("height".into(), Value::Number(r.height));
23 b.props.insert("right".into(), Value::Number(r.right()));
24 b.props.insert("bottom".into(), Value::Number(r.bottom()));
25 }
26 o
27}
28
29pub(crate) fn compute_io_entry(
31 it: &mut Interp,
32 target: Value,
33 root_val: &Value,
34 root_margin: &RootMargin,
35) -> (Value, f64, bool) {
36 let target_idx = this_dom_idx(&target);
37 let (tx, ty, tw, th) = target_idx
38 .and_then(|idx| it.dom.borrow().get_rect(idx))
39 .unwrap_or((0, 0, 0, 0));
40 let target_rect = IntersectionRect::new(tx as f64, ty as f64, tw as f64, th as f64);
41
42 let root_rect = if let Some(r_idx) = this_dom_idx(root_val) {
43 let (rx, ry, rw, rh) = it.dom.borrow().get_rect(r_idx).unwrap_or((0, 0, 0, 0));
44 IntersectionRect::new(rx as f64, ry as f64, rw as f64, rh as f64)
45 } else {
46 let global_vars = it.global.borrow();
47 let vw = global_vars
48 .vars
49 .get("innerWidth")
50 .and_then(|v| match *v {
51 Value::Number(n) => Some(n),
52 _ => None,
53 })
54 .unwrap_or(800.0);
55 let vh = global_vars
56 .vars
57 .get("innerHeight")
58 .and_then(|v| match *v {
59 Value::Number(n) => Some(n),
60 _ => None,
61 })
62 .unwrap_or(600.0);
63 IntersectionRect::new(0.0, 0.0, vw, vh)
64 };
65
66 let state = compute_intersection(&target_rect, &root_rect, root_margin);
67
68 let entry = Obj::plain();
69 {
70 let mut b = entry.borrow_mut();
71 b.props
72 .insert("isIntersecting".into(), Value::Bool(state.is_intersecting));
73 b.props
74 .insert("intersectionRatio".into(), Value::Number(state.intersection_ratio));
75 let bcr = Value::Object(make_dom_rect_f64(&state.bounding_client_rect));
76 b.props.insert("boundingClientRect".into(), bcr.clone());
77 let ir = if state.is_intersecting
78 && state.bounding_client_rect == state.intersection_rect
79 {
80 bcr
81 } else {
82 Value::Object(make_dom_rect_f64(&state.intersection_rect))
83 };
84 b.props.insert("intersectionRect".into(), ir);
85 let root_bounds = if this_dom_idx(root_val).is_some() {
86 match &state.root_bounds {
87 Some(rb) => Value::Object(make_dom_rect_f64(rb)),
88 None => Value::Null,
89 }
90 } else {
91 Value::Null
92 };
93 b.props.insert("rootBounds".into(), root_bounds);
94 b.props
95 .insert("time".into(), Value::Number(next_perf_timestamp()));
96 b.props.insert("target".into(), target);
97 }
98 (Value::Object(entry), state.intersection_ratio, state.is_intersecting)
99}
100
101pub(crate) fn make_io_entry(it: &mut Interp, target: Value) -> Value {
103 let (entry, _, _) = compute_io_entry(it, target, &Value::Null, &RootMargin::zero());
104 entry
105}
106
107pub(crate) fn io_observe(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
108 let Value::Object(self_obj) = &this else { return Ok(Value::Undefined) };
109 let obs_id = match self_obj.borrow().props.get("_id") {
110 Some(Value::Number(n)) => *n as u32,
111 _ => return Ok(Value::Undefined),
112 };
113 let target = arg(a, 0);
114 let target_idx = match this_dom_idx(&target) {
115 Some(idx) => idx,
116 None => return Ok(Value::Undefined),
117 };
118
119 let (cb, root_val, root_margin) = {
120 let dom = it.dom.borrow();
121 match dom.io_observers.iter().find(|o| o.id == obs_id) {
122 Some(o) => (o.callback.clone(), o.root.clone(), o.root_margin),
123 None => return Ok(Value::Undefined),
124 }
125 };
126
127 let (entry, ratio, is_intersecting) =
129 compute_io_entry(it, target.clone(), &root_val, &root_margin);
130
131 {
133 let mut dom = it.dom.borrow_mut();
134 if let Some(obs) = dom.io_observers.iter_mut().find(|o| o.id == obs_id) {
135 obs.targets.retain(|t| t.target_idx != target_idx);
136 obs.targets.push(super::super::dom_bridge::ObservedTarget {
137 target: target.clone(),
138 target_idx,
139 prev_ratio: Some(ratio),
140 prev_is_intersecting: is_intersecting,
141 });
142 }
143 }
144
145 if matches!(cb, Value::Object(_)) {
147 let entries = Value::Object(Obj::array(alloc::vec![entry]));
148 it.call_listener(
149 &cb,
150 Value::Undefined,
151 &[entries, this],
152 "IntersectionObserver callback",
153 );
154 }
155
156 Ok(Value::Undefined)
157}
158
159pub(crate) fn io_unobserve(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
160 let Value::Object(self_obj) = &this else { return Ok(Value::Undefined) };
161 let obs_id = match self_obj.borrow().props.get("_id") {
162 Some(Value::Number(n)) => *n as u32,
163 _ => return Ok(Value::Undefined),
164 };
165 let target = arg(a, 0);
166 if let Some(target_idx) = this_dom_idx(&target) {
167 let mut dom = it.dom.borrow_mut();
168 if let Some(obs) = dom.io_observers.iter_mut().find(|o| o.id == obs_id) {
169 obs.targets.retain(|t| t.target_idx != target_idx);
170 }
171 }
172 Ok(Value::Undefined)
173}
174
175pub(crate) fn io_disconnect(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
176 let Value::Object(self_obj) = &this else { return Ok(Value::Undefined) };
177 let obs_id = match self_obj.borrow().props.get("_id") {
178 Some(Value::Number(n)) => *n as u32,
179 _ => return Ok(Value::Undefined),
180 };
181 let mut dom = it.dom.borrow_mut();
182 if let Some(obs) = dom.io_observers.iter_mut().find(|o| o.id == obs_id) {
183 obs.targets.clear();
184 obs.queued_records.clear();
185 }
186 Ok(Value::Undefined)
187}
188
189pub(crate) fn io_take_records(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
190 let Value::Object(self_obj) = &this else {
191 return Ok(Value::Object(Obj::array(alloc::vec![])));
192 };
193 let obs_id = match self_obj.borrow().props.get("_id") {
194 Some(Value::Number(n)) => *n as u32,
195 _ => return Ok(Value::Object(Obj::array(alloc::vec![]))),
196 };
197 let records = {
198 let mut dom = it.dom.borrow_mut();
199 if let Some(obs) = dom.io_observers.iter_mut().find(|o| o.id == obs_id) {
200 core::mem::take(&mut obs.queued_records)
201 } else {
202 alloc::vec![]
203 }
204 };
205 Ok(Value::Object(Obj::array(records)))
206}
207
208pub(crate) fn intersection_observer_ctor(
209 it: &mut Interp,
210 _t: Value,
211 a: &[Value],
212) -> Result<Value, Value> {
213 let cb = arg(a, 0);
214 let options = arg(a, 1);
215 let root = obj_prop(&options, "root").unwrap_or(Value::Null);
216 let root_margin_str = obj_prop(&options, "rootMargin")
217 .map(|v| v.to_js_string())
218 .unwrap_or_else(|| String::from("0px 0px 0px 0px"));
219 let root_margin = RootMargin::parse(&root_margin_str).unwrap_or_else(|_| RootMargin::zero());
220
221 let threshold_val = obj_prop(&options, "threshold");
222 let thresholds: alloc::vec::Vec<f64> = match threshold_val {
223 Some(v) => {
224 let items = this_items(&v);
225 if items.is_empty() && !matches!(v, Value::Object(_)) {
226 alloc::vec![v.to_number()]
227 } else {
228 items.iter().map(|x| x.to_number()).collect()
229 }
230 }
231 None => alloc::vec![0.0],
232 };
233
234 let obs_id = {
235 let mut dom = it.dom.borrow_mut();
236 let id = dom.next_io_obs_id;
237 dom.next_io_obs_id += 1;
238 dom.io_observers.push(super::super::dom_bridge::IntersectionObserverReg {
239 id,
240 callback: cb.clone(),
241 root: root.clone(),
242 root_margin,
243 thresholds: thresholds.clone(),
244 targets: alloc::vec![],
245 queued_records: alloc::vec![],
246 });
247 id
248 };
249
250 let obs = Obj::plain();
251 {
252 let mut b = obs.borrow_mut();
253 b.props.insert("_cb".into(), cb);
254 b.props.insert("_id".into(), Value::Number(obs_id as f64));
255 b.props.insert("root".into(), root);
256 b.props.insert("rootMargin".into(), Value::str(root_margin_str));
257 b.props.insert(
258 "thresholds".into(),
259 Value::Object(Obj::array(thresholds.into_iter().map(Value::Number).collect())),
260 );
261 b.props.insert("observe".into(), nv("observe", io_observe));
262 b.props
263 .insert("unobserve".into(), nv("unobserve", io_unobserve));
264 b.props
265 .insert("disconnect".into(), nv("disconnect", io_disconnect));
266 b.props
267 .insert("takeRecords".into(), nv("takeRecords", io_take_records));
268 }
269 Ok(Value::Object(obs))
270}
271
272pub fn make_resolved_promise(val: Value) -> ObjRef {
274 use super::super::value::PromiseState;
275 let state = alloc::rc::Rc::new(core::cell::RefCell::new(PromiseState::pending()));
276 state.borrow_mut().status = super::super::value::PromiseStatus::Fulfilled;
277 state.borrow_mut().value = val;
278 let obj = Obj::plain();
279 obj.borrow_mut().kind = super::super::value::ObjKind::PromiseObj(state.clone());
280 let mut b = obj.borrow_mut();
282 b.props.insert("then".into(), nv("then", dom_noop));
283 b.props.insert("catch".into(), nv("catch", dom_noop));
284 b.props.insert("finally".into(), nv("finally", dom_noop));
285 drop(b);
286 obj
287}
288
289pub fn fire_intersection_observers(it: &mut Interp) {
291 let observers = {
292 let dom = it.dom.borrow();
293 dom.io_observers.clone()
294 };
295 if observers.is_empty() {
296 return;
297 }
298
299 for obs in observers {
300 if obs.targets.is_empty() || !matches!(obs.callback, Value::Object(_)) {
301 continue;
302 }
303
304 let mut triggered_entries = alloc::vec![];
305 let mut updated_targets = alloc::vec![];
306
307 for target_info in &obs.targets {
308 let (entry, ratio, is_intersecting) = compute_io_entry(
309 it,
310 target_info.target.clone(),
311 &obs.root,
312 &obs.root_margin,
313 );
314
315 if should_trigger_callback(
316 target_info.prev_ratio,
317 target_info.prev_is_intersecting,
318 ratio,
319 is_intersecting,
320 &obs.thresholds,
321 ) {
322 triggered_entries.push(entry);
323 updated_targets.push(super::super::dom_bridge::ObservedTarget {
324 target: target_info.target.clone(),
325 target_idx: target_info.target_idx,
326 prev_ratio: Some(ratio),
327 prev_is_intersecting: is_intersecting,
328 });
329 } else {
330 updated_targets.push(target_info.clone());
331 }
332 }
333
334 {
336 let mut dom = it.dom.borrow_mut();
337 if let Some(reg) = dom.io_observers.iter_mut().find(|o| o.id == obs.id) {
338 reg.targets = updated_targets;
339 }
340 }
341
342 if !triggered_entries.is_empty() {
343 let entries_val = Value::Object(Obj::array(triggered_entries));
344 let obs_obj = Value::Object(Obj::plain());
345 it.call_listener(
346 &obs.callback,
347 Value::Undefined,
348 &[entries_val, obs_obj],
349 "IntersectionObserver callback",
350 );
351 }
352 }
353}
354
355pub fn flush_mutation_observers(it: &mut Interp) {
358 let all_recs = drain_mut_records(it);
360 if all_recs.is_empty() {
361 return;
362 }
363 use alloc::collections::BTreeMap;
365 let mut groups: BTreeMap<u32, (Value, Value, alloc::vec::Vec<super::super::dom_bridge::MutRec>)> =
366 BTreeMap::new();
367 {
368 let dom = it.dom.borrow();
369 for (id, rec) in all_recs {
370 let (cb, obs_val) = dom
371 .mut_observers
372 .iter()
373 .find(|o| o.id == id)
374 .map(|o| (o.callback.clone(), o.observer.clone()))
375 .unwrap_or((Value::Undefined, Value::Undefined));
376 groups.entry(id).or_insert((cb, obs_val, alloc::vec![])).2.push(rec);
377 }
378 }
379 for (_id, (cb, obs_val, recs)) in groups {
380 if !matches!(cb, Value::Object(_)) {
381 continue;
382 }
383 let js_recs: alloc::vec::Vec<Value> =
384 recs.iter().map(|r| make_mut_record(it, r)).collect();
385 let arr = Value::Object(Obj::array(js_recs));
386 it.call_listener(&cb, Value::Undefined, &[arr, obs_val], "MutationObserver callback");
388 }
389}
390
391pub(crate) fn compute_ro_entry(
394 it: &mut Interp,
395 target: Value,
396 box_opt: ResizeObserverBoxOptions,
397) -> (Value, f64, f64) {
398 let target_idx = this_dom_idx(&target);
399 let (_, _, w, h) = target_idx
400 .and_then(|idx| it.dom.borrow().get_rect(idx))
401 .unwrap_or((0, 0, 0, 0));
402 let (bw, bh) = (w as f64, h as f64);
403
404 let (pt, pr, pb, pl, bt, br, bb, bl) = if let Some(idx) = target_idx {
406 let dom = it.dom.borrow();
407 let parse_px = |prop: &str| -> f64 {
408 let s = dom.get_computed(idx, prop);
409 s.trim_end_matches("px").parse::<f64>().unwrap_or(0.0)
410 };
411 (
412 parse_px("padding-top"),
413 parse_px("padding-right"),
414 parse_px("padding-bottom"),
415 parse_px("padding-left"),
416 parse_px("border-top-width"),
417 parse_px("border-right-width"),
418 parse_px("border-bottom-width"),
419 parse_px("border-left-width"),
420 )
421 } else {
422 (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
423 };
424
425 let dims = compute_resize_boxes(bw, bh, pt, pr, pb, pl, bt, br, bb, bl);
426
427 let entry = Obj::plain();
428 {
429 let mut b = entry.borrow_mut();
430 b.props.insert("target".into(), target);
431 let cr = Obj::plain();
432 {
433 let mut c = cr.borrow_mut();
434 c.props.insert("x".into(), Value::Number(dims.content_rect_x));
435 c.props.insert("y".into(), Value::Number(dims.content_rect_y));
436 c.props.insert("width".into(), Value::Number(dims.content_rect_width));
437 c.props.insert("height".into(), Value::Number(dims.content_rect_height));
438 c.props.insert("top".into(), Value::Number(dims.content_rect_y));
439 c.props.insert("left".into(), Value::Number(dims.content_rect_x));
440 c.props.insert(
441 "right".into(),
442 Value::Number(dims.content_rect_x + dims.content_rect_width),
443 );
444 c.props.insert(
445 "bottom".into(),
446 Value::Number(dims.content_rect_y + dims.content_rect_height),
447 );
448 }
449 b.props.insert("contentRect".into(), Value::Object(cr));
450
451 let make_size = |inline: f64, block: f64| {
452 let s = Obj::plain();
453 s.borrow_mut().props.insert("inlineSize".into(), Value::Number(inline));
454 s.borrow_mut().props.insert("blockSize".into(), Value::Number(block));
455 Value::Object(s)
456 };
457
458 b.props.insert(
459 "borderBoxSize".into(),
460 Value::Object(Obj::array(alloc::vec![make_size(
461 dims.border_box_inline_size,
462 dims.border_box_block_size,
463 )])),
464 );
465 b.props.insert(
466 "contentBoxSize".into(),
467 Value::Object(Obj::array(alloc::vec![make_size(
468 dims.content_box_inline_size,
469 dims.content_box_block_size,
470 )])),
471 );
472 b.props.insert(
473 "devicePixelContentBoxSize".into(),
474 Value::Object(Obj::array(alloc::vec![make_size(
475 dims.device_pixel_content_box_inline_size,
476 dims.device_pixel_content_box_block_size,
477 )])),
478 );
479 }
480
481 let (cur_inline, cur_block) = match box_opt {
482 ResizeObserverBoxOptions::BorderBox => (
483 dims.border_box_inline_size,
484 dims.border_box_block_size,
485 ),
486 ResizeObserverBoxOptions::DevicePixelContentBox => (
487 dims.device_pixel_content_box_inline_size,
488 dims.device_pixel_content_box_block_size,
489 ),
490 ResizeObserverBoxOptions::ContentBox => (
491 dims.content_box_inline_size,
492 dims.content_box_block_size,
493 ),
494 };
495
496 (Value::Object(entry), cur_inline, cur_block)
497}
498
499pub(crate) fn ro_observe(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
500 let Value::Object(self_obj) = &this else { return Ok(Value::Undefined) };
501 let obs_id = match self_obj.borrow().props.get("_id") {
502 Some(Value::Number(n)) => *n as u32,
503 _ => return Ok(Value::Undefined),
504 };
505 let cb = self_obj.borrow().props.get("_cb").cloned().unwrap_or(Value::Undefined);
506
507 let target = arg(a, 0);
508 let target_idx = match this_dom_idx(&target) {
509 Some(idx) => idx,
510 None => return Ok(Value::Undefined),
511 };
512
513 let options = arg(a, 1);
514 let box_str = obj_prop(&options, "box").map(|v| v.to_js_string()).unwrap_or_default();
515 let box_opt = ResizeObserverBoxOptions::parse(&box_str);
516
517 let (entry, inline_size, block_size) = compute_ro_entry(it, target.clone(), box_opt);
518
519 let already_observing = {
521 let mut dom = it.dom.borrow_mut();
522 if let Some(obs) = dom.ro_observers.iter_mut().find(|o| o.id == obs_id) {
523 let was = obs.targets.iter().any(|t| t.target_idx == target_idx);
524 obs.targets.retain(|t| t.target_idx != target_idx);
525 obs.targets.push(super::super::dom_bridge::ObservedResizeTarget {
526 target: target.clone(),
527 target_idx,
528 box_option: box_opt,
529 last_inline_size: inline_size,
530 last_block_size: block_size,
531 });
532 was
533 } else {
534 false
535 }
536 };
537
538 if !already_observing && matches!(cb, Value::Object(_)) {
540 let entries = Value::Object(Obj::array(alloc::vec![entry]));
541 it.call_listener(
542 &cb,
543 Value::Undefined,
544 &[entries, this],
545 "ResizeObserver callback",
546 );
547 }
548
549 Ok(Value::Undefined)
550}
551
552pub(crate) fn ro_unobserve(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
553 let Value::Object(self_obj) = &this else { return Ok(Value::Undefined) };
554 let obs_id = match self_obj.borrow().props.get("_id") {
555 Some(Value::Number(n)) => *n as u32,
556 _ => return Ok(Value::Undefined),
557 };
558 let target = arg(a, 0);
559 if let Some(target_idx) = this_dom_idx(&target) {
560 let mut dom = it.dom.borrow_mut();
561 if let Some(obs) = dom.ro_observers.iter_mut().find(|o| o.id == obs_id) {
562 obs.targets.retain(|t| t.target_idx != target_idx);
563 }
564 }
565 Ok(Value::Undefined)
566}
567
568pub(crate) fn ro_disconnect(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
569 let Value::Object(self_obj) = &this else { return Ok(Value::Undefined) };
570 let obs_id = match self_obj.borrow().props.get("_id") {
571 Some(Value::Number(n)) => *n as u32,
572 _ => return Ok(Value::Undefined),
573 };
574 let mut dom = it.dom.borrow_mut();
575 if let Some(obs) = dom.ro_observers.iter_mut().find(|o| o.id == obs_id) {
576 obs.targets.clear();
577 }
578 Ok(Value::Undefined)
579}
580
581pub(crate) fn resize_observer_ctor(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
582 let cb = arg(a, 0);
583 let obs_id = {
584 let mut dom = it.dom.borrow_mut();
585 let id = dom.next_ro_obs_id;
586 dom.next_ro_obs_id += 1;
587 dom.ro_observers.push(super::super::dom_bridge::ResizeObserverReg {
588 id,
589 callback: cb.clone(),
590 targets: alloc::vec![],
591 });
592 id
593 };
594
595 let obs = Obj::plain();
596 {
597 let mut b = obs.borrow_mut();
598 b.props.insert("_cb".into(), cb);
599 b.props.insert("_id".into(), Value::Number(obs_id as f64));
600 b.props.insert("observe".into(), nv("observe", ro_observe));
601 b.props
602 .insert("unobserve".into(), nv("unobserve", ro_unobserve));
603 b.props
604 .insert("disconnect".into(), nv("disconnect", ro_disconnect));
605 }
606 Ok(Value::Object(obs))
607}
608
609pub fn fire_resize_observers(it: &mut Interp) {
611 let observers = {
612 let dom = it.dom.borrow();
613 dom.ro_observers.clone()
614 };
615 if observers.is_empty() {
616 return;
617 }
618
619 for obs in observers {
620 if obs.targets.is_empty() || !matches!(obs.callback, Value::Object(_)) {
621 continue;
622 }
623
624 let mut triggered_entries = alloc::vec![];
625 let mut updated_targets = alloc::vec![];
626
627 for target_info in &obs.targets {
628 let (entry, inline_size, block_size) = compute_ro_entry(
629 it,
630 target_info.target.clone(),
631 target_info.box_option,
632 );
633
634 if has_size_changed(
635 target_info.last_inline_size,
636 target_info.last_block_size,
637 inline_size,
638 block_size,
639 ) {
640 triggered_entries.push(entry);
641 updated_targets.push(super::super::dom_bridge::ObservedResizeTarget {
642 target: target_info.target.clone(),
643 target_idx: target_info.target_idx,
644 box_option: target_info.box_option,
645 last_inline_size: inline_size,
646 last_block_size: block_size,
647 });
648 } else {
649 updated_targets.push(target_info.clone());
650 }
651 }
652
653 {
655 let mut dom = it.dom.borrow_mut();
656 if let Some(reg) = dom.ro_observers.iter_mut().find(|o| o.id == obs.id) {
657 reg.targets = updated_targets;
658 }
659 }
660
661 if !triggered_entries.is_empty() {
662 let entries_val = Value::Object(Obj::array(triggered_entries));
663 let obs_obj = Value::Object(Obj::plain());
664 it.call_listener(
665 &obs.callback,
666 Value::Undefined,
667 &[entries_val, obs_obj],
668 "ResizeObserver callback",
669 );
670 }
671 }
672}
673
674
675pub(crate) fn mo_observe(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
677 let Value::Object(self_obj) = &this else { return Ok(Value::Undefined) };
678 let obs_id = match self_obj.borrow().props.get("_id") {
679 Some(Value::Number(n)) => *n as u32,
680 _ => return Ok(Value::Undefined),
681 };
682 let callback = self_obj.borrow().props.get("_cb").cloned().unwrap_or(Value::Undefined);
683 let target_idx = match arg(a, 0) {
684 Value::Object(o) => match o.borrow().kind {
685 super::super::value::ObjKind::DomElement(i) => i,
686 _ => return Ok(Value::Undefined),
687 },
688 _ => return Ok(Value::Undefined),
689 };
690 let opts = arg(a, 1);
691 let child_list = obj_prop(&opts, "childList")
692 .map(|v| v.truthy())
693 .unwrap_or(false);
694 let attributes_specified = obj_prop(&opts, "attributes");
695 let mut attributes = attributes_specified.as_ref().map(|v| v.truthy()).unwrap_or(false);
696 let subtree = obj_prop(&opts, "subtree")
697 .map(|v| v.truthy())
698 .unwrap_or(false);
699 let attribute_old_value = obj_prop(&opts, "attributeOldValue")
700 .map(|v| v.truthy())
701 .unwrap_or(false);
702 let attribute_filter = match obj_prop(&opts, "attributeFilter") {
703 Some(v @ Value::Object(_)) => Some(
704 generic_array_items(it, &v)
705 .iter()
706 .map(|item| item.to_js_string())
707 .collect::<Vec<String>>(),
708 ),
709 _ => None,
710 };
711 let character_data_specified = obj_prop(&opts, "characterData");
716 let mut character_data = character_data_specified.as_ref().map(|v| v.truthy()).unwrap_or(false);
717 let character_data_old_value = obj_prop(&opts, "characterDataOldValue")
718 .map(|v| v.truthy())
719 .unwrap_or(false);
720 if attributes_specified.is_none() && (attribute_old_value || attribute_filter.is_some()) {
728 attributes = true;
729 }
730 if character_data_specified.is_none() && character_data_old_value {
731 character_data = true;
732 }
733 use super::super::dom_bridge::MutObsReg;
734 let mut dom = it.dom.borrow_mut();
735 dom.mut_observers.retain(|o| !(o.id == obs_id && o.target_idx == target_idx));
737 dom.mut_observers.push(MutObsReg {
738 id: obs_id,
739 target_idx,
740 child_list,
741 attributes,
742 subtree,
743 callback,
744 observer: this,
745 attribute_old_value,
746 attribute_filter,
747 character_data,
748 character_data_old_value,
749 });
750 Ok(Value::Undefined)
751}
752
753pub(crate) fn mo_disconnect(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
754 let Value::Object(self_obj) = &this else { return Ok(Value::Undefined) };
755 let obs_id = match self_obj.borrow().props.get("_id") {
756 Some(Value::Number(n)) => *n as u32,
757 _ => return Ok(Value::Undefined),
758 };
759 it.dom.borrow_mut().mut_observers.retain(|o| o.id != obs_id);
760 it.dom.borrow_mut().mut_records.retain(|(id, _)| *id != obs_id);
761 Ok(Value::Undefined)
762}
763
764pub(crate) fn mo_take_records(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
765 let Value::Object(self_obj) = &this else {
766 return Ok(Value::Object(Obj::array(alloc::vec![])));
767 };
768 let obs_id = match self_obj.borrow().props.get("_id") {
769 Some(Value::Number(n)) => *n as u32,
770 _ => return Ok(Value::Object(Obj::array(alloc::vec![]))),
771 };
772 let records = build_mut_records_for(it, obs_id);
773 Ok(Value::Object(Obj::array(records)))
774}
775
776pub(crate) fn build_mut_records_for(it: &mut Interp, obs_id: u32) -> alloc::vec::Vec<Value> {
777 let mut matched = alloc::vec![];
778 let mut rest = alloc::vec![];
779 for (id, r) in core::mem::take(&mut it.dom.borrow_mut().mut_records) {
780 if id == obs_id { matched.push(r); } else { rest.push((id, r)); }
781 }
782 it.dom.borrow_mut().mut_records = rest;
783 matched.iter().map(|r| make_mut_record(it, r)).collect()
784}
785
786pub(crate) fn drain_mut_records(it: &mut Interp) -> alloc::vec::Vec<(u32, super::super::dom_bridge::MutRec)> {
787 core::mem::take(&mut it.dom.borrow_mut().mut_records)
788}
789
790pub(crate) fn make_mut_record(it: &mut Interp, r: &super::super::dom_bridge::MutRec) -> Value {
791 let rec = Obj::plain();
792 {
793 let mut b = rec.borrow_mut();
794 b.props.insert("type".into(), Value::str(r.type_));
795 let target = Value::Object(Obj::dom(r.target_idx));
796 b.props.insert("target".into(), target);
797 let added: alloc::vec::Vec<Value> = r.added.iter().map(|&i| Value::Object(Obj::dom(i))).collect();
798 b.props.insert("addedNodes".into(), Value::Object(Obj::array(added)));
799 let removed: alloc::vec::Vec<Value> = r.removed.iter().map(|&i| Value::Object(Obj::dom(i))).collect();
800 b.props.insert("removedNodes".into(), Value::Object(Obj::array(removed)));
801 b.props.insert("previousSibling".into(), Value::Null);
802 b.props.insert("nextSibling".into(), Value::Null);
803 b.props.insert(
804 "attributeName".into(),
805 r.attribute_name
806 .as_deref()
807 .map(Value::str)
808 .unwrap_or(Value::Null),
809 );
810 b.props.insert("attributeNamespace".into(), Value::Null);
811 b.props.insert(
812 "oldValue".into(),
813 r.old_value.as_deref().map(Value::str).unwrap_or(Value::Null),
814 );
815 }
816 let _ = it;
817 Value::Object(rec)
818}
819
820pub(crate) fn mutation_observer_ctor(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
821 let cb = arg(a, 0);
822 let obs_id = {
823 let mut dom = it.dom.borrow_mut();
824 let id = dom.next_mut_obs_id;
825 dom.next_mut_obs_id += 1;
826 id
827 };
828 let obs = Obj::plain();
829 {
830 let mut b = obs.borrow_mut();
831 b.props.insert("_cb".into(), cb);
832 b.props.insert("_id".into(), Value::Number(obs_id as f64));
833 b.props.insert("observe".into(), nv("observe", mo_observe));
834 b.props.insert("disconnect".into(), nv("disconnect", mo_disconnect));
835 b.props.insert("takeRecords".into(), nv("takeRecords", mo_take_records));
836 }
837 Ok(Value::Object(obs))
838}
839