1use super::*;
5use crate::os_lib::js::builtins::this_dom_idx;
6
7impl Interp {
8 pub(crate) fn dom_get_property(&mut self, disp: DomDisp, key: &str) -> Value {
9 match disp {
10 DomDisp::Element(idx) => self.dom_element_get(idx, key),
11 DomDisp::Host(tag) => {
12 if let Some(rest) = tag.strip_prefix("style:") {
13 match key {
14 "setProperty" => {
15 return Value::Object(Obj::native(
16 "style.setProperty",
17 super::super::builtins::dom_style_set_property,
18 ))
19 }
20 "getPropertyValue" => {
21 return Value::Object(Obj::native(
22 "style.getPropertyValue",
23 super::super::builtins::dom_style_get_property_value,
24 ))
25 }
26 "removeProperty" => {
27 return Value::Object(Obj::native(
28 "style.removeProperty",
29 super::super::builtins::dom_style_remove_property,
30 ))
31 }
32 "getPropertyPriority" => {
35 return Value::Object(Obj::native(
36 "style.getPropertyPriority",
37 super::super::builtins::dom_style_get_property_priority,
38 ))
39 }
40 _ => {}
41 }
42 let idx: usize = rest.parse().unwrap_or(usize::MAX);
43 if key == "cssText" {
47 return Value::str(self.dom.borrow().get_css_text(idx));
48 }
49 let style_names = || {
56 self.dom
57 .borrow()
58 .get_css_text(idx)
59 .split(';')
60 .filter_map(|d| d.split_once(':').map(|(k, _)| k.trim().to_string()))
61 .filter(|k| !k.is_empty())
62 .collect::<alloc::vec::Vec<_>>()
63 };
64 if key == "length" {
65 return Value::Number(style_names().len() as f64);
66 }
67 if key == "item" {
68 return Value::Object(Obj::native("item", super::super::builtins::dom_style_item));
69 }
70 let key = if key == "cssFloat" { "float" } else { key };
74 return Value::str(self.dom.borrow().get_style(idx, key));
75 }
76 if let Some(rest) = tag.strip_prefix("stylesheet:") {
77 let idx: usize = rest.parse().unwrap_or(usize::MAX);
78 match key {
79 "ownerNode" => return Value::Object(Obj::dom(idx)),
80 "insertRule" => {
81 return Value::Object(Obj::native(
82 "StyleSheet.insertRule",
83 super::super::builtins::dom_stylesheet_insert_rule,
84 ))
85 }
86 "deleteRule" => {
87 return Value::Object(Obj::native(
88 "StyleSheet.deleteRule",
89 super::super::builtins::dom_stylesheet_delete_rule,
90 ))
91 }
92 "cssRules" => {
93 let text = self.dom.borrow().get_text_content(idx);
94 let rules = super::super::builtins::split_css_rules(&text);
95 let rule_objs: alloc::vec::Vec<Value> = rules
96 .into_iter()
97 .enumerate()
98 .map(|(r_idx, rule_text)| {
99 let r_obj = Obj::host(&format!("cssrule:{}:{}", idx, r_idx));
100 r_obj.borrow_mut().props.insert("cssText".into(), Value::str(rule_text.clone()));
101 let selector = rule_text.split('{').next().unwrap_or("").trim().to_string();
102 r_obj.borrow_mut().props.insert("selectorText".into(), Value::str(selector));
103 Value::Object(r_obj)
104 })
105 .collect();
106 return Value::Object(Obj::array(rule_objs));
107 }
108 _ => {}
109 }
110 }
111 if let Some(rest) = tag.strip_prefix("classList:") {
112 let cidx: usize = rest.parse().unwrap_or(usize::MAX);
113 return match key {
115 "add" => Value::Object(Obj::native(
116 "classList.add",
117 super::super::builtins::dom_classlist_add,
118 )),
119 "remove" => Value::Object(Obj::native(
120 "classList.remove",
121 super::super::builtins::dom_classlist_remove,
122 )),
123 "toggle" => Value::Object(Obj::native(
124 "classList.toggle",
125 super::super::builtins::dom_classlist_toggle,
126 )),
127 "contains" => Value::Object(Obj::native(
128 "classList.contains",
129 super::super::builtins::dom_classlist_contains,
130 )),
131 "replace" => Value::Object(Obj::native(
132 "classList.replace",
133 super::super::builtins::dom_classlist_replace,
134 )),
135 "item" => Value::Object(Obj::native(
136 "classList.item",
137 super::super::builtins::dom_classlist_item,
138 )),
139 "toString" => Value::Object(Obj::native(
140 "classList.toString",
141 super::super::builtins::dom_classlist_to_string,
142 )),
143 "forEach" => Value::Object(Obj::native(
144 "classList.forEach",
145 super::super::builtins::dom_classlist_for_each,
146 )),
147 "entries" => Value::Object(Obj::native(
148 "classList.entries",
149 super::super::builtins::dom_classlist_entries,
150 )),
151 "keys" => Value::Object(Obj::native(
152 "classList.keys",
153 super::super::builtins::dom_classlist_keys,
154 )),
155 "values" => Value::Object(Obj::native(
156 "classList.values",
157 super::super::builtins::dom_classlist_values,
158 )),
159 "supports" => Value::Object(Obj::native(
160 "classList.supports",
161 super::super::builtins::dom_classlist_supports,
162 )),
163 "length" => Value::Number(
164 self.dom
165 .borrow()
166 .nodes
167 .get(cidx)
168 .map(|n| n.classes.len())
169 .unwrap_or(0) as f64,
170 ),
171 "value" => Value::str(
172 self.dom
173 .borrow()
174 .nodes
175 .get(cidx)
176 .map(|n| n.classes.join(" "))
177 .unwrap_or_default(),
178 ),
179 _ => Value::Undefined,
180 };
181 }
182 if let Some(rest) = tag.strip_prefix("dataset:") {
183 let idx: usize = rest.parse().unwrap_or(usize::MAX);
184 let attr = camel_to_data_attr(key);
185 return match self.dom.borrow().get_attr(idx, &attr) {
186 Some(v) => Value::str(v),
187 None => Value::Undefined,
188 };
189 }
190 if let Some(rest) = tag.strip_prefix("computed:") {
191 if key == "getPropertyValue" {
192 return Value::Object(Obj::native(
193 "getPropertyValue",
194 super::super::builtins::computed_get_property_value,
195 ));
196 }
197 let idx: usize = rest.parse().unwrap_or(usize::MAX);
198 let prop = camel_to_kebab(key);
200 return Value::str(self.dom.borrow().get_computed(idx, &prop));
201 }
202 Value::Undefined
203 }
204 }
205 }
206
207 pub(crate) fn dom_element_get(&mut self, idx: usize, key: &str) -> Value {
208 match key {
209 "baseURI" => Value::str(super::super::builtins::location_href(&self.global).unwrap_or_default()),
213 "attributes" => {
219 let dom = self.dom.borrow();
220 let items: Vec<Value> = dom
221 .attr_names(idx)
222 .into_iter()
223 .map(|name| {
224 let value = dom.get_attr(idx, &name).unwrap_or_default();
225 let o = Obj::plain();
226 o.borrow_mut().props.insert("name".into(), Value::str(name));
227 o.borrow_mut().props.insert("value".into(), Value::str(value));
228 Value::Object(o)
229 })
230 .collect();
231 Value::Object(Obj::array(items))
232 }
233 "textContent" | "innerText" | "outerText" => {
234 Value::str(self.dom.borrow().get_text_content(idx))
235 }
236 "text" => Value::str(self.dom.borrow().get_text_content(idx)),
239 "innerHTML" => {
244 let is_template = self.dom.borrow().nodes.get(idx).map(|n| n.tag == "template").unwrap_or(false);
245 if is_template {
246 let frag_val = self.dom_element_get(idx, "content");
247 if let Some(fi) = this_dom_idx(&frag_val) {
248 Value::str(self.dom.borrow().get_inner_html(fi))
249 } else {
250 Value::str(self.dom.borrow().get_inner_html(idx))
251 }
252 } else {
253 Value::str(self.dom.borrow().get_inner_html(idx))
254 }
255 }
256 "outerHTML" => {
257 let is_template = self.dom.borrow().nodes.get(idx).map(|n| n.tag == "template").unwrap_or(false);
258 if is_template {
259 Value::str("<template></template>")
260 } else {
261 Value::str(self.dom.borrow().get_outer_html(idx))
262 }
263 }
264 "id" => Value::str(self.dom.borrow().get_attr(idx, "id").unwrap_or_default()),
265 "className" => Value::str(self.dom.borrow().get_attr(idx, "class").unwrap_or_default()),
266 "tagName" => {
267 let t = self
268 .dom
269 .borrow()
270 .nodes
271 .get(idx)
272 .map(|n| n.tag.to_uppercase())
273 .unwrap_or_default();
274 Value::str(t)
275 }
276 "constructor" => {
283 match self.get_dom_node_proto(idx).and_then(|p| p.borrow().props.get("constructor").cloned()) {
284 Some(v) => v,
285 None => Value::Undefined,
286 }
287 }
288 "namespaceURI" => {
296 let is_text = self.dom.borrow().nodes.get(idx).map(|n| n.is_text).unwrap_or(false);
297 if is_text {
298 Value::Null
299 } else {
300 Value::str("http://www.w3.org/1999/xhtml")
301 }
302 }
303 "localName" => {
304 let dom = self.dom.borrow();
305 match dom.nodes.get(idx) {
306 Some(n) if !n.is_text => Value::str(n.tag.clone()),
307 _ => Value::Null,
308 }
309 }
310 "prefix" => Value::Null,
311 "value" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "li").unwrap_or(false) => {
327 let v = self
328 .dom
329 .borrow()
330 .get_attr(idx, "value")
331 .and_then(|s| s.trim().parse::<i64>().ok())
332 .unwrap_or(0);
333 Value::Number(v as f64)
334 }
335 "value" => {
336 let dom = self.dom.borrow();
337 let tag = dom.nodes.get(idx).map(|n| n.tag.as_str()).unwrap_or("");
338 if tag == "select" {
339 Value::str(dom.select_effective_value(idx))
340 } else if tag == "textarea" {
341 Value::str(
342 dom.get_attr(idx, "_live_value")
343 .or_else(|| dom.get_attr(idx, "value"))
344 .unwrap_or_else(|| dom.nodes.get(idx).map(|n| n.initial_text.clone()).unwrap_or_default()),
345 )
346 } else if tag == "output" {
347 Value::str(dom.get_attr(idx, "value").unwrap_or_else(|| dom.get_text_content(idx)))
357 } else if tag == "progress" || tag == "meter" {
358 Value::Number(
362 dom.get_attr(idx, "value").and_then(|s| s.trim().parse::<f64>().ok()).unwrap_or(0.0),
363 )
364 } else if tag == "input" && dom.get_attr(idx, "type").as_deref() == Some("file") {
365 Value::str("")
366 } else if tag == "input"
367 && matches!(dom.get_attr(idx, "type").as_deref(), Some("checkbox") | Some("radio"))
368 {
369 let raw_val = dom.get_attr(idx, "_live_value").or_else(|| dom.get_attr(idx, "value"));
370 Value::str(raw_val.unwrap_or_else(|| String::from("on")))
371 } else if tag == "input"
372 && dom.get_attr(idx, "type").as_deref() == Some("color")
373 {
374 let is_valid_hex_color = |s: &str| {
375 s.len() == 7
376 && s.starts_with('#')
377 && s.chars().skip(1).all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
378 };
379 let raw_val = dom.get_attr(idx, "_live_value").or_else(|| dom.get_attr(idx, "value"));
380 match raw_val {
381 Some(v) if is_valid_hex_color(&v) => Value::str(v),
382 _ => Value::str("#000000"),
383 }
384 } else if tag == "input" && dom.get_attr(idx, "type").as_deref() == Some("range") {
385 let min = dom.get_attr(idx, "min").and_then(|s| s.trim().parse::<f64>().ok()).unwrap_or(0.0);
386 let max = dom.get_attr(idx, "max").and_then(|s| s.trim().parse::<f64>().ok()).unwrap_or(100.0);
387 let default = if max < min { min } else { min + (max - min) / 2.0 };
388 let raw_val = dom.get_attr(idx, "_live_value").or_else(|| dom.get_attr(idx, "value"));
389 let v = raw_val
390 .and_then(|s| s.trim().parse::<f64>().ok())
391 .unwrap_or(default);
392 let clamped = if max < min {
393 min
394 } else {
395 v.clamp(min, max)
396 };
397 Value::str(Value::Number(clamped).to_js_string())
398 } else if tag == "input"
399 && matches!(
400 dom.get_attr(idx, "type").as_deref(),
401 Some("date") | Some("month") | Some("datetime-local")
402 )
403 {
404 let ty = dom.get_attr(idx, "type").unwrap_or_default();
405 let raw_val = dom.get_attr(idx, "_live_value").or_else(|| dom.get_attr(idx, "value"));
406 match raw_val {
407 Some(v) if super::super::builtins::input_value_as_date_ms(&ty, &v).is_some() => {
408 Value::str(v)
409 }
410 _ => Value::str(""),
411 }
412 } else {
413 let raw_val = dom.get_attr(idx, "_live_value").or_else(|| dom.get_attr(idx, "value"));
414 Value::str(raw_val.unwrap_or_default())
415 }
416 }
417 "defaultValue" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "textarea").unwrap_or(false) => {
426 let dom = self.dom.borrow();
427 Value::str(
428 dom.get_attr(idx, "value")
429 .unwrap_or_else(|| dom.nodes.get(idx).map(|n| n.initial_text.clone()).unwrap_or_default()),
430 )
431 }
432 "defaultValue" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "input").unwrap_or(false) => {
433 Value::str(self.dom.borrow().get_attr(idx, "value").unwrap_or_default())
434 }
435 "defaultValue" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "output").unwrap_or(false) => {
440 let dom = self.dom.borrow();
441 Value::str(
442 dom.get_attr(idx, "value")
443 .unwrap_or_else(|| dom.nodes.get(idx).map(|n| n.initial_text.clone()).unwrap_or_default()),
444 )
445 }
446 "defaultChecked" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "input").unwrap_or(false) => {
447 Value::Bool(self.dom.borrow().has_attr(idx, "checked"))
448 }
449 "defaultSelected" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "option").unwrap_or(false) => {
450 Value::Bool(self.dom.borrow().has_attr(idx, "selected"))
451 }
452 "selectionStart" | "selectionEnd"
459 if self
460 .dom
461 .borrow()
462 .nodes
463 .get(idx)
464 .map(|n| n.tag == "input" || n.tag == "textarea")
465 .unwrap_or(false) =>
466 {
467 let attr = if key == "selectionStart" { "_selection_start" } else { "_selection_end" };
468 Value::Number(self.dom.borrow().get_attr(idx, attr).and_then(|v| v.parse::<f64>().ok()).unwrap_or(0.0))
469 }
470 "selectionDirection"
471 if self
472 .dom
473 .borrow()
474 .nodes
475 .get(idx)
476 .map(|n| n.tag == "input" || n.tag == "textarea")
477 .unwrap_or(false) =>
478 {
479 Value::str(self.dom.borrow().get_attr(idx, "_selection_direction").unwrap_or_else(|| "none".into()))
480 }
481 "textLength" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "textarea").unwrap_or(false) => {
485 let dom = self.dom.borrow();
486 let v = dom
487 .get_attr(idx, "_live_value")
488 .or_else(|| dom.get_attr(idx, "value"))
489 .unwrap_or_else(|| dom.nodes.get(idx).map(|n| n.initial_text.clone()).unwrap_or_default());
490 Value::Number(v.chars().count() as f64)
491 }
492 "wrap" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "textarea").unwrap_or(false) => {
497 let v = self.dom.borrow().get_attr(idx, "wrap").map(|s| s.to_lowercase());
498 match v.as_deref() {
499 Some("hard") => Value::str("hard"),
500 _ => Value::str("soft"),
501 }
502 }
503 "setSelectionRange"
508 if self
509 .dom
510 .borrow()
511 .nodes
512 .get(idx)
513 .map(|n| n.tag == "input" || n.tag == "textarea")
514 .unwrap_or(false) =>
515 {
516 Value::Object(Obj::native("setSelectionRange", super::super::builtins::dom_set_selection_range))
517 }
518 "setRangeText"
519 if self
520 .dom
521 .borrow()
522 .nodes
523 .get(idx)
524 .map(|n| n.tag == "input" || n.tag == "textarea")
525 .unwrap_or(false) =>
526 {
527 Value::Object(Obj::native("setRangeText", super::super::builtins::dom_set_range_text))
528 }
529 "valueAsNumber" => {
533 let dom = self.dom.borrow();
534 let ty = dom.get_attr(idx, "type").unwrap_or_default();
535 if ty == "number" || ty == "range" {
536 let raw_val = dom.get_attr(idx, "_live_value").or_else(|| dom.get_attr(idx, "value"));
537 Value::Number(
538 raw_val
539 .and_then(|s| s.trim().parse::<f64>().ok())
540 .unwrap_or(f64::NAN),
541 )
542 } else {
543 Value::Number(f64::NAN)
544 }
545 }
546 "valueAsDate" => {
551 let dom = self.dom.borrow();
552 let ty = dom.get_attr(idx, "type").unwrap_or_default();
553 let value = dom.get_attr(idx, "_live_value")
554 .or_else(|| dom.get_attr(idx, "value"))
555 .unwrap_or_default();
556 match super::super::builtins::input_value_as_date_ms(&ty, &value) {
557 Some(ms) => Value::Object(Obj::date_obj(ms)),
558 None => Value::Null,
559 }
560 }
561 "max"
564 if self
565 .dom
566 .borrow()
567 .nodes
568 .get(idx)
569 .map(|n| n.tag == "progress" || n.tag == "meter")
570 .unwrap_or(false) =>
571 {
572 Value::Number(
573 self.dom
574 .borrow()
575 .get_attr(idx, "max")
576 .and_then(|s| s.trim().parse::<f64>().ok())
577 .unwrap_or(1.0),
578 )
579 }
580 "min"
581 if self
582 .dom
583 .borrow()
584 .nodes
585 .get(idx)
586 .map(|n| n.tag == "meter")
587 .unwrap_or(false) =>
588 {
589 Value::Number(
590 self.dom
591 .borrow()
592 .get_attr(idx, "min")
593 .and_then(|s| s.trim().parse::<f64>().ok())
594 .unwrap_or(0.0),
595 )
596 }
597 "position"
602 if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "progress").unwrap_or(false) =>
603 {
604 let dom = self.dom.borrow();
605 match dom.get_attr(idx, "value").and_then(|s| s.trim().parse::<f64>().ok()) {
606 None => Value::Number(-1.0),
607 Some(v) => {
608 let max = dom.get_attr(idx, "max").and_then(|s| s.trim().parse::<f64>().ok()).unwrap_or(1.0);
609 let max = if max > 0.0 { max } else { 1.0 };
610 Value::Number((v / max).clamp(0.0, 1.0))
611 }
612 }
613 }
614 "low" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "meter").unwrap_or(false) => {
620 let dom = self.dom.borrow();
621 let min = dom.get_attr(idx, "min").and_then(|s| s.trim().parse::<f64>().ok()).unwrap_or(0.0);
622 let max = dom.get_attr(idx, "max").and_then(|s| s.trim().parse::<f64>().ok()).unwrap_or(1.0);
623 let low = dom.get_attr(idx, "low").and_then(|s| s.trim().parse::<f64>().ok()).unwrap_or(min);
624 Value::Number(low.clamp(min, max))
625 }
626 "high" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "meter").unwrap_or(false) => {
627 let dom = self.dom.borrow();
628 let min = dom.get_attr(idx, "min").and_then(|s| s.trim().parse::<f64>().ok()).unwrap_or(0.0);
629 let max = dom.get_attr(idx, "max").and_then(|s| s.trim().parse::<f64>().ok()).unwrap_or(1.0);
630 let high = dom.get_attr(idx, "high").and_then(|s| s.trim().parse::<f64>().ok()).unwrap_or(max);
631 Value::Number(high.clamp(min, max))
632 }
633 "optimum" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "meter").unwrap_or(false) => {
634 let dom = self.dom.borrow();
635 let min = dom.get_attr(idx, "min").and_then(|s| s.trim().parse::<f64>().ok()).unwrap_or(0.0);
636 let max = dom.get_attr(idx, "max").and_then(|s| s.trim().parse::<f64>().ok()).unwrap_or(1.0);
637 let optimum =
638 dom.get_attr(idx, "optimum").and_then(|s| s.trim().parse::<f64>().ok()).unwrap_or((min + max) / 2.0);
639 Value::Number(optimum.clamp(min, max))
640 }
641 "selectedIndex" => Value::Number(self.dom.borrow().select_selected_index(idx) as f64),
642 "add" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "select").unwrap_or(false) => {
645 Value::Object(Obj::native("add", super::super::builtins::dom_select_add))
646 }
647 "length"
651 if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "select").unwrap_or(false) =>
652 {
653 Value::Number(self.dom.borrow().select_options(idx).len() as f64)
654 }
655 "selectedOptions" => {
659 let indices = self.dom.borrow().selected_option_indices(idx);
660 Value::Object(Obj::array(
661 indices.into_iter().map(|i| Value::Object(Obj::dom(i))).collect(),
662 ))
663 }
664 "options" => {
668 let indices = self.dom.borrow().select_options(idx);
669 let arr = Obj::array(
670 indices.into_iter().map(|i| Value::Object(Obj::dom(i))).collect(),
671 );
672 {
676 let mut b = arr.borrow_mut();
677 b.props.insert("_select_idx".into(), Value::Number(idx as f64));
678 b.props.insert(
679 "item".into(),
680 Value::Object(Obj::native("options.item", super::super::builtins::html_collection_item)),
681 );
682 b.props.insert(
683 "add".into(),
684 Value::Object(Obj::native("options.add", super::super::builtins::select_options_add)),
685 );
686 b.props.insert(
687 "remove".into(),
688 Value::Object(Obj::native("options.remove", super::super::builtins::select_options_remove)),
689 );
690 b.props.insert(
691 "namedItem".into(),
692 Value::Object(Obj::native(
693 "options.namedItem",
694 super::super::builtins::select_options_named_item,
695 )),
696 );
697 }
698 Value::Object(arr)
699 }
700 "form" => match self.dom.borrow().associated_form(idx) {
706 Some(fi) => Value::Object(Obj::dom(fi)),
707 None => Value::Null,
708 },
709 "control" => {
714 let dom = self.dom.borrow();
715 let is_label = dom.nodes.get(idx).map(|n| n.tag == "label").unwrap_or(false);
716 if !is_label {
717 Value::Undefined
718 } else {
719 let for_id = dom.get_attr(idx, "for");
720 let by_for = for_id.filter(|f| !f.is_empty()).and_then(|f| {
721 (0..dom.nodes.len())
722 .find(|&i| !dom.nodes[i].is_text && dom.nodes[i].id == f)
723 });
724 let target = by_for.or_else(|| {
725 dom.descendants_by_tags(idx, &["input", "select", "textarea", "button"])
726 .into_iter()
727 .next()
728 });
729 match target {
730 Some(i) => Value::Object(Obj::dom(i)),
731 None => Value::Null,
732 }
733 }
734 }
735 "labels" => {
739 let dom = self.dom.borrow();
740 let control_id = dom.nodes.get(idx).map(|n| n.id.clone()).unwrap_or_default();
741 let mut labels: Vec<usize> = Vec::new();
742 for (i, n) in dom.nodes.iter().enumerate() {
743 if n.is_text || n.tag != "label" {
744 continue;
745 }
746 let matches_for = !control_id.is_empty()
747 && dom.get_attr(i, "for").map(|f| f == control_id).unwrap_or(false);
748 let is_ancestor = dom.is_ancestor_of(i, idx);
749 if matches_for || is_ancestor {
750 labels.push(i);
751 }
752 }
753 Value::Object(Obj::array(
754 labels.into_iter().map(|i| Value::Object(Obj::dom(i))).collect(),
755 ))
756 }
757 "elements" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "fieldset").unwrap_or(false) => {
770 let fields = self.dom.borrow().descendants_by_tags(idx, &["input", "select", "textarea", "button"]);
771 super::super::builtins::make_html_collection(self, fields)
772 }
773 "elements" => {
774 let dom = self.dom.borrow();
775 let is_form = dom.nodes.get(idx).map(|n| n.tag == "form").unwrap_or(false);
776 if is_form {
777 let fields =
778 dom.form_associated_controls(idx, &["input", "select", "textarea", "button"]);
779 drop(dom);
780 super::super::builtins::make_html_collection(self, fields)
781 } else {
782 Value::Undefined
783 }
784 }
785 "length" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "form").unwrap_or(false) => {
790 Value::Number(
791 self.dom
792 .borrow()
793 .form_associated_controls(idx, &["input", "select", "textarea", "button"])
794 .len() as f64,
795 )
796 }
797 "rows"
806 if self
807 .dom
808 .borrow()
809 .nodes
810 .get(idx)
811 .map(|n| matches!(n.tag.as_str(), "table" | "thead" | "tbody" | "tfoot"))
812 .unwrap_or(false) =>
813 {
814 let rows = self.dom.borrow().descendants_by_tags(idx, &["tr"]);
815 Value::Object(Obj::array(rows.into_iter().map(|i| Value::Object(Obj::dom(i))).collect()))
816 }
817 "cells" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "tr").unwrap_or(false) => {
818 let cells = self.dom.borrow().descendants_by_tags(idx, &["td", "th"]);
819 Value::Object(Obj::array(cells.into_iter().map(|i| Value::Object(Obj::dom(i))).collect()))
820 }
821 "tHead" | "tFoot" | "caption"
826 if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "table").unwrap_or(false) =>
827 {
828 let want = match key {
829 "tHead" => "thead",
830 "tFoot" => "tfoot",
831 _ => "caption",
832 };
833 let dom = self.dom.borrow();
834 let found = dom.nodes[idx].children.iter().find(|&&c| dom.nodes[c].tag == want).copied();
835 match found {
836 Some(i) => Value::Object(Obj::dom(i)),
837 None => Value::Null,
838 }
839 }
840 "tBodies" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "table").unwrap_or(false) => {
842 let dom = self.dom.borrow();
843 let bodies: alloc::vec::Vec<usize> =
844 dom.nodes[idx].children.iter().filter(|&&c| dom.nodes[c].tag == "tbody").copied().collect();
845 Value::Object(Obj::array(bodies.into_iter().map(|i| Value::Object(Obj::dom(i))).collect()))
846 }
847 "rowIndex" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "tr").unwrap_or(false) => {
849 let dom = self.dom.borrow();
850 let mut cur = dom.nodes.get(idx).and_then(|n| n.parent);
851 let table = loop {
852 match cur {
853 Some(p) if dom.nodes.get(p).map(|n| n.tag == "table").unwrap_or(false) => break Some(p),
854 Some(p) => cur = dom.nodes.get(p).and_then(|n| n.parent),
855 None => break None,
856 }
857 };
858 match table {
859 Some(t) => {
860 let rows = dom.descendants_by_tags(t, &["tr"]);
861 Value::Number(rows.iter().position(|&r| r == idx).map(|p| p as f64).unwrap_or(-1.0))
862 }
863 None => Value::Number(-1.0),
864 }
865 }
866 "sectionRowIndex" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "tr").unwrap_or(false) => {
875 let dom = self.dom.borrow();
876 match dom.nodes.get(idx).and_then(|n| n.parent) {
877 Some(p) => {
878 let rows = dom.descendants_by_tags(p, &["tr"]);
879 Value::Number(rows.iter().position(|&r| r == idx).map(|i| i as f64).unwrap_or(-1.0))
880 }
881 None => Value::Number(-1.0),
882 }
883 }
884 "cellIndex" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "td" || n.tag == "th").unwrap_or(false) => {
886 let dom = self.dom.borrow();
887 match dom.nodes.get(idx).and_then(|n| n.parent) {
888 Some(p) => {
889 let cells = dom.descendants_by_tags(p, &["td", "th"]);
890 Value::Number(cells.iter().position(|&c| c == idx).map(|i| i as f64).unwrap_or(-1.0))
891 }
892 None => Value::Number(-1.0),
893 }
894 }
895 "colSpan" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "td" || n.tag == "th").unwrap_or(false) => {
902 let v = self
903 .dom
904 .borrow()
905 .get_attr(idx, "colspan")
906 .and_then(|s| s.trim().parse::<i64>().ok())
907 .filter(|n| *n >= 1)
908 .unwrap_or(1);
909 Value::Number(v.min(1000) as f64)
910 }
911 "rowSpan" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "td" || n.tag == "th").unwrap_or(false) => {
912 let v = self
913 .dom
914 .borrow()
915 .get_attr(idx, "rowspan")
916 .and_then(|s| s.trim().parse::<i64>().ok())
917 .filter(|n| *n >= 0)
918 .unwrap_or(1);
919 Value::Number(v.min(65534) as f64)
920 }
921 "headers" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "td" || n.tag == "th").unwrap_or(false) => {
928 Value::str(self.dom.borrow().get_attr(idx, "headers").unwrap_or_default())
929 }
930 "scope" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "th").unwrap_or(false) => {
931 let v = self.dom.borrow().get_attr(idx, "scope").map(|s| s.to_lowercase());
932 match v.as_deref() {
933 Some("row") => Value::str("row"),
934 Some("col") => Value::str("col"),
935 Some("rowgroup") => Value::str("rowgroup"),
936 Some("colgroup") => Value::str("colgroup"),
937 _ => Value::str(""),
938 }
939 }
940 "abbr" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "th").unwrap_or(false) => {
941 Value::str(self.dom.borrow().get_attr(idx, "abbr").unwrap_or_default())
942 }
943 "span" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "col" || n.tag == "colgroup").unwrap_or(false) => {
947 let v = self
948 .dom
949 .borrow()
950 .get_attr(idx, "span")
951 .and_then(|s| s.trim().parse::<i64>().ok())
952 .filter(|n| *n >= 1)
953 .unwrap_or(1);
954 Value::Number(v.min(1000) as f64)
955 }
956 "insertRow"
968 if self
969 .dom
970 .borrow()
971 .nodes
972 .get(idx)
973 .map(|n| matches!(n.tag.as_str(), "table" | "thead" | "tbody" | "tfoot"))
974 .unwrap_or(false) =>
975 {
976 Value::Object(Obj::native("insertRow", super::super::builtins::dom_table_insert_row))
977 }
978 "deleteRow"
979 if self
980 .dom
981 .borrow()
982 .nodes
983 .get(idx)
984 .map(|n| matches!(n.tag.as_str(), "table" | "thead" | "tbody" | "tfoot"))
985 .unwrap_or(false) =>
986 {
987 Value::Object(Obj::native("deleteRow", super::super::builtins::dom_table_delete_row))
988 }
989 "insertCell" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "tr").unwrap_or(false) => {
990 Value::Object(Obj::native("insertCell", super::super::builtins::dom_table_insert_cell))
991 }
992 "deleteCell" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "tr").unwrap_or(false) => {
993 Value::Object(Obj::native("deleteCell", super::super::builtins::dom_table_delete_cell))
994 }
995 "createTHead" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "table").unwrap_or(false) => {
999 Value::Object(Obj::native("createTHead", super::super::builtins::dom_table_create_thead))
1000 }
1001 "createTFoot" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "table").unwrap_or(false) => {
1002 Value::Object(Obj::native("createTFoot", super::super::builtins::dom_table_create_tfoot))
1003 }
1004 "createCaption" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "table").unwrap_or(false) => {
1005 Value::Object(Obj::native("createCaption", super::super::builtins::dom_table_create_caption))
1006 }
1007 "deleteTHead" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "table").unwrap_or(false) => {
1008 Value::Object(Obj::native("deleteTHead", super::super::builtins::dom_table_delete_thead))
1009 }
1010 "deleteTFoot" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "table").unwrap_or(false) => {
1011 Value::Object(Obj::native("deleteTFoot", super::super::builtins::dom_table_delete_tfoot))
1012 }
1013 "deleteCaption" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "table").unwrap_or(false) => {
1014 Value::Object(Obj::native("deleteCaption", super::super::builtins::dom_table_delete_caption))
1015 }
1016 "type" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "fieldset").unwrap_or(false) => {
1024 Value::str("fieldset")
1025 }
1026 "type" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "textarea").unwrap_or(false) => {
1030 Value::str("textarea")
1031 }
1032 "type" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "output").unwrap_or(false) => {
1037 Value::str("output")
1038 }
1039 "htmlFor" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "output").unwrap_or(false) => {
1046 let v = self.dom.borrow().get_attr(idx, "for").unwrap_or_default();
1047 Value::Object(Obj::array(v.split_whitespace().map(Value::str).collect()))
1048 }
1049 "type" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "select").unwrap_or(false) => {
1055 if self.dom.borrow().has_attr(idx, "multiple") {
1056 Value::str("select-multiple")
1057 } else {
1058 Value::str("select-one")
1059 }
1060 }
1061 "type" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "button").unwrap_or(false) => {
1069 let v = self.dom.borrow().get_attr(idx, "type").map(|s| s.to_lowercase());
1070 match v.as_deref() {
1071 Some("reset") => Value::str("reset"),
1072 Some("button") => Value::str("button"),
1073 _ => Value::str("submit"),
1074 }
1075 }
1076 "type" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "input").unwrap_or(false) => {
1083 const KNOWN_TYPES: [&str; 22] = [
1084 "text", "hidden", "search", "tel", "url", "email", "password", "date", "month",
1085 "week", "time", "datetime-local", "number", "range", "color", "checkbox", "radio",
1086 "file", "submit", "image", "reset", "button",
1087 ];
1088 let v = self.dom.borrow().get_attr(idx, "type").map(|s| s.to_lowercase());
1089 match v {
1090 Some(t) if KNOWN_TYPES.contains(&t.as_str()) => Value::str(t),
1091 _ => Value::str("text"),
1092 }
1093 }
1094 "type" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "ol").unwrap_or(false) => {
1099 let v = self.dom.borrow().get_attr(idx, "type");
1100 match v.as_deref() {
1101 Some("1") => Value::str("1"),
1102 Some("a") => Value::str("a"),
1103 Some("A") => Value::str("A"),
1104 Some("i") => Value::str("i"),
1105 Some("I") => Value::str("I"),
1106 _ => Value::str("1"),
1107 }
1108 }
1109 "type" => Value::str(self.dom.borrow().get_attr(idx, "type").unwrap_or_default()),
1110 "checked" => {
1116 let dom = self.dom.borrow();
1117 if let Some(lc) = dom.get_attr(idx, "_live_checked") {
1118 Value::Bool(lc == "true")
1119 } else {
1120 Value::Bool(dom.has_attr(idx, "checked"))
1121 }
1122 }
1123 "files"
1129 if self.dom.borrow().get_attr(idx, "type").as_deref() == Some("file") =>
1130 {
1131 Value::Object(Obj::array(alloc::vec![]))
1132 }
1133
1134 "contentWindow"
1142 if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "iframe").unwrap_or(false) =>
1143 {
1144 self.global.borrow().vars.get("window").cloned().unwrap_or(Value::Undefined)
1145 }
1146 "contentDocument"
1151 if self
1152 .dom
1153 .borrow()
1154 .nodes
1155 .get(idx)
1156 .map(|n| n.tag == "iframe" || n.tag == "object")
1157 .unwrap_or(false) =>
1158 {
1159 self.global.borrow().vars.get("document").cloned().unwrap_or(Value::Undefined)
1160 }
1161 "data" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "object").unwrap_or(false) => {
1168 let raw = self.dom.borrow().get_attr(idx, "data").unwrap_or_default();
1169 Value::str(self.resolve_url_against_page(&raw))
1170 }
1171 "open" => Value::Bool(self.dom.borrow().has_attr(idx, "open")),
1174 "inert" => Value::Bool(self.dom.borrow().has_attr(idx, "inert")),
1178 "returnValue" => Value::str(
1181 self.dom
1182 .borrow()
1183 .get_attr(idx, "_return_value")
1184 .unwrap_or_default(),
1185 ),
1186 "indeterminate" => Value::Bool(self.dom.borrow().has_attr(idx, "_indeterminate")),
1192 "disabled" => Value::Bool(self.dom.borrow().is_disabled(idx)),
1203 "selected" => Value::Bool(self.dom.borrow().has_attr(idx, "selected")),
1204 "index"
1208 if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "option").unwrap_or(false) =>
1209 {
1210 Value::Number(self.dom.borrow().option_index(idx) as f64)
1211 }
1212 "placeholder" => {
1213 Value::str(self.dom.borrow().get_attr(idx, "placeholder").unwrap_or_default())
1214 }
1215 "pattern" => Value::str(self.dom.borrow().get_attr(idx, "pattern").unwrap_or_default()),
1222 "name" => Value::str(self.dom.borrow().get_attr(idx, "name").unwrap_or_default()),
1223 "required" => Value::Bool(self.dom.borrow().has_attr(idx, "required")),
1228 "noValidate" => Value::Bool(self.dom.borrow().has_attr(idx, "novalidate")),
1233 "readOnly" => Value::Bool(self.dom.borrow().has_attr(idx, "readonly")),
1234 "multiple" => Value::Bool(self.dom.borrow().has_attr(idx, "multiple")),
1235 "capture" => Value::str(self.dom.borrow().get_attr(idx, "capture").unwrap_or_default()),
1240 "accept" => Value::str(self.dom.borrow().get_attr(idx, "accept").unwrap_or_default()),
1244 "src" => {
1250 let raw = self.dom.borrow().get_attr(idx, "src").unwrap_or_default();
1251 Value::str(self.resolve_url_against_page(&raw))
1252 }
1253 "alt" => Value::str(self.dom.borrow().get_attr(idx, "alt").unwrap_or_default()),
1254 "href" => {
1255 let raw = self.dom.borrow().get_attr(idx, "href").unwrap_or_default();
1256 Value::str(self.resolve_url_against_page(&raw))
1257 }
1258 "protocol" | "host" | "hostname" | "port" | "pathname" | "search" | "hash" | "origin"
1267 | "username" | "password"
1268 if self
1269 .dom
1270 .borrow()
1271 .nodes
1272 .get(idx)
1273 .map(|n| n.tag == "a" || n.tag == "area")
1274 .unwrap_or(false) =>
1275 {
1276 let raw = self.dom.borrow().get_attr(idx, "href").unwrap_or_default();
1277 let abs = self.resolve_url_against_page(&raw);
1278 let comps = super::super::builtins::location_components(&abs);
1279 Value::str(comps.iter().find(|(k, _)| *k == key).map(|(_, v)| v.clone()).unwrap_or_default())
1280 }
1281 "htmlFor" => Value::str(self.dom.borrow().get_attr(idx, "for").unwrap_or_default()),
1286 "acceptCharset" => Value::str(self.dom.borrow().get_attr(idx, "accept-charset").unwrap_or_default()),
1290 "httpEquiv" => Value::str(self.dom.borrow().get_attr(idx, "http-equiv").unwrap_or_default()),
1295 "content" => {
1296 let tag = self.dom.borrow().nodes.get(idx).map(|n| n.tag.clone()).unwrap_or_default();
1297 if tag == "meta" {
1298 Value::str(self.dom.borrow().get_attr(idx, "content").unwrap_or_default())
1299 } else if tag == "template" {
1300 let existing_attr = self.dom.borrow().get_attr(idx, "_template_content_idx");
1301 let frag_idx = existing_attr.and_then(|s| s.parse::<usize>().ok());
1302 let frag_idx = match frag_idx {
1303 Some(fi) => fi,
1304 None => {
1305 let fi = self.dom.borrow_mut().create_element("#document-fragment");
1306 self.dom.borrow_mut().set_attr(idx, "_template_content_idx", &fi.to_string());
1307 let direct_children = self.dom.borrow().nodes.get(idx).map(|n| n.children.clone()).unwrap_or_default();
1308 if !direct_children.is_empty() {
1309 let mut dom = self.dom.borrow_mut();
1310 if let Some(template_node) = dom.nodes.get_mut(idx) {
1311 template_node.children.clear();
1312 }
1313 if let Some(frag_node) = dom.nodes.get_mut(fi) {
1314 frag_node.children = direct_children.clone();
1315 }
1316 for &child_idx in &direct_children {
1317 if let Some(child_node) = dom.nodes.get_mut(child_idx) {
1318 child_node.parent = Some(fi);
1319 }
1320 }
1321 }
1322 fi
1323 }
1324 };
1325 Value::Object(Obj::dom(frag_idx))
1326 } else {
1327 Value::str(self.dom.borrow().get_attr(idx, "content").unwrap_or_default())
1328 }
1329 }
1330 "charset" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "meta").unwrap_or(false) => {
1331 Value::str(self.dom.borrow().get_attr(idx, "charset").unwrap_or_default())
1332 }
1333 "charset" => Value::str(self.dom.borrow().get_attr(idx, "charset").unwrap_or_default()),
1338 "cite" => Value::str(self.dom.borrow().get_attr(idx, "cite").unwrap_or_default()),
1343 "dateTime" => Value::str(self.dom.borrow().get_attr(idx, "datetime").unwrap_or_default()),
1344 "action" => {
1345 let raw = self.dom.borrow().get_attr(idx, "action").unwrap_or_default();
1346 Value::str(self.resolve_url_against_page(&raw))
1347 }
1348 "method" => {
1354 let v = self.dom.borrow().get_attr(idx, "method").map(|s| s.to_lowercase());
1355 match v.as_deref() {
1356 Some("post") => Value::str("post"),
1357 Some("dialog") => Value::str("dialog"),
1358 _ => Value::str("get"),
1359 }
1360 }
1361 "enctype" | "encoding" => {
1367 let v = self.dom.borrow().get_attr(idx, "enctype").map(|s| s.to_lowercase());
1368 match v.as_deref() {
1369 Some("multipart/form-data") => Value::str("multipart/form-data"),
1370 Some("text/plain") => Value::str("text/plain"),
1371 _ => Value::str("application/x-www-form-urlencoded"),
1372 }
1373 }
1374 "formAction"
1382 if self
1383 .dom
1384 .borrow()
1385 .nodes
1386 .get(idx)
1387 .map(|n| n.tag == "button" || n.tag == "input")
1388 .unwrap_or(false) =>
1389 {
1390 let dom = self.dom.borrow();
1391 let raw = dom.get_attr(idx, "formaction").or_else(|| {
1392 dom.associated_form(idx).and_then(|fi| dom.get_attr(fi, "action"))
1393 }).unwrap_or_default();
1394 drop(dom);
1395 Value::str(self.resolve_url_against_page(&raw))
1396 }
1397 "formEnctype"
1398 if self
1399 .dom
1400 .borrow()
1401 .nodes
1402 .get(idx)
1403 .map(|n| n.tag == "button" || n.tag == "input")
1404 .unwrap_or(false) =>
1405 {
1406 let dom = self.dom.borrow();
1407 let raw = dom.get_attr(idx, "formenctype").or_else(|| {
1408 dom.associated_form(idx).and_then(|fi| dom.get_attr(fi, "enctype"))
1409 }).map(|s| s.to_lowercase());
1410 match raw.as_deref() {
1411 Some("multipart/form-data") => Value::str("multipart/form-data"),
1412 Some("text/plain") => Value::str("text/plain"),
1413 _ => Value::str("application/x-www-form-urlencoded"),
1414 }
1415 }
1416 "formMethod"
1417 if self
1418 .dom
1419 .borrow()
1420 .nodes
1421 .get(idx)
1422 .map(|n| n.tag == "button" || n.tag == "input")
1423 .unwrap_or(false) =>
1424 {
1425 let dom = self.dom.borrow();
1426 let raw = dom.get_attr(idx, "formmethod").or_else(|| {
1427 dom.associated_form(idx).and_then(|fi| dom.get_attr(fi, "method"))
1428 }).map(|s| s.to_lowercase());
1429 match raw.as_deref() {
1430 Some("post") => Value::str("post"),
1431 Some("dialog") => Value::str("dialog"),
1432 _ => Value::str("get"),
1433 }
1434 }
1435 "formNoValidate"
1436 if self
1437 .dom
1438 .borrow()
1439 .nodes
1440 .get(idx)
1441 .map(|n| n.tag == "button" || n.tag == "input")
1442 .unwrap_or(false) =>
1443 {
1444 Value::Bool(self.dom.borrow().has_attr(idx, "formnovalidate"))
1445 }
1446 "formTarget"
1447 if self
1448 .dom
1449 .borrow()
1450 .nodes
1451 .get(idx)
1452 .map(|n| n.tag == "button" || n.tag == "input")
1453 .unwrap_or(false) =>
1454 {
1455 let dom = self.dom.borrow();
1456 let raw = dom.get_attr(idx, "formtarget").or_else(|| {
1457 dom.associated_form(idx).and_then(|fi| dom.get_attr(fi, "target"))
1458 }).unwrap_or_default();
1459 Value::str(raw)
1460 }
1461 "min" => Value::str(self.dom.borrow().get_attr(idx, "min").unwrap_or_default()),
1462 "max" => Value::str(self.dom.borrow().get_attr(idx, "max").unwrap_or_default()),
1463 "step" => Value::str(self.dom.borrow().get_attr(idx, "step").unwrap_or_default()),
1464 "maxLength" => Value::Number(
1471 self.dom
1472 .borrow()
1473 .get_attr(idx, "maxlength")
1474 .and_then(|s| s.trim().parse::<i64>().ok())
1475 .unwrap_or(-1) as f64,
1476 ),
1477 "minLength" => Value::Number(
1478 self.dom
1479 .borrow()
1480 .get_attr(idx, "minlength")
1481 .and_then(|s| s.trim().parse::<i64>().ok())
1482 .unwrap_or(-1) as f64,
1483 ),
1484 "target" => Value::str(self.dom.borrow().get_attr(idx, "target").unwrap_or_default()),
1485 "rel" => Value::str(self.dom.borrow().get_attr(idx, "rel").unwrap_or_default()),
1486 "download" => Value::str(self.dom.borrow().get_attr(idx, "download").unwrap_or_default()),
1487 "coords"
1493 if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "area").unwrap_or(false) =>
1494 {
1495 Value::str(self.dom.borrow().get_attr(idx, "coords").unwrap_or_default())
1496 }
1497 "shape"
1498 if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "area").unwrap_or(false) =>
1499 {
1500 let v = self.dom.borrow().get_attr(idx, "shape").map(|s| s.to_lowercase());
1501 match v.as_deref() {
1502 Some("circle") | Some("circ") => Value::str("circle"),
1503 Some("poly") | Some("polygon") => Value::str("poly"),
1504 Some("default") => Value::str("default"),
1505 _ => Value::str("rect"),
1506 }
1507 }
1508 "start" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "ol").unwrap_or(false) => {
1512 let v = self
1513 .dom
1514 .borrow()
1515 .get_attr(idx, "start")
1516 .and_then(|s| s.trim().parse::<i64>().ok());
1517 Value::Number(v.unwrap_or(1) as f64)
1518 }
1519 "reversed" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "ol").unwrap_or(false) => {
1520 Value::Bool(self.dom.borrow().has_attr(idx, "reversed"))
1521 }
1522 "lang" => Value::str(self.dom.borrow().get_attr(idx, "lang").unwrap_or_default()),
1526 "dir" => Value::str(self.dom.borrow().get_attr(idx, "dir").unwrap_or_default()),
1527 "title" => Value::str(self.dom.borrow().get_attr(idx, "title").unwrap_or_default()),
1534 "label" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "option").unwrap_or(false) => {
1540 match self.dom.borrow().get_attr(idx, "label") {
1541 Some(v) => Value::str(v),
1542 None => Value::str(self.dom.borrow().get_text_content(idx).trim().to_string()),
1543 }
1544 }
1545 "label" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "optgroup").unwrap_or(false) => {
1546 Value::str(self.dom.borrow().get_attr(idx, "label").unwrap_or_default())
1547 }
1548 "label" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "track").unwrap_or(false) => {
1553 Value::str(self.dom.borrow().get_attr(idx, "label").unwrap_or_default())
1554 }
1555 "srclang" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "track").unwrap_or(false) => {
1556 Value::str(self.dom.borrow().get_attr(idx, "srclang").unwrap_or_default())
1557 }
1558 "default" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "track").unwrap_or(false) => {
1559 Value::Bool(self.dom.borrow().has_attr(idx, "default"))
1560 }
1561 "accessKey" => Value::str(self.dom.borrow().get_attr(idx, "accesskey").unwrap_or_default()),
1565 "translate" => Value::Bool(
1568 self.dom.borrow().get_attr(idx, "translate").as_deref() != Some("no"),
1569 ),
1570 "draggable" => Value::Bool(
1577 self.dom.borrow().get_attr(idx, "draggable").as_deref() == Some("true"),
1578 ),
1579 "slot" => Value::str(self.dom.borrow().get_attr(idx, "slot").unwrap_or_default()),
1586 "tabIndex" => Value::Number(
1591 self.dom
1592 .borrow()
1593 .get_attr(idx, "tabindex")
1594 .and_then(|v| v.trim().parse::<f64>().ok())
1595 .unwrap_or(-1.0),
1596 ),
1597 "contentEditable" => Value::str(
1603 match self.dom.borrow().get_attr(idx, "contenteditable").as_deref() {
1604 Some("true") | Some("") => "true",
1605 Some("false") => "false",
1606 Some("plaintext-only") => "plaintext-only",
1607 _ => "inherit",
1608 },
1609 ),
1610 "isContentEditable" => Value::Bool(matches!(
1611 self.dom.borrow().get_attr(idx, "contenteditable").as_deref(),
1612 Some("true") | Some("") | Some("plaintext-only")
1613 )),
1614 "spellcheck" => Value::Bool(
1619 self.dom.borrow().get_attr(idx, "spellcheck").as_deref() != Some("false"),
1620 ),
1621 "inputMode" => Value::str(self.dom.borrow().get_attr(idx, "inputmode").unwrap_or_default()),
1625 "size" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "select").unwrap_or(false) => {
1631 let v = self
1632 .dom
1633 .borrow()
1634 .get_attr(idx, "size")
1635 .and_then(|s| s.trim().parse::<i64>().ok())
1636 .filter(|n| *n >= 0)
1637 .unwrap_or(0);
1638 Value::Number(v as f64)
1639 }
1640 "size" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "input").unwrap_or(false) => {
1641 let v = self
1642 .dom
1643 .borrow()
1644 .get_attr(idx, "size")
1645 .and_then(|s| s.trim().parse::<i64>().ok())
1646 .filter(|n| *n >= 1)
1647 .unwrap_or(20);
1648 Value::Number(v as f64)
1649 }
1650 "rows" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "textarea").unwrap_or(false) => {
1654 let v = self
1655 .dom
1656 .borrow()
1657 .get_attr(idx, "rows")
1658 .and_then(|s| s.trim().parse::<i64>().ok())
1659 .filter(|n| *n >= 1)
1660 .unwrap_or(2);
1661 Value::Number(v as f64)
1662 }
1663 "cols" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "textarea").unwrap_or(false) => {
1664 let v = self
1665 .dom
1666 .borrow()
1667 .get_attr(idx, "cols")
1668 .and_then(|s| s.trim().parse::<i64>().ok())
1669 .filter(|n| *n >= 1)
1670 .unwrap_or(20);
1671 Value::Number(v as f64)
1672 }
1673 "autocomplete" => Value::str(self.dom.borrow().get_attr(idx, "autocomplete").unwrap_or_default()),
1678 "autofocus" => Value::Bool(self.dom.borrow().has_attr(idx, "autofocus")),
1679 "enterKeyHint" => {
1680 Value::str(self.dom.borrow().get_attr(idx, "enterkeyhint").unwrap_or_default())
1681 }
1682 "autocapitalize" => {
1686 Value::str(self.dom.borrow().get_attr(idx, "autocapitalize").unwrap_or_default())
1687 }
1688 "autoplay" => Value::Bool(self.dom.borrow().has_attr(idx, "autoplay")),
1693 "controls" => Value::Bool(self.dom.borrow().has_attr(idx, "controls")),
1694 "loop" => Value::Bool(self.dom.borrow().has_attr(idx, "loop")),
1695 "muted" => Value::Bool(self.dom.borrow().has_attr(idx, "muted")),
1696 "playsInline" => Value::Bool(self.dom.borrow().has_attr(idx, "playsinline")),
1697 "disablePictureInPicture" => {
1702 Value::Bool(self.dom.borrow().has_attr(idx, "disablepictureinpicture"))
1703 }
1704 "disableRemotePlayback" => {
1708 Value::Bool(self.dom.borrow().has_attr(idx, "disableremoteplayback"))
1709 }
1710 "allowFullscreen" => Value::Bool(self.dom.borrow().has_attr(idx, "allowfullscreen")),
1711 "credentialless" => Value::Bool(self.dom.borrow().has_attr(idx, "credentialless")),
1715 "width" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "canvas").unwrap_or(false) => {
1723 let v = self
1724 .dom
1725 .borrow()
1726 .get_attr(idx, "width")
1727 .and_then(|s| s.trim().parse::<i64>().ok())
1728 .filter(|n| *n >= 0)
1729 .unwrap_or(300);
1730 Value::Number(v as f64)
1731 }
1732 "height" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "canvas").unwrap_or(false) => {
1733 let v = self
1734 .dom
1735 .borrow()
1736 .get_attr(idx, "height")
1737 .and_then(|s| s.trim().parse::<i64>().ok())
1738 .filter(|n| *n >= 0)
1739 .unwrap_or(150);
1740 Value::Number(v as f64)
1741 }
1742 "getContext" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "canvas").unwrap_or(false) => {
1743 Value::Object(Obj::native("getContext", super::super::builtins::dom_canvas_get_context))
1744 }
1745 "toDataURL" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "canvas").unwrap_or(false) => {
1746 Value::Object(Obj::native("toDataURL", super::super::builtins::dom_canvas_to_data_url))
1747 }
1748 "toBlob" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "canvas").unwrap_or(false) => {
1749 Value::Object(Obj::native("toBlob", super::super::builtins::dom_canvas_to_blob))
1750 }
1751 "width" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "img" || n.tag == "video").unwrap_or(false) => {
1752 let v = self
1753 .dom
1754 .borrow()
1755 .get_attr(idx, "width")
1756 .and_then(|s| s.trim().parse::<i64>().ok())
1757 .filter(|n| *n >= 0)
1758 .unwrap_or(0);
1759 Value::Number(v as f64)
1760 }
1761 "height" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "img" || n.tag == "video").unwrap_or(false) => {
1762 let v = self
1763 .dom
1764 .borrow()
1765 .get_attr(idx, "height")
1766 .and_then(|s| s.trim().parse::<i64>().ok())
1767 .filter(|n| *n >= 0)
1768 .unwrap_or(0);
1769 Value::Number(v as f64)
1770 }
1771 "width"
1777 if self
1778 .dom
1779 .borrow()
1780 .nodes
1781 .get(idx)
1782 .map(|n| n.tag == "embed" || n.tag == "object" || n.tag == "iframe")
1783 .unwrap_or(false) =>
1784 {
1785 Value::str(self.dom.borrow().get_attr(idx, "width").unwrap_or_default())
1786 }
1787 "height"
1788 if self
1789 .dom
1790 .borrow()
1791 .nodes
1792 .get(idx)
1793 .map(|n| n.tag == "embed" || n.tag == "object" || n.tag == "iframe")
1794 .unwrap_or(false) =>
1795 {
1796 Value::str(self.dom.borrow().get_attr(idx, "height").unwrap_or_default())
1797 }
1798 "paused" => Value::Bool(self.dom.borrow().get_attr(idx, "_paused").as_deref() != Some("false")),
1806 "currentTime" => Value::Number(
1807 self.dom
1808 .borrow()
1809 .get_attr(idx, "_current_time")
1810 .and_then(|v| v.parse::<f64>().ok())
1811 .unwrap_or(0.0),
1812 ),
1813 "duration" => Value::Number(f64::NAN),
1814 "volume" => Value::Number(
1815 self.dom
1816 .borrow()
1817 .get_attr(idx, "_volume")
1818 .and_then(|v| v.parse::<f64>().ok())
1819 .unwrap_or(1.0),
1820 ),
1821 "playbackRate" => Value::Number(
1822 self.dom
1823 .borrow()
1824 .get_attr(idx, "_playback_rate")
1825 .and_then(|v| v.parse::<f64>().ok())
1826 .unwrap_or(1.0),
1827 ),
1828 "ended" => Value::Bool(self.dom.borrow().has_attr(idx, "_ended")),
1829 "readyState" => Value::Number(0.0),
1837 "networkState" => {
1838 let has_src = self.dom.borrow().has_attr(idx, "src")
1839 || self.dom.borrow().has_attr(idx, "srcset");
1840 Value::Number(if has_src { 3.0 } else { 0.0 })
1841 }
1842 "error" => Value::Null,
1843 "nonce" => Value::str(self.dom.borrow().get_attr(idx, "nonce").unwrap_or_default()),
1848 "role" => match self.dom.borrow().get_attr(idx, "role") {
1858 Some(v) => Value::str(v),
1859 None => Value::Null,
1860 },
1861 "ariaActiveDescendantElement" => {
1868 let dom = self.dom.borrow();
1869 match dom.get_attr(idx, "aria-activedescendant").filter(|f| !f.is_empty()) {
1870 Some(target_id) => match dom.get_element_by_id(&target_id) {
1871 Some(ti) => Value::Object(Obj::dom(ti)),
1872 None => Value::Null,
1873 },
1874 None => Value::Null,
1875 }
1876 }
1877 _ if key.starts_with("aria") && key.ends_with("Elements") && key.len() > 12 => {
1888 let suffix: String =
1889 key.chars().skip(4).take(key.len() - 4 - 8).collect::<String>().to_lowercase();
1890 let attr = alloc::format!("aria-{}", suffix);
1891 let dom = self.dom.borrow();
1892 let ids = dom.get_attr(idx, &attr).unwrap_or_default();
1893 let elems: Vec<Value> = ids
1894 .split_whitespace()
1895 .filter_map(|id| dom.get_element_by_id(id))
1896 .map(|ti| Value::Object(Obj::dom(ti)))
1897 .collect();
1898 Value::Object(Obj::array(elems))
1899 }
1900 _ if key.starts_with("aria") && key.as_bytes().get(4).is_some_and(u8::is_ascii_uppercase) => {
1901 let suffix: String = key.chars().skip(4).collect::<String>().to_lowercase();
1902 let attr = alloc::format!("aria-{}", suffix);
1903 match self.dom.borrow().get_attr(idx, &attr) {
1904 Some(v) => Value::str(v),
1905 None => Value::Null,
1906 }
1907 }
1908 "buffered" | "seekable" | "played" => {
1913 Value::Object(super::super::builtins::make_empty_time_ranges())
1918 }
1919 "videoWidth" | "videoHeight"
1925 if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "video").unwrap_or(false) =>
1926 {
1927 Value::Number(0.0)
1928 }
1929 "seeking" => Value::Bool(false),
1930 "getVideoPlaybackQuality"
1935 if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "video").unwrap_or(false) =>
1936 {
1937 Value::Object(Obj::native("getVideoPlaybackQuality", |_, _, _| {
1938 let q = Obj::plain();
1939 {
1940 let mut b = q.borrow_mut();
1941 b.props.insert("creationTime".into(), Value::Number(0.0));
1942 b.props.insert("totalVideoFrames".into(), Value::Number(0.0));
1943 b.props.insert("droppedVideoFrames".into(), Value::Number(0.0));
1944 b.props.insert("corruptedVideoFrames".into(), Value::Number(0.0));
1945 }
1946 Ok(Value::Object(q))
1947 }))
1948 }
1949 "defaultPlaybackRate" => Value::Number(
1954 self.dom
1955 .borrow()
1956 .get_attr(idx, "_default_playback_rate")
1957 .and_then(|v| v.parse::<f64>().ok())
1958 .unwrap_or(1.0),
1959 ),
1960 "preload" => {
1966 let v = self.dom.borrow().get_attr(idx, "preload").map(|s| s.to_lowercase());
1967 match v.as_deref() {
1968 None => Value::str("metadata"),
1969 Some("none") => Value::str("none"),
1970 Some("metadata") => Value::str("metadata"),
1971 Some(_) => Value::str("auto"),
1972 }
1973 }
1974 "poster" => Value::str(self.dom.borrow().get_attr(idx, "poster").unwrap_or_default()),
1976 "kind" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "track").unwrap_or(false) => {
1982 let v = self.dom.borrow().get_attr(idx, "kind").map(|s| s.to_lowercase());
1983 match v.as_deref() {
1984 None => Value::str("subtitles"),
1985 Some("subtitles") => Value::str("subtitles"),
1986 Some("captions") => Value::str("captions"),
1987 Some("descriptions") => Value::str("descriptions"),
1988 Some("chapters") => Value::str("chapters"),
1989 Some(_) => Value::str("metadata"),
1990 }
1991 }
1992 "ping" => Value::str(self.dom.borrow().get_attr(idx, "ping").unwrap_or_default()),
1995 "hreflang" => Value::str(self.dom.borrow().get_attr(idx, "hreflang").unwrap_or_default()),
1996 "srcset" => Value::str(self.dom.borrow().get_attr(idx, "srcset").unwrap_or_default()),
2000 "sizes" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "link").unwrap_or(false) => {
2008 let v = self.dom.borrow().get_attr(idx, "sizes").unwrap_or_default();
2009 Value::Object(Obj::array(v.split_whitespace().map(Value::str).collect()))
2010 }
2011 "sizes" => Value::str(self.dom.borrow().get_attr(idx, "sizes").unwrap_or_default()),
2012 "media" => Value::str(self.dom.borrow().get_attr(idx, "media").unwrap_or_default()),
2016 "complete" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "img").unwrap_or(false) => {
2027 Value::Bool(self.dom.borrow().get_attr(idx, "src").map(|s| !s.is_empty()).unwrap_or(false))
2028 }
2029 "naturalWidth" | "naturalHeight"
2030 if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "img").unwrap_or(false) =>
2031 {
2032 Value::Number(0.0)
2033 }
2034 "useMap" => Value::str(self.dom.borrow().get_attr(idx, "usemap").unwrap_or_default()),
2036 "decode" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "img").unwrap_or(false) => {
2040 Value::Object(Obj::native("decode", super::super::builtins::dom_img_decode))
2041 }
2042 "isMap" => Value::Bool(self.dom.borrow().has_attr(idx, "ismap")),
2043 "currentSrc" => Value::str(self.dom.borrow().get_attr(idx, "src").unwrap_or_default()),
2048 "crossOrigin" => match self.dom.borrow().get_attr(idx, "crossorigin") {
2052 Some(v) => Value::str(v),
2053 None => Value::Null,
2054 },
2055 "fetchPriority" => Value::str(
2058 self.dom.borrow().get_attr(idx, "fetchpriority").unwrap_or_else(|| "auto".into()),
2059 ),
2060 "integrity" => Value::str(self.dom.borrow().get_attr(idx, "integrity").unwrap_or_default()),
2063 "async" => Value::Bool(self.dom.borrow().has_attr(idx, "async")),
2066 "defer" => Value::Bool(self.dom.borrow().has_attr(idx, "defer")),
2067 "noModule" => Value::Bool(self.dom.borrow().has_attr(idx, "nomodule")),
2068 "referrerPolicy" => {
2072 Value::str(self.dom.borrow().get_attr(idx, "referrerpolicy").unwrap_or_default())
2073 }
2074 "as" => Value::str(self.dom.borrow().get_attr(idx, "as").unwrap_or_default()),
2078 "imageSrcset" => Value::str(self.dom.borrow().get_attr(idx, "imagesrcset").unwrap_or_default()),
2083 "imageSizes" => Value::str(self.dom.borrow().get_attr(idx, "imagesizes").unwrap_or_default()),
2084 "loading" => {
2090 let v = self.dom.borrow().get_attr(idx, "loading").map(|s| s.to_lowercase());
2091 match v.as_deref() {
2092 Some("lazy") => Value::str("lazy"),
2093 Some("eager") => Value::str("eager"),
2094 _ => Value::str("auto"),
2095 }
2096 }
2097 "decoding" => {
2101 let v = self.dom.borrow().get_attr(idx, "decoding").map(|s| s.to_lowercase());
2102 match v.as_deref() {
2103 Some("sync") => Value::str("sync"),
2104 Some("async") => Value::str("async"),
2105 _ => Value::str("auto"),
2106 }
2107 }
2108 "allow" => Value::str(self.dom.borrow().get_attr(idx, "allow").unwrap_or_default()),
2110 "srcdoc" => Value::str(self.dom.borrow().get_attr(idx, "srcdoc").unwrap_or_default()),
2114 "sandbox" => {
2119 let v = self.dom.borrow().get_attr(idx, "sandbox").unwrap_or_default();
2120 Value::Object(Obj::array(v.split_whitespace().map(Value::str).collect()))
2121 }
2122 "popover" => match self.dom.borrow().get_attr(idx, "popover") {
2127 Some(v) => Value::str(v),
2128 None => Value::Null,
2129 },
2130 "relList" => {
2143 let rel = self.dom.borrow().get_attr(idx, "rel").unwrap_or_default();
2144 let tokens: Vec<Value> =
2145 rel.split_whitespace().map(Value::str).collect();
2146 Value::Object(Obj::array(tokens))
2147 }
2148 "part" => {
2155 let v = self.dom.borrow().get_attr(idx, "part").unwrap_or_default();
2156 Value::Object(Obj::array(v.split_whitespace().map(Value::str).collect()))
2157 }
2158 "blocking" => {
2163 let v = self.dom.borrow().get_attr(idx, "blocking").unwrap_or_default();
2164 Value::Object(Obj::array(v.split_whitespace().map(Value::str).collect()))
2165 }
2166 "list" => {
2171 let dom = self.dom.borrow();
2172 match dom.get_attr(idx, "list").filter(|f| !f.is_empty()) {
2173 Some(target_id) => match dom.get_element_by_id(&target_id) {
2174 Some(ti) if dom.nodes.get(ti).map(|n| n.tag == "datalist").unwrap_or(false) => {
2175 Value::Object(Obj::dom(ti))
2176 }
2177 _ => Value::Null,
2178 },
2179 None => Value::Null,
2180 }
2181 }
2182 "popoverTargetElement" => {
2183 let dom = self.dom.borrow();
2184 match dom.get_attr(idx, "popovertarget").filter(|f| !f.is_empty()) {
2185 Some(target_id) => match dom.get_element_by_id(&target_id) {
2186 Some(ti) => Value::Object(Obj::dom(ti)),
2187 None => Value::Null,
2188 },
2189 None => Value::Null,
2190 }
2191 }
2192 "popoverTargetAction" => {
2193 Value::str(self.dom.borrow().get_attr(idx, "popovertargetaction").unwrap_or_default())
2194 }
2195 "command" => Value::str(self.dom.borrow().get_attr(idx, "command").unwrap_or_default()),
2205 "commandForElement" => {
2206 let dom = self.dom.borrow();
2207 match dom.get_attr(idx, "commandfor").filter(|f| !f.is_empty()) {
2208 Some(target_id) => match dom.get_element_by_id(&target_id) {
2209 Some(ti) => Value::Object(Obj::dom(ti)),
2210 None => Value::Null,
2211 },
2212 None => Value::Null,
2213 }
2214 }
2215 "style" => Value::Object(Obj::host(&format!("style:{}", idx))),
2216 "classList" => Value::Object(Obj::host(&format!("classList:{}", idx))),
2217 "dataset" => Value::Object(Obj::host(&format!("dataset:{}", idx))),
2218 "addEventListener" => Value::Object(Obj::native(
2219 "addEventListener",
2220 super::super::builtins::dom_add_event_listener,
2221 )),
2222 "removeEventListener" => Value::Object(Obj::native(
2223 "removeEventListener",
2224 super::super::builtins::dom_remove_event_listener,
2225 )),
2226 "dispatchEvent" => Value::Object(Obj::native(
2227 "dispatchEvent",
2228 super::super::builtins::dom_dispatch_event,
2229 )),
2230 "hasChildNodes" => Value::Object(Obj::native(
2232 "hasChildNodes",
2233 super::super::builtins::dom_has_child_nodes,
2234 )),
2235 "getAttribute" => Value::Object(Obj::native(
2236 "getAttribute",
2237 super::super::builtins::dom_get_attribute,
2238 )),
2239 "getAttributeNode" => Value::Object(Obj::native(
2243 "getAttributeNode",
2244 super::super::builtins::dom_get_attribute_node,
2245 )),
2246 "getAttributeNodeNS" => Value::Object(Obj::native(
2247 "getAttributeNodeNS",
2248 super::super::builtins::dom_get_attribute_node_ns,
2249 )),
2250 "setAttributeNode" => Value::Object(Obj::native(
2251 "setAttributeNode",
2252 super::super::builtins::dom_set_attribute_node,
2253 )),
2254 "removeAttributeNode" => Value::Object(Obj::native(
2255 "removeAttributeNode",
2256 super::super::builtins::dom_remove_attribute_node,
2257 )),
2258 "setAttribute" => Value::Object(Obj::native(
2259 "setAttribute",
2260 super::super::builtins::dom_set_attribute,
2261 )),
2262 "hasAttribute" => Value::Object(Obj::native(
2263 "hasAttribute",
2264 super::super::builtins::dom_has_attribute,
2265 )),
2266 "removeAttribute" => Value::Object(Obj::native(
2267 "removeAttribute",
2268 super::super::builtins::dom_remove_attribute,
2269 )),
2270 "getAttributeNS" => Value::Object(Obj::native(
2271 "getAttributeNS",
2272 super::super::builtins::dom_get_attribute_ns,
2273 )),
2274 "setAttributeNS" => Value::Object(Obj::native(
2275 "setAttributeNS",
2276 super::super::builtins::dom_set_attribute_ns,
2277 )),
2278 "hasAttributeNS" => Value::Object(Obj::native(
2279 "hasAttributeNS",
2280 super::super::builtins::dom_has_attribute_ns,
2281 )),
2282 "removeAttributeNS" => Value::Object(Obj::native(
2283 "removeAttributeNS",
2284 super::super::builtins::dom_remove_attribute_ns,
2285 )),
2286 "getAttributeNames" => Value::Object(Obj::native(
2287 "getAttributeNames",
2288 super::super::builtins::dom_get_attribute_names,
2289 )),
2290 "hasAttributes" => Value::Object(Obj::native(
2291 "hasAttributes",
2292 super::super::builtins::dom_has_attributes,
2293 )),
2294 "normalize" => Value::Object(Obj::native("normalize", super::super::builtins::dom_normalize)),
2295 "isSameNode" => Value::Object(Obj::native("isSameNode", super::super::builtins::dom_is_same_node)),
2296 "isEqualNode" => {
2297 Value::Object(Obj::native("isEqualNode", super::super::builtins::dom_is_equal_node))
2298 }
2299 "matches" | "webkitMatchesSelector" => {
2300 Value::Object(Obj::native("matches", super::super::builtins::dom_matches))
2301 }
2302 "closest" => Value::Object(Obj::native("closest", super::super::builtins::dom_closest)),
2303 "getBoundingClientRect" => Value::Object(Obj::native(
2304 "getBoundingClientRect",
2305 super::super::builtins::dom_get_bounding_client_rect,
2306 )),
2307 "getClientRects" => Value::Object(Obj::native(
2308 "getClientRects",
2309 super::super::builtins::dom_get_client_rects,
2310 )),
2311 "setPointerCapture" => Value::Object(Obj::native(
2312 "setPointerCapture",
2313 super::super::builtins::dom_set_pointer_capture,
2314 )),
2315 "releasePointerCapture" => Value::Object(Obj::native(
2316 "releasePointerCapture",
2317 super::super::builtins::dom_release_pointer_capture,
2318 )),
2319 "hasPointerCapture" => Value::Object(Obj::native(
2320 "hasPointerCapture",
2321 super::super::builtins::dom_has_pointer_capture,
2322 )),
2323 "offsetWidth" => {
2324 Value::Number(self.dom.borrow().get_rect(idx).map(|r| r.2).unwrap_or(0) as f64)
2325 }
2326 "clientWidth" => {
2327 let dom = self.dom.borrow();
2328 let w = dom.get_rect(idx).map(|r| r.2).unwrap_or(0);
2329 let borders = dom.get_border_widths(idx);
2330 Value::Number((w - borders.3 - borders.1).max(0) as f64)
2331 }
2332 "offsetHeight" => {
2333 Value::Number(self.dom.borrow().get_rect(idx).map(|r| r.3).unwrap_or(0) as f64)
2334 }
2335 "clientHeight" => {
2336 let dom = self.dom.borrow();
2337 let h = dom.get_rect(idx).map(|r| r.3).unwrap_or(0);
2338 let borders = dom.get_border_widths(idx);
2339 Value::Number((h - borders.0 - borders.2).max(0) as f64)
2340 }
2341 "offsetLeft" => {
2342 Value::Number(self.dom.borrow().get_rect(idx).map(|r| r.0).unwrap_or(0) as f64)
2343 }
2344 "offsetTop" => {
2345 Value::Number(self.dom.borrow().get_rect(idx).map(|r| r.1).unwrap_or(0) as f64)
2346 }
2347 "offsetParent" => match self.dom.borrow().offset_parent(idx) {
2351 Some(pi) => Value::Object(Obj::dom(pi)),
2352 None => Value::Null,
2353 },
2354 "scrollWidth" => {
2355 Value::Number(self.dom.borrow().get_rect(idx).map(|r| r.2).unwrap_or(0) as f64)
2356 }
2357 "scrollHeight" => {
2358 let dom = self.dom.borrow();
2359 let v = dom.scroll_heights.get(&idx).copied().unwrap_or_else(|| {
2360 dom.get_rect(idx).map(|r| r.3).unwrap_or(0)
2361 });
2362 Value::Number(v as f64)
2363 }
2364 "scrollTop" => {
2365 let v = self.dom.borrow().scroll_tops.get(&idx).copied().unwrap_or(0);
2366 Value::Number(v as f64)
2367 }
2368 "clientLeft" => {
2369 let borders = self.dom.borrow().get_border_widths(idx);
2370 Value::Number(borders.3 as f64)
2371 }
2372 "clientTop" => {
2373 let borders = self.dom.borrow().get_border_widths(idx);
2374 Value::Number(borders.0 as f64)
2375 }
2376 "scrollLeft" => Value::Number(0.0),
2377 "nodeType" => {
2382 let tag = self.dom.borrow().nodes.get(idx).map(|n| n.tag.clone()).unwrap_or_default();
2383 let is_text = self.dom.borrow().nodes.get(idx).map(|n| n.is_text).unwrap_or(false);
2384 let num = if is_text {
2385 3.0
2386 } else if tag == "#comment" {
2387 8.0
2388 } else if tag == "#document-fragment" || tag == "#fragment" {
2389 11.0
2390 } else {
2391 1.0
2392 };
2393 Value::Number(num)
2394 }
2395 "nodeName" => {
2396 let dom = self.dom.borrow();
2397 let name = match dom.nodes.get(idx) {
2398 Some(n) if n.is_text => String::from("#text"),
2399 Some(n) if n.tag == "#comment" => String::from("#comment"),
2400 Some(n) if n.tag == "#document-fragment" || n.tag == "#fragment" => String::from("#document-fragment"),
2401 Some(n) => n.tag.to_uppercase(),
2402 None => String::new(),
2403 };
2404 Value::str(name)
2405 }
2406 "nodeValue" => {
2411 let dom = self.dom.borrow();
2412 match dom.nodes.get(idx) {
2413 Some(n) if n.is_text || n.tag == "#comment" => Value::str(dom.get_text_content(idx)),
2414 _ => Value::Null,
2415 }
2416 }
2417 "data" if self.dom.borrow().nodes.get(idx).map(|n| n.is_text || n.tag == "#comment").unwrap_or(false) => {
2424 Value::str(self.dom.borrow().get_text_content(idx))
2425 }
2426 "length" if self.dom.borrow().nodes.get(idx).map(|n| n.is_text || n.tag == "#comment").unwrap_or(false) => {
2427 Value::Number(self.dom.borrow().get_text_content(idx).chars().count() as f64)
2428 }
2429 "appendData" if self.dom.borrow().nodes.get(idx).map(|n| n.is_text || n.tag == "#comment").unwrap_or(false) => {
2434 Value::Object(Obj::native("appendData", super::super::builtins::dom_char_data_append))
2435 }
2436 "deleteData" if self.dom.borrow().nodes.get(idx).map(|n| n.is_text || n.tag == "#comment").unwrap_or(false) => {
2437 Value::Object(Obj::native("deleteData", super::super::builtins::dom_char_data_delete))
2438 }
2439 "insertData" if self.dom.borrow().nodes.get(idx).map(|n| n.is_text || n.tag == "#comment").unwrap_or(false) => {
2440 Value::Object(Obj::native("insertData", super::super::builtins::dom_char_data_insert))
2441 }
2442 "replaceData" if self.dom.borrow().nodes.get(idx).map(|n| n.is_text || n.tag == "#comment").unwrap_or(false) => {
2443 Value::Object(Obj::native("replaceData", super::super::builtins::dom_char_data_replace))
2444 }
2445 "substringData" if self.dom.borrow().nodes.get(idx).map(|n| n.is_text || n.tag == "#comment").unwrap_or(false) => {
2446 Value::Object(Obj::native("substringData", super::super::builtins::dom_char_data_substring))
2447 }
2448 "splitText" if self.dom.borrow().nodes.get(idx).map(|n| n.is_text).unwrap_or(false) => {
2450 Value::Object(Obj::native("splitText", super::super::builtins::dom_split_text))
2451 }
2452 "wholeText" if self.dom.borrow().nodes.get(idx).map(|n| n.is_text).unwrap_or(false) => {
2457 let dom = self.dom.borrow();
2458 let parent = dom.nodes.get(idx).and_then(|n| n.parent);
2459 let out = match parent {
2460 Some(p) => {
2461 let is_text = |c: usize| dom.nodes.get(c).map(|n| n.is_text).unwrap_or(false);
2462 let children = &dom.nodes[p].children;
2463 let pos = children.iter().position(|&c| c == idx).unwrap_or(0);
2464 let mut start = pos;
2465 while start > 0 && is_text(children[start - 1]) {
2466 start -= 1;
2467 }
2468 let mut end = pos;
2469 while end + 1 < children.len() && is_text(children[end + 1]) {
2470 end += 1;
2471 }
2472 children[start..=end].iter().map(|&c| dom.get_text_content(c)).collect()
2473 }
2474 None => dom.get_text_content(idx),
2475 };
2476 Value::str(out)
2477 }
2478 "childNodes" => {
2484 let is_template = self.dom.borrow().nodes.get(idx).map(|n| n.tag == "template").unwrap_or(false);
2485 let kids = if is_template {
2486 Vec::new()
2487 } else {
2488 self.dom
2489 .borrow()
2490 .nodes
2491 .get(idx)
2492 .map(|n| n.children.clone())
2493 .unwrap_or_default()
2494 };
2495 Value::Object(Obj::array(
2496 kids.into_iter().map(|i| Value::Object(Obj::dom(i))).collect(),
2497 ))
2498 }
2499 "firstChild" => {
2500 let is_template = self.dom.borrow().nodes.get(idx).map(|n| n.tag == "template").unwrap_or(false);
2501 let first = if is_template {
2502 None
2503 } else {
2504 self.dom
2505 .borrow()
2506 .nodes
2507 .get(idx)
2508 .and_then(|n| n.children.first().copied())
2509 };
2510 match first {
2511 Some(i) => Value::Object(Obj::dom(i)),
2512 None => Value::Null,
2513 }
2514 }
2515 "lastChild" => {
2516 let is_template = self.dom.borrow().nodes.get(idx).map(|n| n.tag == "template").unwrap_or(false);
2517 let last = if is_template {
2518 None
2519 } else {
2520 self.dom
2521 .borrow()
2522 .nodes
2523 .get(idx)
2524 .and_then(|n| n.children.last().copied())
2525 };
2526 match last {
2527 Some(i) => Value::Object(Obj::dom(i)),
2528 None => Value::Null,
2529 }
2530 }
2531 "nextSibling" | "previousSibling" => {
2532 let dom = self.dom.borrow();
2533 let sib = dom.nodes.get(idx).and_then(|n| n.parent).and_then(|p| {
2534 let kids = &dom.nodes.get(p)?.children;
2535 let pos = kids.iter().position(|&c| c == idx)?;
2536 if key == "nextSibling" {
2537 kids.get(pos + 1).copied()
2538 } else if pos > 0 {
2539 kids.get(pos - 1).copied()
2540 } else {
2541 None
2542 }
2543 });
2544 match sib {
2545 Some(i) => Value::Object(Obj::dom(i)),
2546 None => Value::Null,
2547 }
2548 }
2549 "hidden" => Value::Bool(self.dom.borrow().has_attr(idx, "hidden")),
2550 "querySelector" => Value::Object(Obj::native(
2551 "querySelector",
2552 super::super::builtins::dom_element_query_selector,
2553 )),
2554 "querySelectorAll" => Value::Object(Obj::native(
2555 "querySelectorAll",
2556 super::super::builtins::dom_element_query_selector_all,
2557 )),
2558 "getElementsByClassName" => Value::Object(Obj::native(
2562 "getElementsByClassName",
2563 super::super::builtins::dom_element_get_elements_by_class_name,
2564 )),
2565 "getElementsByTagName" => Value::Object(Obj::native(
2566 "getElementsByTagName",
2567 super::super::builtins::dom_element_get_elements_by_tag_name,
2568 )),
2569 "getElementsByTagNameNS" => Value::Object(Obj::native(
2570 "getElementsByTagNameNS",
2571 super::super::builtins::dom_element_get_elements_by_tag_name_ns,
2572 )),
2573 "appendChild" => Value::Object(Obj::native(
2574 "appendChild",
2575 super::super::builtins::dom_append_child,
2576 )),
2577 "insertBefore" => Value::Object(Obj::native(
2578 "insertBefore",
2579 super::super::builtins::dom_insert_before,
2580 )),
2581 "moveBefore" => Value::Object(Obj::native(
2584 "moveBefore",
2585 super::super::builtins::dom_move_before,
2586 )),
2587 "replaceChild" => Value::Object(Obj::native(
2588 "replaceChild",
2589 super::super::builtins::dom_replace_child,
2590 )),
2591 "cloneNode" => Value::Object(Obj::native("cloneNode", super::super::builtins::dom_clone_node)),
2592 "contains" => Value::Object(Obj::native("contains", super::super::builtins::dom_contains)),
2593 "compareDocumentPosition" => Value::Object(Obj::native(
2594 "compareDocumentPosition",
2595 super::super::builtins::dom_compare_document_position,
2596 )),
2597 "getRootNode" => Value::Object(Obj::native(
2598 "getRootNode",
2599 super::super::builtins::dom_get_root_node,
2600 )),
2601 "lookupNamespaceURI" => Value::Object(Obj::native(
2605 "lookupNamespaceURI",
2606 super::super::builtins::dom_lookup_namespace_uri,
2607 )),
2608 "lookupPrefix" => Value::Object(Obj::native(
2609 "lookupPrefix",
2610 super::super::builtins::dom_lookup_prefix,
2611 )),
2612 "checkVisibility" => Value::Object(Obj::native(
2614 "checkVisibility",
2615 super::super::builtins::dom_check_visibility,
2616 )),
2617 "isDefaultNamespace" => Value::Object(Obj::native(
2618 "isDefaultNamespace",
2619 super::super::builtins::dom_is_default_namespace,
2620 )),
2621 "requestFullscreen" => Value::Object(Obj::native(
2624 "requestFullscreen",
2625 super::super::builtins::dom_request_fullscreen,
2626 )),
2627 "requestPointerLock" => Value::Object(Obj::native(
2630 "requestPointerLock",
2631 super::super::builtins::dom_request_pointer_lock,
2632 )),
2633 "requestPictureInPicture" => Value::Object(Obj::native(
2636 "requestPictureInPicture",
2637 super::super::builtins::dom_request_picture_in_picture,
2638 )),
2639 "append" => Value::Object(Obj::native("append", super::super::builtins::dom_append)),
2640 "prepend" => Value::Object(Obj::native("prepend", super::super::builtins::dom_prepend)),
2641 "replaceChildren" => Value::Object(Obj::native(
2644 "replaceChildren",
2645 super::super::builtins::dom_replace_children,
2646 )),
2647 "before" => Value::Object(Obj::native("before", super::super::builtins::dom_before)),
2648 "after" => Value::Object(Obj::native("after", super::super::builtins::dom_after)),
2649 "replaceWith" => Value::Object(Obj::native(
2650 "replaceWith",
2651 super::super::builtins::dom_replace_with,
2652 )),
2653 "insertAdjacentHTML" => Value::Object(Obj::native(
2654 "insertAdjacentHTML",
2655 super::super::builtins::dom_insert_adjacent_html,
2656 )),
2657 "getHTML" => Value::Object(Obj::native("getHTML", super::super::builtins::dom_get_html)),
2661 "setHTMLUnsafe" => Value::Object(Obj::native(
2662 "setHTMLUnsafe",
2663 super::super::builtins::dom_set_html_unsafe,
2664 )),
2665 "insertAdjacentElement" => Value::Object(Obj::native(
2666 "insertAdjacentElement",
2667 super::super::builtins::dom_insert_adjacent_element,
2668 )),
2669 "insertAdjacentText" => Value::Object(Obj::native(
2670 "insertAdjacentText",
2671 super::super::builtins::dom_insert_adjacent_text,
2672 )),
2673 "toggleAttribute" => Value::Object(Obj::native(
2674 "toggleAttribute",
2675 super::super::builtins::dom_toggle_attribute,
2676 )),
2677 "show" => Value::Object(Obj::native("show", super::super::builtins::dom_dialog_show)),
2681 "showModal" => {
2682 Value::Object(Obj::native("showModal", super::super::builtins::dom_dialog_show_modal))
2683 }
2684 "close" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "dialog").unwrap_or(false) => {
2685 Value::Object(Obj::native("close", super::super::builtins::dom_dialog_close))
2686 }
2687 "requestClose"
2691 if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "dialog").unwrap_or(false) =>
2692 {
2693 Value::Object(Obj::native("requestClose", super::super::builtins::dom_dialog_request_close))
2694 }
2695 "showPopover" => Value::Object(Obj::native(
2698 "showPopover",
2699 super::super::builtins::dom_show_popover,
2700 )),
2701 "hidePopover" => Value::Object(Obj::native(
2702 "hidePopover",
2703 super::super::builtins::dom_hide_popover,
2704 )),
2705 "togglePopover" => Value::Object(Obj::native(
2706 "togglePopover",
2707 super::super::builtins::dom_toggle_popover,
2708 )),
2709 "removeChild" => Value::Object(Obj::native(
2710 "removeChild",
2711 super::super::builtins::dom_remove_child,
2712 )),
2713 "remove" => Value::Object(Obj::native("remove", super::super::builtins::dom_remove)),
2714 "ownerDocument" => match self.global.borrow().vars.get("document") {
2719 Some(d) => d.clone(),
2720 None => Value::Null,
2721 },
2722 "isConnected" => Value::Bool(self.dom.borrow().is_attached(idx)),
2728 "parentElement" | "parentNode" => {
2730 let p = self.dom.borrow().nodes.get(idx).and_then(|n| n.parent);
2731 match p {
2732 Some(pi) => Value::Object(Obj::dom(pi)),
2733 None => Value::Null,
2734 }
2735 }
2736 "children" => {
2737 let is_template = self.dom.borrow().nodes.get(idx).map(|n| n.tag == "template").unwrap_or(false);
2738 let kids = if is_template { Vec::new() } else { self.dom.borrow().element_children(idx) };
2739 super::super::builtins::make_html_collection(self, kids)
2740 }
2741 "childElementCount" => {
2742 let is_template = self.dom.borrow().nodes.get(idx).map(|n| n.tag == "template").unwrap_or(false);
2743 let count = if is_template { 0 } else { self.dom.borrow().element_children(idx).len() };
2744 Value::Number(count as f64)
2745 }
2746 "firstElementChild" => {
2747 let is_template = self.dom.borrow().nodes.get(idx).map(|n| n.tag == "template").unwrap_or(false);
2748 let first = if is_template { None } else { self.dom.borrow().element_children(idx).first().copied() };
2749 match first {
2750 Some(i) => Value::Object(Obj::dom(i)),
2751 None => Value::Null,
2752 }
2753 }
2754 "lastElementChild" => {
2755 let is_template = self.dom.borrow().nodes.get(idx).map(|n| n.tag == "template").unwrap_or(false);
2756 let last = if is_template { None } else { self.dom.borrow().element_children(idx).last().copied() };
2757 match last {
2758 Some(i) => Value::Object(Obj::dom(i)),
2759 None => Value::Null,
2760 }
2761 }
2762 "nextElementSibling" | "previousElementSibling" => {
2763 let dom = self.dom.borrow();
2764 let sib = dom.nodes.get(idx).and_then(|n| n.parent).and_then(|p| {
2765 let kids = dom.element_children(p);
2766 let pos = kids.iter().position(|&c| c == idx);
2767 match pos {
2768 Some(pi) if key == "nextElementSibling" => kids.get(pi + 1).copied(),
2769 Some(pi) if pi > 0 => kids.get(pi - 1).copied(),
2770 _ => None,
2771 }
2772 });
2773 match sib {
2774 Some(i) => Value::Object(Obj::dom(i)),
2775 None => Value::Null,
2776 }
2777 }
2778 "click" => Value::Object(Obj::native("click", super::super::builtins::dom_click)),
2779 "requestSubmit" => {
2788 Value::Object(Obj::native("requestSubmit", super::super::builtins::dom_request_submit))
2789 }
2790 "submit" => Value::Object(Obj::native("domNoop", |_, _, _| Ok(Value::Undefined))),
2791 "reset" => Value::Object(Obj::native("reset", super::super::builtins::dom_form_reset)),
2792 "showPicker" => Value::Object(Obj::native("domNoop", |_, _, _| Ok(Value::Undefined))),
2799 "getAnimations" => Value::Object(Obj::native(
2804 "getAnimations",
2805 |_, _, _| Ok(Value::Object(Obj::array(alloc::vec![]))),
2806 )),
2807 "stepUp" => Value::Object(Obj::native("stepUp", super::super::builtins::dom_step_up)),
2810 "stepDown" => Value::Object(Obj::native("stepDown", super::super::builtins::dom_step_down)),
2811 "scrollTo" => Value::Object(Obj::native("scrollTo", super::super::builtins::dom_scroll_to)),
2815 "scrollBy" => Value::Object(Obj::native("scrollBy", super::super::builtins::dom_scroll_by)),
2816 "scrollIntoView" => Value::Object(Obj::native("scrollIntoView", super::super::builtins::dom_scroll_into_view)),
2820 "scrollIntoViewIfNeeded" => Value::Object(Obj::native("scrollIntoViewIfNeeded", super::super::builtins::dom_scroll_into_view)),
2821 "focus" => Value::Object(Obj::native("focus", super::super::builtins::dom_focus)),
2825 "blur" => Value::Object(Obj::native("blur", super::super::builtins::dom_blur)),
2826 "play" => Value::Object(Obj::native("play", super::super::builtins::dom_media_play)),
2833 "pause" => Value::Object(Obj::native("pause", super::super::builtins::dom_media_pause)),
2834 "load" => Value::Object(Obj::native("domNoop", |_, _, _| Ok(Value::Undefined))),
2835 "canPlayType" => {
2840 Value::Object(Obj::native("canPlayType", |_, _, _| Ok(Value::str(""))))
2841 }
2842 "animate" => Value::Object(Obj::native("animate", |_, _, _| {
2844 let anim = Obj::plain();
2845 {
2846 let mut b = anim.borrow_mut();
2847 b.props.insert(
2848 "play".into(),
2849 Value::Object(Obj::native("play", |_, _, _| Ok(Value::Undefined))),
2850 );
2851 b.props.insert(
2852 "pause".into(),
2853 Value::Object(Obj::native("pause", |_, _, _| Ok(Value::Undefined))),
2854 );
2855 b.props.insert(
2856 "cancel".into(),
2857 Value::Object(Obj::native("cancel", |_, _, _| Ok(Value::Undefined))),
2858 );
2859 b.props.insert(
2863 "reverse".into(),
2864 Value::Object(Obj::native("reverse", |_, _, _| Ok(Value::Undefined))),
2865 );
2866 b.props.insert(
2867 "finish".into(),
2868 Value::Object(Obj::native("finish", |_, _, _| Ok(Value::Undefined))),
2869 );
2870 b.props.insert(
2871 "finished".into(),
2872 Value::Object(super::super::builtins::make_resolved_promise(Value::Undefined)),
2873 );
2874 b.props.insert("playState".into(), Value::str("finished"));
2875 b.props.insert("currentTime".into(), Value::Number(0.0));
2876 b.props.insert("playbackRate".into(), Value::Number(1.0));
2880 }
2881 Ok(Value::Object(anim))
2882 })),
2883 "checkValidity" => Value::Object(Obj::native(
2885 "checkValidity",
2886 super::super::builtins::dom_check_validity,
2887 )),
2888 "reportValidity" => Value::Object(Obj::native(
2889 "reportValidity",
2890 super::super::builtins::dom_check_validity,
2891 )),
2892 "setCustomValidity" => Value::Object(Obj::native(
2893 "setCustomValidity",
2894 super::super::builtins::dom_set_custom_validity,
2895 )),
2896 "validationMessage" => {
2897 let v = self.dom.borrow().effective_form_value(idx);
2898 Value::str(
2899 self.dom
2900 .borrow()
2901 .validate_field(idx, &v)
2902 .unwrap_or_default(),
2903 )
2904 }
2905 "willValidate" => {
2906 let dom = self.dom.borrow();
2915 let tag = dom.nodes.get(idx).map(|n| n.tag.as_str()).unwrap_or("");
2916 let is_candidate_tag = matches!(tag, "button" | "input" | "select" | "textarea");
2917 let disabled = dom.has_attr(idx, "disabled");
2918 let ty = dom.get_attr(idx, "type").unwrap_or_default().to_lowercase();
2919 let excluded_type = matches!(
2920 ty.as_str(),
2921 "submit" | "button" | "reset" | "hidden" | "image"
2922 );
2923 Value::Bool(is_candidate_tag && !disabled && !excluded_type)
2924 }
2925 "validity" => {
2932 let v = self.dom.borrow().effective_form_value(idx);
2933 let flags = self.dom.borrow().validity_flags(idx, &v);
2934 let vs = Obj::plain();
2935 {
2936 let mut b = vs.borrow_mut();
2937 b.props.insert("valid".into(), Value::Bool(flags.valid()));
2938 b.props
2939 .insert("valueMissing".into(), Value::Bool(flags.value_missing));
2940 b.props
2941 .insert("typeMismatch".into(), Value::Bool(flags.type_mismatch));
2942 b.props
2943 .insert("patternMismatch".into(), Value::Bool(flags.pattern_mismatch));
2944 b.props.insert("tooLong".into(), Value::Bool(flags.too_long));
2945 b.props.insert("tooShort".into(), Value::Bool(flags.too_short));
2946 b.props
2947 .insert("rangeOverflow".into(), Value::Bool(flags.range_overflow));
2948 b.props
2949 .insert("rangeUnderflow".into(), Value::Bool(flags.range_underflow));
2950 b.props
2951 .insert("stepMismatch".into(), Value::Bool(flags.step_mismatch));
2952 b.props.insert("badInput".into(), Value::Bool(flags.bad_input));
2953 b.props
2954 .insert("customError".into(), Value::Bool(flags.custom_error));
2955 }
2956 Value::Object(vs)
2957 }
2958 key if aria_attr_name(key).is_some() => {
2965 let attr = aria_attr_name(key).unwrap_or_default();
2966 match self.dom.borrow().get_attr(idx, attr) {
2967 Some(v) => Value::str(v),
2968 None => Value::Null,
2969 }
2970 }
2971 key if key.starts_with("on") && key.len() > 2 => {
2976 if let Some(val) = self.dom.borrow().get_on_handler(idx, key) {
2977 if !matches!(val, Value::Undefined) {
2978 return val;
2979 }
2980 }
2981 match self.dom.borrow().get_attr(idx, key) {
2982 Some(v) => Value::str(v),
2983 None => Value::Null,
2984 }
2985 }
2986 _ => Value::Undefined,
2987 }
2988 }
2989
2990 pub(crate) fn dom_set_property(&mut self, disp: DomDisp, key: &str, val: Value) {
2991 let s = val.to_js_string();
2992 let s_nullable = if matches!(val, Value::Null | Value::Undefined) {
3000 String::new()
3001 } else {
3002 s.clone()
3003 };
3004 match disp {
3005 DomDisp::Element(idx) => match key {
3006 "textContent" | "innerText" | "text" => {
3007 self.dom.borrow_mut().set_text_content(idx, &s_nullable)
3008 }
3009 "outerText" => {
3016 let parent = self.dom.borrow().nodes.get(idx).and_then(|n| n.parent);
3017 if let Some(parent) = parent {
3018 let text_idx = self.dom.borrow_mut().create_text_node(&s_nullable);
3019 self.dom.borrow_mut().replace_child(parent, text_idx, idx);
3020 }
3021 }
3022 "nodeValue" => {
3028 let has_val = self.dom.borrow().nodes.get(idx).map(|n| n.is_text || n.tag == "#comment").unwrap_or(false);
3029 if has_val {
3030 self.dom.borrow_mut().set_text_content(idx, &s_nullable);
3031 }
3032 }
3033 "data" if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "object").unwrap_or(false) => {
3037 self.dom.borrow_mut().set_attr(idx, "data", &s);
3038 }
3039 "data" => {
3042 let has_data = self.dom.borrow().nodes.get(idx).map(|n| n.is_text || n.tag == "#comment").unwrap_or(false);
3043 if has_data {
3044 self.dom.borrow_mut().set_text_content(idx, &s_nullable);
3045 }
3046 }
3047 "innerHTML" => {
3048 let is_template = self.dom.borrow().nodes.get(idx).map(|n| n.tag == "template").unwrap_or(false);
3049 if is_template {
3050 let frag_val = self.dom_element_get(idx, "content");
3051 if let Some(fi) = this_dom_idx(&frag_val) {
3052 self.dom.borrow_mut().set_inner_html(fi, &s_nullable);
3053 } else {
3054 self.dom.borrow_mut().set_inner_html(idx, &s_nullable);
3055 }
3056 } else {
3057 self.dom.borrow_mut().set_inner_html(idx, &s_nullable);
3058 }
3059 }
3060 "outerHTML" => self.dom.borrow_mut().set_outer_html(idx, &s_nullable),
3061 "id" => self.dom.borrow_mut().set_attr(idx, "id", &s),
3062 "className" => self.dom.borrow_mut().set_attr(idx, "class", &s),
3063 "value" => {
3064 let tag = self.dom.borrow().nodes.get(idx).map(|n| n.tag.clone()).unwrap_or_default();
3065 if tag == "select" {
3066 self.dom.borrow_mut().set_select_value(idx, &s);
3067 } else if tag == "input" || tag == "textarea" {
3068 let sanitized = if tag == "input"
3069 && self.dom.borrow().get_attr(idx, "type").as_deref() == Some("color")
3070 {
3071 let is_valid_hex = s.len() == 7
3072 && s.starts_with('#')
3073 && s.chars().skip(1).all(|c| c.is_ascii_hexdigit());
3074 if is_valid_hex {
3075 s.to_lowercase()
3076 } else {
3077 String::from("#000000")
3078 }
3079 } else {
3080 s
3081 };
3082 self.dom.borrow_mut().set_attr(idx, "_live_value", &sanitized);
3083 } else {
3084 self.dom.borrow_mut().set_attr(idx, "value", &s);
3085 }
3086 }
3087 "valueAsNumber" => {
3088 let n = val.to_number();
3089 let mut dom = self.dom.borrow_mut();
3090 if n.is_nan() {
3091 dom.set_attr(idx, "_live_value", "");
3092 dom.set_attr(idx, "value", "");
3093 } else {
3094 let str_v = Value::Number(n).to_js_string();
3095 dom.set_attr(idx, "_live_value", &str_v);
3096 dom.set_attr(idx, "value", &str_v);
3097 }
3098 }
3099 "valueAsDate" => {
3100 let ty = self.dom.borrow().get_attr(idx, "type").unwrap_or_default();
3101 let mut dom = self.dom.borrow_mut();
3102 if matches!(val, Value::Null | Value::Undefined) {
3103 dom.set_attr(idx, "_live_value", "");
3104 dom.set_attr(idx, "value", "");
3105 } else {
3106 let ms = super::super::builtins::date_value_ms(&val);
3107 if let Some(s) = super::super::builtins::input_date_ms_to_value(&ty, ms) {
3108 dom.set_attr(idx, "_live_value", &s);
3109 dom.set_attr(idx, "value", &s);
3110 } else {
3111 dom.set_attr(idx, "_live_value", "");
3112 dom.set_attr(idx, "value", "");
3113 }
3114 }
3115 }
3116 "selectedIndex" => {
3117 let n = val.to_number() as i32;
3118 self.dom.borrow_mut().set_select_selected_index(idx, n);
3119 }
3120 "length"
3125 if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "select").unwrap_or(false) =>
3126 {
3127 let n = val.to_number();
3128 let n = if n.is_finite() && n > 0.0 { n as usize } else { 0 };
3129 self.dom.borrow_mut().set_select_length(idx, n);
3130 }
3131 "selectionStart" => self.dom.borrow_mut().set_attr(idx, "_selection_start", &s),
3135 "selectionEnd" => self.dom.borrow_mut().set_attr(idx, "_selection_end", &s),
3136 "selectionDirection" => self.dom.borrow_mut().set_attr(idx, "_selection_direction", &s),
3137 "type" => self.dom.borrow_mut().set_attr(idx, "type", &s),
3138 "defaultValue" => self.dom.borrow_mut().set_attr(idx, "value", &s),
3141 "defaultChecked" => {
3142 let mut dom = self.dom.borrow_mut();
3143 if val.truthy() {
3144 dom.set_attr(idx, "checked", "checked");
3145 } else {
3146 dom.remove_attr(idx, "checked");
3147 }
3148 }
3149 "defaultSelected" => {
3150 let mut dom = self.dom.borrow_mut();
3151 if val.truthy() {
3152 dom.set_attr(idx, "selected", "selected");
3153 } else {
3154 dom.remove_attr(idx, "selected");
3155 }
3156 }
3157 "checked" => {
3161 let mut dom = self.dom.borrow_mut();
3162 if val.truthy() {
3163 dom.set_attr(idx, "_live_checked", "true");
3164 dom.uncheck_radio_group_siblings(idx);
3165 } else {
3166 dom.set_attr(idx, "_live_checked", "false");
3167 }
3168 }
3169 "open" => {
3172 let mut dom = self.dom.borrow_mut();
3173 if val.truthy() {
3174 dom.set_attr(idx, "open", "open");
3175 } else {
3176 dom.remove_attr(idx, "open");
3177 }
3178 }
3179 "inert" => {
3181 let mut dom = self.dom.borrow_mut();
3182 if val.truthy() {
3183 dom.set_attr(idx, "inert", "");
3184 } else {
3185 dom.remove_attr(idx, "inert");
3186 }
3187 }
3188 "returnValue" => {
3190 self.dom
3191 .borrow_mut()
3192 .set_attr(idx, "_return_value", &val.to_js_string());
3193 }
3194 "indeterminate" => {
3195 let mut dom = self.dom.borrow_mut();
3196 if val.truthy() {
3197 dom.set_attr(idx, "_indeterminate", "true");
3198 } else {
3199 dom.remove_attr(idx, "_indeterminate");
3200 }
3201 }
3202 "disabled" => {
3203 let mut dom = self.dom.borrow_mut();
3204 if val.truthy() {
3205 dom.set_attr(idx, "disabled", "disabled");
3206 } else {
3207 dom.remove_attr(idx, "disabled");
3208 }
3209 }
3210 "selected" => {
3211 let mut dom = self.dom.borrow_mut();
3212 if val.truthy() {
3213 dom.set_attr(idx, "selected", "selected");
3214 } else {
3215 dom.remove_attr(idx, "selected");
3216 }
3217 }
3218
3219 "placeholder" => self.dom.borrow_mut().set_attr(idx, "placeholder", &s),
3220 "pattern" => self.dom.borrow_mut().set_attr(idx, "pattern", &s),
3223 "capture" => self.dom.borrow_mut().set_attr(idx, "capture", &s),
3226 "accept" => self.dom.borrow_mut().set_attr(idx, "accept", &s),
3229 "name" => self.dom.borrow_mut().set_attr(idx, "name", &s),
3230 "required" => {
3231 let mut dom = self.dom.borrow_mut();
3232 if val.truthy() {
3233 dom.set_attr(idx, "required", "required");
3234 } else {
3235 dom.remove_attr(idx, "required");
3236 }
3237 }
3238 "noValidate" => {
3239 let mut dom = self.dom.borrow_mut();
3240 if val.truthy() {
3241 dom.set_attr(idx, "novalidate", "novalidate");
3242 } else {
3243 dom.remove_attr(idx, "novalidate");
3244 }
3245 }
3246 "formNoValidate" => {
3247 let mut dom = self.dom.borrow_mut();
3248 if val.truthy() {
3249 dom.set_attr(idx, "formnovalidate", "formnovalidate");
3250 } else {
3251 dom.remove_attr(idx, "formnovalidate");
3252 }
3253 }
3254 "reversed" => {
3257 let mut dom = self.dom.borrow_mut();
3258 if val.truthy() {
3259 dom.set_attr(idx, "reversed", "reversed");
3260 } else {
3261 dom.remove_attr(idx, "reversed");
3262 }
3263 }
3264 "start" => self.dom.borrow_mut().set_attr(idx, "start", &s),
3265 "readOnly" => {
3266 let mut dom = self.dom.borrow_mut();
3267 if val.truthy() {
3268 dom.set_attr(idx, "readonly", "readonly");
3269 } else {
3270 dom.remove_attr(idx, "readonly");
3271 }
3272 }
3273 "hidden" => {
3278 let mut dom = self.dom.borrow_mut();
3279 if val.truthy() {
3280 dom.set_attr(idx, "hidden", "hidden");
3281 } else {
3282 dom.remove_attr(idx, "hidden");
3283 }
3284 }
3285 "multiple" => {
3286 let mut dom = self.dom.borrow_mut();
3287 if val.truthy() {
3288 dom.set_attr(idx, "multiple", "multiple");
3289 } else {
3290 dom.remove_attr(idx, "multiple");
3291 }
3292 }
3293 "src" => self.dom.borrow_mut().set_attr(idx, "src", &s),
3294 "alt" => self.dom.borrow_mut().set_attr(idx, "alt", &s),
3295 "autoplay" => {
3296 let mut dom = self.dom.borrow_mut();
3297 if val.truthy() {
3298 dom.set_attr(idx, "autoplay", "autoplay");
3299 } else {
3300 dom.remove_attr(idx, "autoplay");
3301 }
3302 }
3303 "controls" => {
3304 let mut dom = self.dom.borrow_mut();
3305 if val.truthy() {
3306 dom.set_attr(idx, "controls", "controls");
3307 } else {
3308 dom.remove_attr(idx, "controls");
3309 }
3310 }
3311 "loop" => {
3312 let mut dom = self.dom.borrow_mut();
3313 if val.truthy() {
3314 dom.set_attr(idx, "loop", "loop");
3315 } else {
3316 dom.remove_attr(idx, "loop");
3317 }
3318 }
3319 "muted" => {
3320 {
3321 let mut dom = self.dom.borrow_mut();
3322 if val.truthy() {
3323 dom.set_attr(idx, "muted", "muted");
3324 } else {
3325 dom.remove_attr(idx, "muted");
3326 }
3327 }
3328 let _ = self.dispatch_event_in_interp(idx, "volumechange", &[]);
3330 }
3331 "playsInline" => {
3332 let mut dom = self.dom.borrow_mut();
3333 if val.truthy() {
3334 dom.set_attr(idx, "playsinline", "playsinline");
3335 } else {
3336 dom.remove_attr(idx, "playsinline");
3337 }
3338 }
3339 "disablePictureInPicture" => {
3342 let mut dom = self.dom.borrow_mut();
3343 if val.truthy() {
3344 dom.set_attr(idx, "disablepictureinpicture", "disablepictureinpicture");
3345 } else {
3346 dom.remove_attr(idx, "disablepictureinpicture");
3347 }
3348 }
3349 "disableRemotePlayback" => {
3352 let mut dom = self.dom.borrow_mut();
3353 if val.truthy() {
3354 dom.set_attr(idx, "disableremoteplayback", "disableremoteplayback");
3355 } else {
3356 dom.remove_attr(idx, "disableremoteplayback");
3357 }
3358 }
3359 "allowFullscreen" => {
3360 let mut dom = self.dom.borrow_mut();
3361 if val.truthy() {
3362 dom.set_attr(idx, "allowfullscreen", "allowfullscreen");
3363 } else {
3364 dom.remove_attr(idx, "allowfullscreen");
3365 }
3366 }
3367 "credentialless" => {
3370 let mut dom = self.dom.borrow_mut();
3371 if val.truthy() {
3372 dom.set_attr(idx, "credentialless", "credentialless");
3373 } else {
3374 dom.remove_attr(idx, "credentialless");
3375 }
3376 }
3377 "width"
3380 if self
3381 .dom
3382 .borrow()
3383 .nodes
3384 .get(idx)
3385 .map(|n| n.tag == "canvas" || n.tag == "img" || n.tag == "video")
3386 .unwrap_or(false) =>
3387 {
3388 self.dom.borrow_mut().set_attr(idx, "width", &s);
3389 }
3390 "height"
3391 if self
3392 .dom
3393 .borrow()
3394 .nodes
3395 .get(idx)
3396 .map(|n| n.tag == "canvas" || n.tag == "img" || n.tag == "video")
3397 .unwrap_or(false) =>
3398 {
3399 self.dom.borrow_mut().set_attr(idx, "height", &s);
3400 }
3401 "width"
3404 if self
3405 .dom
3406 .borrow()
3407 .nodes
3408 .get(idx)
3409 .map(|n| n.tag == "embed" || n.tag == "object" || n.tag == "iframe")
3410 .unwrap_or(false) =>
3411 {
3412 self.dom.borrow_mut().set_attr(idx, "width", &s);
3413 }
3414 "height"
3415 if self
3416 .dom
3417 .borrow()
3418 .nodes
3419 .get(idx)
3420 .map(|n| n.tag == "embed" || n.tag == "object" || n.tag == "iframe")
3421 .unwrap_or(false) =>
3422 {
3423 self.dom.borrow_mut().set_attr(idx, "height", &s);
3424 }
3425 "currentTime" => {
3433 self.dom.borrow_mut().set_attr(idx, "_current_time", &val.to_number().to_string());
3434 let _ = self.dispatch_event_in_interp(idx, "seeked", &[]);
3435 }
3436 "volume" => {
3437 let v = val.to_number().clamp(0.0, 1.0);
3438 self.dom.borrow_mut().set_attr(idx, "_volume", &v.to_string());
3439 let _ = self.dispatch_event_in_interp(idx, "volumechange", &[]);
3440 }
3441 "playbackRate" => {
3442 self.dom.borrow_mut().set_attr(idx, "_playback_rate", &val.to_number().to_string());
3443 let _ = self.dispatch_event_in_interp(idx, "ratechange", &[]);
3444 }
3445 "defaultPlaybackRate" => self
3446 .dom
3447 .borrow_mut()
3448 .set_attr(idx, "_default_playback_rate", &val.to_number().to_string()),
3449 "nonce" => self.dom.borrow_mut().set_attr(idx, "nonce", &s),
3450 "role" => {
3454 if matches!(val, Value::Null) {
3455 self.dom.borrow_mut().remove_attr(idx, "role");
3456 } else {
3457 self.dom.borrow_mut().set_attr(idx, "role", &s);
3458 }
3459 }
3460 _ if key.starts_with("aria") && key.ends_with("Elements") && key.len() > 12 => {
3473 let suffix: String =
3474 key.chars().skip(4).take(key.len() - 4 - 8).collect::<String>().to_lowercase();
3475 let attr = alloc::format!("aria-{}", suffix);
3476 if matches!(val, Value::Null | Value::Undefined) {
3477 self.dom.borrow_mut().remove_attr(idx, &attr);
3478 } else {
3479 let items = self.iterate_values(&val, true);
3480 let ids: Vec<String> = items
3481 .iter()
3482 .filter_map(|v| match v {
3483 Value::Object(o) => match &o.borrow().kind {
3484 ObjKind::DomElement(ti) => {
3485 self.dom.borrow().nodes.get(*ti).map(|n| n.id.clone())
3486 }
3487 _ => None,
3488 },
3489 _ => None,
3490 })
3491 .filter(|s| !s.is_empty())
3492 .collect();
3493 self.dom.borrow_mut().set_attr(idx, &attr, &ids.join(" "));
3494 }
3495 }
3496 "ariaActiveDescendantElement" => match val {
3497 Value::Null => {
3498 self.dom.borrow_mut().remove_attr(idx, "aria-activedescendant");
3499 }
3500 Value::Object(o) if matches!(&o.borrow().kind, ObjKind::DomElement(_)) => {
3501 if let ObjKind::DomElement(ti) = &o.borrow().kind {
3502 let target_id = self.dom.borrow().nodes.get(*ti).map(|n| n.id.clone());
3503 if let Some(tid) = target_id.filter(|s| !s.is_empty()) {
3504 self.dom.borrow_mut().set_attr(idx, "aria-activedescendant", &tid);
3505 }
3506 }
3507 }
3508 _ => {}
3509 },
3510 _ if key.starts_with("aria") && key.as_bytes().get(4).is_some_and(u8::is_ascii_uppercase) => {
3511 let suffix: String = key.chars().skip(4).collect::<String>().to_lowercase();
3512 let attr = alloc::format!("aria-{}", suffix);
3513 if matches!(val, Value::Null) {
3514 self.dom.borrow_mut().remove_attr(idx, &attr);
3515 } else {
3516 self.dom.borrow_mut().set_attr(idx, &attr, &s);
3517 }
3518 }
3519 "preload" => self.dom.borrow_mut().set_attr(idx, "preload", &s),
3520 "wrap" => self.dom.borrow_mut().set_attr(idx, "wrap", &s),
3523 "poster" => self.dom.borrow_mut().set_attr(idx, "poster", &s),
3524 "kind" => self.dom.borrow_mut().set_attr(idx, "kind", &s),
3526 "label" => self.dom.borrow_mut().set_attr(idx, "label", &s),
3532 "srclang" => self.dom.borrow_mut().set_attr(idx, "srclang", &s),
3533 "default" => {
3534 let mut dom = self.dom.borrow_mut();
3535 if val.truthy() {
3536 dom.set_attr(idx, "default", "default");
3537 } else {
3538 dom.remove_attr(idx, "default");
3539 }
3540 }
3541 "ping" => self.dom.borrow_mut().set_attr(idx, "ping", &s),
3542 "hreflang" => self.dom.borrow_mut().set_attr(idx, "hreflang", &s),
3543 "srcset" => self.dom.borrow_mut().set_attr(idx, "srcset", &s),
3544 "sizes" => self.dom.borrow_mut().set_attr(idx, "sizes", &s),
3545 "media" => self.dom.borrow_mut().set_attr(idx, "media", &s),
3546 "useMap" => self.dom.borrow_mut().set_attr(idx, "usemap", &s),
3547 "isMap" => {
3548 let mut dom = self.dom.borrow_mut();
3549 if val.truthy() {
3550 dom.set_attr(idx, "ismap", "ismap");
3551 } else {
3552 dom.remove_attr(idx, "ismap");
3553 }
3554 }
3555 "referrerPolicy" => self.dom.borrow_mut().set_attr(idx, "referrerpolicy", &s),
3556 "as" => self.dom.borrow_mut().set_attr(idx, "as", &s),
3558 "imageSrcset" => self.dom.borrow_mut().set_attr(idx, "imagesrcset", &s),
3561 "imageSizes" => self.dom.borrow_mut().set_attr(idx, "imagesizes", &s),
3562 "loading" => self.dom.borrow_mut().set_attr(idx, "loading", &s),
3563 "decoding" => self.dom.borrow_mut().set_attr(idx, "decoding", &s),
3564 "allow" => self.dom.borrow_mut().set_attr(idx, "allow", &s),
3565 "srcdoc" => self.dom.borrow_mut().set_attr(idx, "srcdoc", &s),
3568 "crossOrigin" => {
3570 if matches!(val, Value::Null) {
3571 self.dom.borrow_mut().remove_attr(idx, "crossorigin");
3572 } else {
3573 self.dom.borrow_mut().set_attr(idx, "crossorigin", &s);
3574 }
3575 }
3576 "fetchPriority" => self.dom.borrow_mut().set_attr(idx, "fetchpriority", &s),
3577 "integrity" => self.dom.borrow_mut().set_attr(idx, "integrity", &s),
3578 "async" => {
3579 let mut dom = self.dom.borrow_mut();
3580 if val.truthy() {
3581 dom.set_attr(idx, "async", "async");
3582 } else {
3583 dom.remove_attr(idx, "async");
3584 }
3585 }
3586 "defer" => {
3587 let mut dom = self.dom.borrow_mut();
3588 if val.truthy() {
3589 dom.set_attr(idx, "defer", "defer");
3590 } else {
3591 dom.remove_attr(idx, "defer");
3592 }
3593 }
3594 "noModule" => {
3595 let mut dom = self.dom.borrow_mut();
3596 if val.truthy() {
3597 dom.set_attr(idx, "nomodule", "nomodule");
3598 } else {
3599 dom.remove_attr(idx, "nomodule");
3600 }
3601 }
3602 "href" => self.dom.borrow_mut().set_attr(idx, "href", &s),
3603 "protocol" | "host" | "hostname" | "port" | "pathname" | "search" | "hash"
3612 | "username" | "password"
3613 if self
3614 .dom
3615 .borrow()
3616 .nodes
3617 .get(idx)
3618 .map(|n| n.tag == "a" || n.tag == "area")
3619 .unwrap_or(false) =>
3620 {
3621 let raw = self.dom.borrow().get_attr(idx, "href").unwrap_or_default();
3622 let abs = self.resolve_url_against_page(&raw);
3623 let comps = super::super::builtins::location_components(&abs);
3624 let get = |k: &str| {
3625 comps.iter().find(|(kk, _)| *kk == k).map(|(_, v)| v.clone()).unwrap_or_default()
3626 };
3627 let mut protocol = get("protocol");
3628 let mut hostname = get("hostname");
3629 let mut port = get("port");
3630 let mut pathname = get("pathname");
3631 let mut search = get("search");
3632 let mut hash = get("hash");
3633 let mut username = get("username");
3634 let mut password = get("password");
3635 match key {
3636 "protocol" => protocol = if s.ends_with(':') { s.clone() } else { alloc::format!("{}:", s) },
3637 "hostname" => hostname = s.clone(),
3638 "port" => port = s.clone(),
3639 "host" => match s.split_once(':') {
3640 Some((h, p)) => {
3641 hostname = String::from(h);
3642 port = String::from(p);
3643 }
3644 None => {
3645 hostname = s.clone();
3646 port = String::new();
3647 }
3648 },
3649 "pathname" => pathname = if s.starts_with('/') { s.clone() } else { alloc::format!("/{}", s) },
3650 "search" => {
3651 search = if s.is_empty() || s.starts_with('?') { s.clone() } else { alloc::format!("?{}", s) }
3652 }
3653 "hash" => {
3654 hash = if s.is_empty() || s.starts_with('#') { s.clone() } else { alloc::format!("#{}", s) }
3655 }
3656 "username" => username = s.clone(),
3657 "password" => password = s.clone(),
3658 _ => {}
3659 }
3660 let hostport =
3661 if port.is_empty() { hostname.clone() } else { alloc::format!("{}:{}", hostname, port) };
3662 let userinfo = if username.is_empty() && password.is_empty() {
3663 String::new()
3664 } else {
3665 alloc::format!("{}:{}@", username, password)
3666 };
3667 let new_href =
3668 alloc::format!("{}//{}{}{}{}{}", protocol, userinfo, hostport, pathname, search, hash);
3669 self.dom.borrow_mut().set_attr(idx, "href", &new_href);
3670 }
3671 "htmlFor" => self.dom.borrow_mut().set_attr(idx, "for", &s),
3672 "acceptCharset" => self.dom.borrow_mut().set_attr(idx, "accept-charset", &s),
3675 "httpEquiv" => self.dom.borrow_mut().set_attr(idx, "http-equiv", &s),
3678 "content" => self.dom.borrow_mut().set_attr(idx, "content", &s),
3679 "charset" => self.dom.borrow_mut().set_attr(idx, "charset", &s),
3680 "cite" => self.dom.borrow_mut().set_attr(idx, "cite", &s),
3683 "dateTime" => self.dom.borrow_mut().set_attr(idx, "datetime", &s),
3684 "action" => self.dom.borrow_mut().set_attr(idx, "action", &s),
3685 "method" => self.dom.borrow_mut().set_attr(idx, "method", &s),
3686 "enctype" | "encoding" => self.dom.borrow_mut().set_attr(idx, "enctype", &s),
3689 "formAction" => self.dom.borrow_mut().set_attr(idx, "formaction", &s),
3692 "formEnctype" => self.dom.borrow_mut().set_attr(idx, "formenctype", &s),
3693 "formMethod" => self.dom.borrow_mut().set_attr(idx, "formmethod", &s),
3694 "formTarget" => self.dom.borrow_mut().set_attr(idx, "formtarget", &s),
3695 "min" => self.dom.borrow_mut().set_attr(idx, "min", &s),
3696 "max" => self.dom.borrow_mut().set_attr(idx, "max", &s),
3697 "low" => self.dom.borrow_mut().set_attr(idx, "low", &s),
3700 "high" => self.dom.borrow_mut().set_attr(idx, "high", &s),
3701 "optimum" => self.dom.borrow_mut().set_attr(idx, "optimum", &s),
3702 "colSpan" => self.dom.borrow_mut().set_attr(idx, "colspan", &s),
3705 "rowSpan" => self.dom.borrow_mut().set_attr(idx, "rowspan", &s),
3706 "headers" => self.dom.borrow_mut().set_attr(idx, "headers", &s),
3709 "scope" => self.dom.borrow_mut().set_attr(idx, "scope", &s),
3710 "abbr" => self.dom.borrow_mut().set_attr(idx, "abbr", &s),
3711 "span" => self.dom.borrow_mut().set_attr(idx, "span", &s),
3714 "step" => self.dom.borrow_mut().set_attr(idx, "step", &s),
3715 "maxLength" => self.dom.borrow_mut().set_attr(idx, "maxlength", &s),
3716 "minLength" => self.dom.borrow_mut().set_attr(idx, "minlength", &s),
3719 "target" => self.dom.borrow_mut().set_attr(idx, "target", &s),
3720 "rel" => self.dom.borrow_mut().set_attr(idx, "rel", &s),
3721 "download" => self.dom.borrow_mut().set_attr(idx, "download", &s),
3722 "coords" => self.dom.borrow_mut().set_attr(idx, "coords", &s),
3725 "shape" => self.dom.borrow_mut().set_attr(idx, "shape", &s),
3726 "lang" => self.dom.borrow_mut().set_attr(idx, "lang", &s),
3727 "dir" => self.dom.borrow_mut().set_attr(idx, "dir", &s),
3728 "title" => self.dom.borrow_mut().set_attr(idx, "title", &s),
3731 "accessKey" => self.dom.borrow_mut().set_attr(idx, "accesskey", &s),
3732 "translate" => {
3735 let v = if val.truthy() { "yes" } else { "no" };
3736 self.dom.borrow_mut().set_attr(idx, "translate", v);
3737 }
3738 "draggable" => {
3741 let v = if val.truthy() { "true" } else { "false" };
3742 self.dom.borrow_mut().set_attr(idx, "draggable", v);
3743 }
3744 "slot" => self.dom.borrow_mut().set_attr(idx, "slot", &s),
3746 "contentEditable" => {
3751 if matches!(s.as_str(), "true" | "false" | "plaintext-only") {
3752 self.dom.borrow_mut().set_attr(idx, "contenteditable", &s);
3753 }
3754 }
3755
3756 "tabIndex" => {
3757 let n = val.to_number() as i64;
3758 self.dom.borrow_mut().set_attr(idx, "tabindex", &n.to_string());
3759 }
3760 "spellcheck" => {
3763 let b = val.truthy();
3764 self.dom.borrow_mut().set_attr(idx, "spellcheck", if b { "true" } else { "false" });
3765 }
3766 "inputMode" => self.dom.borrow_mut().set_attr(idx, "inputmode", &s),
3767 "size" => self.dom.borrow_mut().set_attr(idx, "size", &s),
3770 "rows" => self.dom.borrow_mut().set_attr(idx, "rows", &s),
3773 "cols" => self.dom.borrow_mut().set_attr(idx, "cols", &s),
3774 "autocomplete" => self.dom.borrow_mut().set_attr(idx, "autocomplete", &s),
3777 "autofocus" => {
3778 let mut dom = self.dom.borrow_mut();
3779 if val.truthy() {
3780 dom.set_attr(idx, "autofocus", "autofocus");
3781 } else {
3782 dom.remove_attr(idx, "autofocus");
3783 }
3784 }
3785 "enterKeyHint" => self.dom.borrow_mut().set_attr(idx, "enterkeyhint", &s),
3786 "autocapitalize" => self.dom.borrow_mut().set_attr(idx, "autocapitalize", &s),
3787 "popover" => {
3789 if matches!(val, Value::Null) {
3790 self.dom.borrow_mut().remove_attr(idx, "popover");
3791 } else {
3792 self.dom.borrow_mut().set_attr(idx, "popover", &s);
3793 }
3794 }
3795 "popoverTargetAction" => {
3796 self.dom.borrow_mut().set_attr(idx, "popovertargetaction", &s)
3797 }
3798 "popoverTargetElement" => match val {
3803 Value::Null => {
3804 self.dom.borrow_mut().remove_attr(idx, "popovertarget");
3805 }
3806 Value::Object(o) if matches!(&o.borrow().kind, ObjKind::DomElement(_)) => {
3807 if let ObjKind::DomElement(ti) = &o.borrow().kind {
3808 let target_id = self.dom.borrow().nodes.get(*ti).map(|n| n.id.clone());
3809 if let Some(tid) = target_id.filter(|s| !s.is_empty()) {
3810 self.dom.borrow_mut().set_attr(idx, "popovertarget", &tid);
3811 }
3812 }
3813 }
3814 _ => {}
3815 },
3816 "command" => self.dom.borrow_mut().set_attr(idx, "command", &s),
3820 "commandForElement" => match val {
3821 Value::Null => {
3822 self.dom.borrow_mut().remove_attr(idx, "commandfor");
3823 }
3824 Value::Object(o) if matches!(&o.borrow().kind, ObjKind::DomElement(_)) => {
3825 if let ObjKind::DomElement(ti) = &o.borrow().kind {
3826 let target_id = self.dom.borrow().nodes.get(*ti).map(|n| n.id.clone());
3827 if let Some(tid) = target_id.filter(|s| !s.is_empty()) {
3828 self.dom.borrow_mut().set_attr(idx, "commandfor", &tid);
3829 }
3830 }
3831 }
3832 _ => {}
3833 },
3834 "tHead" | "tFoot" | "caption"
3843 if self.dom.borrow().nodes.get(idx).map(|n| n.tag == "table").unwrap_or(false) =>
3844 {
3845 let want = match key {
3846 "tHead" => "thead",
3847 "tFoot" => "tfoot",
3848 _ => "caption",
3849 };
3850 let existing = {
3851 let dom = self.dom.borrow();
3852 dom.nodes[idx].children.iter().find(|&&c| dom.nodes[c].tag == want).copied()
3853 };
3854 if let Some(old) = existing {
3855 self.dom.borrow_mut().remove_node(old);
3856 }
3857 if let Value::Object(o) = &val {
3858 if let ObjKind::DomElement(new_idx) = &o.borrow().kind {
3859 let new_idx = *new_idx;
3860 let ref_child = if want == "tfoot" {
3861 None
3862 } else {
3863 self.dom.borrow().nodes[idx].children.first().copied()
3864 };
3865 self.dom.borrow_mut().insert_before(idx, new_idx, ref_child);
3866 }
3867 }
3868 }
3869 "scrollTop" => {
3870 let n = val.to_number() as i32;
3872 let mut dom = self.dom.borrow_mut();
3873 dom.scroll_tops.insert(idx, n.max(0));
3874 dom.dirty = true;
3875 }
3876 key if aria_attr_name(key).is_some() => {
3879 let attr = aria_attr_name(key).unwrap_or_default();
3880 let mut dom = self.dom.borrow_mut();
3881 if matches!(val, Value::Null | Value::Undefined) {
3882 dom.remove_attr(idx, attr);
3883 } else {
3884 dom.set_attr(idx, attr, &s);
3885 }
3886 }
3887 "width" | "height" if self.dom.borrow().nodes.get(idx)
3902 .map(|n| n.tag == "canvas").unwrap_or(false) =>
3903 {
3904 self.dom.borrow_mut().set_attr(idx, key, &s);
3905 let id = self
3906 .dom
3907 .borrow()
3908 .get_attr(idx, "_c2d_id")
3909 .and_then(|v| v.parse::<u32>().ok());
3910 if let Some(id) = id {
3911 let get = |k: &str, d: u32| {
3912 self.dom
3913 .borrow()
3914 .get_attr(idx, k)
3915 .and_then(|v| v.trim().parse::<u32>().ok())
3916 .unwrap_or(d)
3917 };
3918 let (w, h) = (get("width", 300), get("height", 150));
3919 crate::os_lib::canvas2d::resize_context(id, w, h);
3920 }
3921 }
3922 _ if key.starts_with("on") && key.len() > 2 => {
3923 self.dom.borrow_mut().set_on_handler(idx, key, val.clone());
3924 if let Value::Str(s_val) = &val {
3925 self.dom.borrow_mut().set_attr(idx, key, s_val);
3926 }
3927 }
3928 _ => {}
3929 },
3930 DomDisp::Host(tag) => {
3931 if let Some(rest) = tag.strip_prefix("style:") {
3932 let idx: usize = rest.parse().unwrap_or(usize::MAX);
3933 if key == "cssText" {
3934 self.dom.borrow_mut().set_css_text(idx, &s);
3935 } else {
3936 let key = if key == "cssFloat" { "float" } else { key };
3939 self.dom.borrow_mut().set_style(idx, key, &s);
3940 }
3941 } else if let Some(rest) = tag.strip_prefix("dataset:") {
3942 let idx: usize = rest.parse().unwrap_or(usize::MAX);
3943 let attr = camel_to_data_attr(key);
3944 self.dom.borrow_mut().set_attr(idx, &attr, &s);
3945 } else if let Some(rest) = tag.strip_prefix("classList:") {
3946 let idx: usize = rest.parse().unwrap_or(usize::MAX);
3947 if key == "value" {
3948 self.dom.borrow_mut().set_attr(idx, "class", &s);
3949 }
3950 }
3951 }
3952 }
3953 }
3954}
3955
3956pub(crate) fn template_fragment_clone_node(
3957 it: &mut Interp,
3958 this: Value,
3959 args: &[Value],
3960) -> Result<Value, Value> {
3961 let deep = args.get(0).map(|v| v.truthy()).unwrap_or(false);
3962 let new_frag = Obj::plain();
3963 new_frag.borrow_mut().props.insert("nodeType".into(), Value::Number(11.0));
3964 new_frag.borrow_mut().props.insert("nodeName".into(), Value::str("#document-fragment"));
3965
3966 if let Value::Object(o) = &this {
3967 let child_vals = if let Some(Value::Object(arr_obj)) = o.borrow().props.get("childNodes") {
3968 if let ObjKind::Array(elems) = &arr_obj.borrow().kind {
3969 elems.clone()
3970 } else {
3971 alloc::vec::Vec::new()
3972 }
3973 } else {
3974 alloc::vec::Vec::new()
3975 };
3976
3977 let mut cloned_children = alloc::vec::Vec::new();
3978 for child_val in child_vals {
3979 if let Value::Object(co) = &child_val {
3980 if let ObjKind::DomElement(c_idx) = co.borrow().kind {
3981 let cloned_idx = it.dom.borrow_mut().clone_node(c_idx, deep);
3982 cloned_children.push(Value::Object(Obj::dom(cloned_idx)));
3983 continue;
3984 }
3985 }
3986 cloned_children.push(child_val.clone());
3987 }
3988
3989 let cloned_arr = Obj::array(cloned_children);
3990 new_frag.borrow_mut().props.insert("childNodes".into(), Value::Object(cloned_arr.clone()));
3991 new_frag.borrow_mut().props.insert("children".into(), Value::Object(cloned_arr));
3992 }
3993 let clone_fn = Obj::native("cloneNode", template_fragment_clone_node);
3994 new_frag.borrow_mut().props.insert("cloneNode".into(), Value::Object(clone_fn));
3995
3996 Ok(Value::Object(new_frag))
3997}