Skip to main content

atmos/os_lib/js/builtins/
dom_document.rs

1// 分割: builtins.rs より機械的に移動(2026-07-16 リファクタ フェーズ2)。
2// ロジック不変。可視性のみ pub(crate) へ昇格し、親が pub(crate) use で再エクスポート。
3use super::*;
4
5// ===== DOM Range 実装 =====
6fn range_set_start(_it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
7    let node = arg(a, 0);
8    let offset = arg(a, 1).to_number().max(0.0) as usize;
9    if let Value::Object(ref o) = t {
10        let mut b = o.borrow_mut();
11        b.props.insert("startContainer".into(), node.clone());
12        b.props.insert("startOffset".into(), Value::Number(offset as f64));
13
14        let end_node = b.props.get("endContainer").cloned().unwrap_or(Value::Null);
15        let end_offset = b.props.get("endOffset").map(|v| v.to_number().max(0.0) as usize).unwrap_or(0);
16
17        let must_collapse = matches!(end_node, Value::Null) || (node.strict_eq(&end_node) && offset > end_offset);
18        if must_collapse {
19            b.props.insert("endContainer".into(), node.clone());
20            b.props.insert("endOffset".into(), Value::Number(offset as f64));
21            b.props.insert("collapsed".into(), Value::Bool(true));
22        } else {
23            let collapsed = node.strict_eq(&end_node) && offset == end_offset;
24            b.props.insert("collapsed".into(), Value::Bool(collapsed));
25        }
26        b.props.insert("commonAncestorContainer".into(), node);
27    }
28    Ok(Value::Undefined)
29}
30
31fn range_set_end(_it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
32    let node = arg(a, 0);
33    let offset = arg(a, 1).to_number().max(0.0) as usize;
34    if let Value::Object(ref o) = t {
35        let mut b = o.borrow_mut();
36        b.props.insert("endContainer".into(), node.clone());
37        b.props.insert("endOffset".into(), Value::Number(offset as f64));
38
39        let start_node = b.props.get("startContainer").cloned().unwrap_or(Value::Null);
40        let start_offset = b.props.get("startOffset").map(|v| v.to_number().max(0.0) as usize).unwrap_or(0);
41
42        let must_collapse = matches!(start_node, Value::Null) || (node.strict_eq(&start_node) && offset < start_offset);
43        if must_collapse {
44            b.props.insert("startContainer".into(), node.clone());
45            b.props.insert("startOffset".into(), Value::Number(offset as f64));
46            b.props.insert("collapsed".into(), Value::Bool(true));
47        } else {
48            let collapsed = node.strict_eq(&start_node) && offset == start_offset;
49            b.props.insert("collapsed".into(), Value::Bool(collapsed));
50        }
51        b.props.insert("commonAncestorContainer".into(), node);
52    }
53    Ok(Value::Undefined)
54}
55
56fn range_collapse(_it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
57    // W3C DOM Standard: collapse(toStart = false)
58    let to_start = a.first().map(|v| v.truthy()).unwrap_or(false);
59    if let Value::Object(ref o) = t {
60        let mut b = o.borrow_mut();
61        if to_start {
62            let sc = b.props.get("startContainer").cloned().unwrap_or(Value::Null);
63            let so = b.props.get("startOffset").cloned().unwrap_or(Value::Number(0.0));
64            b.props.insert("endContainer".into(), sc);
65            b.props.insert("endOffset".into(), so);
66        } else {
67            let ec = b.props.get("endContainer").cloned().unwrap_or(Value::Null);
68            let eo = b.props.get("endOffset").cloned().unwrap_or(Value::Number(0.0));
69            b.props.insert("startContainer".into(), ec);
70            b.props.insert("startOffset".into(), eo);
71        }
72        b.props.insert("collapsed".into(), Value::Bool(true));
73    }
74    Ok(Value::Undefined)
75}
76
77fn range_select_node_contents(_it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
78    let node = arg(a, 0);
79    if let Value::Object(ref o) = t {
80        let mut b = o.borrow_mut();
81        b.props.insert("startContainer".into(), node.clone());
82        b.props.insert("startOffset".into(), Value::Number(0.0));
83        b.props.insert("endContainer".into(), node.clone());
84        let len = if let Value::Object(ref no) = node {
85            let nb = no.borrow();
86            if let Some(Value::Object(ref ch)) = nb.props.get("childNodes") {
87                ch.borrow().props.get("length").map(|l| l.to_number() as usize).unwrap_or(0)
88            } else if let Some(ref s) = nb.props.get("textContent") {
89                s.to_js_string().len()
90            } else {
91                0
92            }
93        } else {
94            0
95        };
96        b.props.insert("endOffset".into(), Value::Number(len as f64));
97        b.props.insert("collapsed".into(), Value::Bool(len == 0));
98        b.props.insert("commonAncestorContainer".into(), node);
99    }
100    Ok(Value::Undefined)
101}
102
103fn range_clone_range(it: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
104    let new_r = document_create_range(it, Value::Undefined, &[])?;
105    if let (Value::Object(ref src), Value::Object(ref dst)) = (&t, &new_r) {
106        let sb = src.borrow();
107        let mut db = dst.borrow_mut();
108        for k in &[
109            "startContainer", "startOffset", "endContainer", "endOffset", "collapsed", "commonAncestorContainer"
110        ] {
111            if let Some(v) = sb.props.get(*k) {
112                db.props.insert((*k).into(), v.clone());
113            }
114        }
115    }
116    Ok(new_r)
117}
118
119fn range_compare_boundary_points(_it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
120    let how = arg(a, 0).to_number() as u16;
121    let source = arg(a, 1);
122    if let (Value::Object(ref this_obj), Value::Object(ref src_obj)) = (&t, &source) {
123        let tb = this_obj.borrow();
124        let sb = src_obj.borrow();
125        let (this_node, this_offset) = match how {
126            crate::os_lib::dom_range::START_TO_START | crate::os_lib::dom_range::END_TO_START => {
127                (tb.props.get("startContainer"), tb.props.get("startOffset"))
128            }
129            _ => (tb.props.get("endContainer"), tb.props.get("endOffset")),
130        };
131        let (src_node, src_offset) = match how {
132            crate::os_lib::dom_range::START_TO_START | crate::os_lib::dom_range::START_TO_END => {
133                (sb.props.get("startContainer"), sb.props.get("startOffset"))
134            }
135            _ => (sb.props.get("endContainer"), sb.props.get("endOffset")),
136        };
137        let to = this_offset.map(|v| v.to_number() as usize).unwrap_or(0);
138        let so = src_offset.map(|v| v.to_number() as usize).unwrap_or(0);
139        if let (Some(tn), Some(sn)) = (this_node, src_node) {
140            if tn.strict_eq(sn) {
141                let res = match to.cmp(&so) {
142                    core::cmp::Ordering::Less => -1.0,
143                    core::cmp::Ordering::Equal => 0.0,
144                    core::cmp::Ordering::Greater => 1.0,
145                };
146                return Ok(Value::Number(res));
147            }
148        }
149    }
150    Ok(Value::Number(0.0))
151}
152
153fn range_is_point_in_range(_it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
154    let node = arg(a, 0);
155    let offset = arg(a, 1).to_number().max(0.0) as usize;
156    if let Value::Object(ref o) = t {
157        let b = o.borrow();
158        let start_node = b.props.get("startContainer");
159        let start_offset = b.props.get("startOffset").map(|v| v.to_number() as usize).unwrap_or(0);
160        let end_node = b.props.get("endContainer");
161        let end_offset = b.props.get("endOffset").map(|v| v.to_number() as usize).unwrap_or(0);
162
163        if matches!(node, Value::Null | Value::Undefined) {
164            return Ok(Value::Bool(false));
165        }
166
167        if let (Some(sn), Some(en)) = (start_node, end_node) {
168            if matches!(sn, Value::Null | Value::Undefined) || matches!(en, Value::Null | Value::Undefined) {
169                return Ok(Value::Bool(false));
170            }
171            if sn.strict_eq(&node) && en.strict_eq(&node) {
172                return Ok(Value::Bool(offset >= start_offset && offset <= end_offset));
173            }
174        }
175    }
176    Ok(Value::Bool(false))
177}
178
179fn range_create_contextual_fragment(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
180    let html = arg(a, 0).to_js_string();
181    let start_node_idx = if let Value::Object(ref o) = this {
182        o.borrow()
183            .props
184            .get("startContainer")
185            .and_then(|v| crate::os_lib::js::builtins::dom::this_dom_idx(v))
186    } else {
187        None
188    };
189    let frag_idx = it.dom.borrow_mut().create_contextual_fragment(start_node_idx, &html);
190    Ok(Value::Object(Obj::dom(frag_idx)))
191}
192
193// ===== DOM document の追加メソッド =====
194pub(crate) fn document_create_range(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
195    let r = Obj::plain();
196    {
197        let mut b = r.borrow_mut();
198        b.props.insert(
199            "START_TO_START".into(),
200            Value::Number(crate::os_lib::dom_range::START_TO_START as f64),
201        );
202        b.props.insert(
203            "START_TO_END".into(),
204            Value::Number(crate::os_lib::dom_range::START_TO_END as f64),
205        );
206        b.props.insert(
207            "END_TO_END".into(),
208            Value::Number(crate::os_lib::dom_range::END_TO_END as f64),
209        );
210        b.props.insert(
211            "END_TO_START".into(),
212            Value::Number(crate::os_lib::dom_range::END_TO_START as f64),
213        );
214
215        b.props.insert(
216            "selectNodeContents".into(),
217            nv("selectNodeContents", range_select_node_contents),
218        );
219        b.props.insert("collapse".into(), nv("collapse", range_collapse));
220        b.props.insert("setStart".into(), nv("setStart", range_set_start));
221        b.props.insert("setEnd".into(), nv("setEnd", range_set_end));
222        b.props.insert("cloneRange".into(), nv("cloneRange", range_clone_range));
223        b.props.insert(
224            "compareBoundaryPoints".into(),
225            nv("compareBoundaryPoints", range_compare_boundary_points),
226        );
227        b.props.insert(
228            "isPointInRange".into(),
229            nv("isPointInRange", range_is_point_in_range),
230        );
231        b.props.insert(
232            "createContextualFragment".into(),
233            nv("createContextualFragment", range_create_contextual_fragment),
234        );
235
236        b.props.insert(
237            "getBoundingClientRect".into(),
238            nv("getBoundingClientRect", |_, _, _| {
239                let rect = Obj::plain();
240                for k in &[
241                    "top", "left", "bottom", "right", "width", "height", "x", "y",
242                ] {
243                    rect.borrow_mut()
244                        .props
245                        .insert(k.to_string(), Value::Number(0.0));
246                }
247                Ok(Value::Object(rect))
248            }),
249        );
250        b.props.insert("startContainer".into(), Value::Null);
251        b.props.insert("endContainer".into(), Value::Null);
252        b.props.insert("startOffset".into(), Value::Number(0.0));
253        b.props.insert("endOffset".into(), Value::Number(0.0));
254        b.props.insert("collapsed".into(), Value::Bool(true));
255        b.props.insert("commonAncestorContainer".into(), Value::Null);
256
257        for m in [
258            "setStartBefore",
259            "setStartAfter",
260            "setEndBefore",
261            "setEndAfter",
262            "selectNode",
263            "deleteContents",
264            "insertNode",
265            "surroundContents",
266            "detach",
267        ] {
268            b.props.insert(m.into(), nv(m, dom_noop));
269        }
270        b.props.insert(
271            "extractContents".into(),
272            nv("extractContents", |it: &mut Interp, _t: Value, _a: &[Value]| {
273                document_create_range(it, Value::Undefined, &[])
274            }),
275        );
276        b.props.insert(
277            "cloneContents".into(),
278            nv("cloneContents", |it: &mut Interp, _t: Value, _a: &[Value]| {
279                document_create_range(it, Value::Undefined, &[])
280            }),
281        );
282        b.props.insert(
283            "toString".into(),
284            nv("toString", |_, _, _| Ok(Value::str(""))),
285        );
286        b.props
287            .insert("comparePoint".into(), nv("comparePoint", |_, _, _| Ok(Value::Number(0.0))));
288        b.props.insert(
289            "intersectsNode".into(),
290            nv("intersectsNode", |_, _, _| Ok(Value::Bool(false))),
291        );
292    }
293    Ok(Value::Object(r))
294}
295
296fn selection_add_range(_it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
297    let r = arg(a, 0);
298    if let Value::Object(ref sel_obj) = t {
299        let mut sb = sel_obj.borrow_mut();
300        sb.props.insert("_range".into(), r.clone());
301        sb.props.insert("rangeCount".into(), Value::Number(1.0));
302        if let Value::Object(ref ro) = r {
303            let rb = ro.borrow();
304            let sc = rb.props.get("startContainer").cloned().unwrap_or(Value::Null);
305            let so = rb.props.get("startOffset").cloned().unwrap_or(Value::Number(0.0));
306            let ec = rb.props.get("endContainer").cloned().unwrap_or(Value::Null);
307            let eo = rb.props.get("endOffset").cloned().unwrap_or(Value::Number(0.0));
308            let collapsed = rb.props.get("collapsed").map(|v| v.truthy()).unwrap_or(true);
309
310            sb.props.insert("anchorNode".into(), sc);
311            sb.props.insert("anchorOffset".into(), so);
312            sb.props.insert("focusNode".into(), ec);
313            sb.props.insert("focusOffset".into(), eo);
314            sb.props.insert("isCollapsed".into(), Value::Bool(collapsed));
315            sb.props.insert("type".into(), Value::str(if collapsed { "Caret" } else { "Range" }));
316        }
317    }
318    Ok(Value::Undefined)
319}
320
321fn selection_remove_all_ranges(_it: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
322    if let Value::Object(ref sel_obj) = t {
323        let mut sb = sel_obj.borrow_mut();
324        sb.props.insert("_range".into(), Value::Null);
325        sb.props.insert("rangeCount".into(), Value::Number(0.0));
326        sb.props.insert("anchorNode".into(), Value::Null);
327        sb.props.insert("anchorOffset".into(), Value::Number(0.0));
328        sb.props.insert("focusNode".into(), Value::Null);
329        sb.props.insert("focusOffset".into(), Value::Number(0.0));
330        sb.props.insert("isCollapsed".into(), Value::Bool(true));
331        sb.props.insert("type".into(), Value::str("None"));
332    }
333    Ok(Value::Undefined)
334}
335
336fn selection_remove_range(_it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
337    let r = arg(a, 0);
338    if let Value::Object(ref sel_obj) = t {
339        let mut sb = sel_obj.borrow_mut();
340        if let Some(cur) = sb.props.get("_range") {
341            if cur.strict_eq(&r) {
342                sb.props.insert("_range".into(), Value::Null);
343                sb.props.insert("rangeCount".into(), Value::Number(0.0));
344                sb.props.insert("anchorNode".into(), Value::Null);
345                sb.props.insert("anchorOffset".into(), Value::Number(0.0));
346                sb.props.insert("focusNode".into(), Value::Null);
347                sb.props.insert("focusOffset".into(), Value::Number(0.0));
348                sb.props.insert("isCollapsed".into(), Value::Bool(true));
349                sb.props.insert("type".into(), Value::str("None"));
350            }
351        }
352    }
353    Ok(Value::Undefined)
354}
355
356fn selection_get_range_at(it: &mut Interp, t: Value, a: &[Value]) -> Result<Value, Value> {
357    let idx = arg(a, 0).to_number() as i64;
358    if let Value::Object(ref sel_obj) = t {
359        let sb = sel_obj.borrow();
360        let rc = sb.props.get("rangeCount").map(|v| v.to_number() as i64).unwrap_or(0);
361        if idx == 0 && rc > 0 {
362            if let Some(r) = sb.props.get("_range") {
363                if !matches!(r, Value::Null | Value::Undefined) {
364                    return Ok(r.clone());
365                }
366            }
367        }
368        return Err(it.error(alloc::format!(
369            "Failed to execute 'getRangeAt' on 'Selection': The index provided ({}) is greater than the maximum bound ({}).",
370            idx,
371            if rc > 0 { rc - 1 } else { 0 }
372        )));
373    }
374    Err(it.error("Illegal invocation"))
375}
376
377pub(crate) fn document_get_selection(it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
378    if let Some(ref sel) = it.dom.borrow().selection {
379        return Ok(sel.clone());
380    }
381    let sel = Obj::plain();
382    {
383        let mut b = sel.borrow_mut();
384        b.props
385            .insert("removeAllRanges".into(), nv("removeAllRanges", selection_remove_all_ranges));
386        b.props.insert("addRange".into(), nv("addRange", selection_add_range));
387        b.props.insert("removeRange".into(), nv("removeRange", selection_remove_range));
388        b.props.insert("getRangeAt".into(), nv("getRangeAt", selection_get_range_at));
389        b.props.insert("_range".into(), Value::Null);
390        b.props.insert(
391            "toString".into(),
392            nv("toString", |_, _, _| Ok(Value::str(""))),
393        );
394        b.props.insert("rangeCount".into(), Value::Number(0.0));
395        b.props.insert("isCollapsed".into(), Value::Bool(true));
396        b.props.insert("collapse".into(), nv("collapse", dom_noop));
397        b.props.insert("collapseToStart".into(), nv("collapseToStart", dom_noop));
398        b.props.insert("collapseToEnd".into(), nv("collapseToEnd", dom_noop));
399        b.props.insert("extend".into(), nv("extend", dom_noop));
400        b.props.insert("empty".into(), nv("empty", dom_noop));
401        b.props
402            .insert("selectAllChildren".into(), nv("selectAllChildren", dom_noop));
403        b.props
404            .insert("setBaseAndExtent".into(), nv("setBaseAndExtent", dom_noop));
405        b.props
406            .insert("deleteFromDocument".into(), nv("deleteFromDocument", dom_noop));
407        b.props.insert(
408            "containsNode".into(),
409            nv("containsNode", |_, _, _| Ok(Value::Bool(false))),
410        );
411        b.props.insert("anchorNode".into(), Value::Null);
412        b.props.insert("anchorOffset".into(), Value::Number(0.0));
413        b.props.insert("focusNode".into(), Value::Null);
414        b.props.insert("focusOffset".into(), Value::Number(0.0));
415        b.props.insert("type".into(), Value::str("None"));
416    }
417    let res = Value::Object(sel);
418    it.dom.borrow_mut().selection = Some(res.clone());
419    Ok(res)
420}
421
422pub(crate) fn document_get_elements_by_class_name(
423    it: &mut Interp,
424    _t: Value,
425    a: &[Value],
426) -> Result<Value, Value> {
427    let raw = arg(a, 0).to_js_string();
428    let sel_classes: Vec<String> = raw
429        .split_whitespace()
430        .map(|c| alloc::format!(".{}", c))
431        .collect();
432    let sel = if sel_classes.is_empty() {
433        alloc::format!(".__invalid_class__")
434    } else {
435        sel_classes.concat()
436    };
437    let items: Vec<Value> = it
438        .dom
439        .borrow()
440        .query_all(&sel)
441        .into_iter()
442        .map(|i| Value::Object(Obj::dom(i)))
443        .collect();
444    Ok(Value::Object(Obj::array(items)))
445}
446pub(crate) fn document_get_elements_by_tag_name(
447    it: &mut Interp,
448    _t: Value,
449    a: &[Value],
450) -> Result<Value, Value> {
451    let tag = arg(a, 0).to_js_string();
452    let items: Vec<Value> = it
453        .dom
454        .borrow()
455        .query_all(&tag)
456        .into_iter()
457        .map(|i| Value::Object(Obj::dom(i)))
458        .collect();
459    Ok(Value::Object(Obj::array(items)))
460}
461/// `document.getElementsByTagNameNS(namespace, localName)`(丸ごと未対応だった)。
462/// 他の `*AttributeNS`/`createElementNS` と同じく XML 名前空間をモデル化していないため、
463/// `namespace` 引数は無視し `localName` で要素を抽出する。
464pub(crate) fn document_get_elements_by_tag_name_ns(
465    it: &mut Interp,
466    _t: Value,
467    a: &[Value],
468) -> Result<Value, Value> {
469    let tag = if a.len() >= 2 {
470        arg(a, 1).to_js_string()
471    } else {
472        arg(a, 0).to_js_string()
473    };
474    let items: Vec<Value> = it
475        .dom
476        .borrow()
477        .query_all(&tag)
478        .into_iter()
479        .map(|i| Value::Object(Obj::dom(i)))
480        .collect();
481    Ok(Value::Object(Obj::array(items)))
482}
483/// `document.getElementsByName(name)`(丸ごと未対応だった。ラジオボタン
484/// グループ等、同じ `name` 属性を持つ要素群をまとめて取得する定番パターン)。
485/// 既存の属性セレクタ実装へ委譲する(`getElementsByClassName` と同型)。
486pub(crate) fn document_get_elements_by_name(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
487    let name = arg(a, 0).to_js_string();
488    let sel = alloc::format!("[name=\"{name}\"]");
489    let items: Vec<Value> = it
490        .dom
491        .borrow()
492        .query_all(&sel)
493        .into_iter()
494        .map(|i| Value::Object(Obj::dom(i)))
495        .collect();
496    Ok(Value::Object(Obj::array(items)))
497}
498
499/// `document.execCommand(commandName, showUI, value)`(HTML Standard §7.7)。
500pub(crate) fn document_exec_command(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
501    let cmd = arg(a, 0).to_js_string().to_ascii_lowercase();
502    match cmd.as_str() {
503        "copy" | "cut" | "paste" | "selectall" | "bold" | "italic" | "underline" | "delete" | "inserttext" => Ok(Value::Bool(true)),
504        _ => Ok(Value::Bool(false)),
505    }
506}
507
508/// `document.adoptNode(externalNode)` (WHATWG DOM Standard §4.5)。
509pub(crate) fn doc_adopt_node(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
510    let node = arg(a, 0);
511    if matches!(node, Value::Object(_)) {
512        if let Ok(remove_fn) = it.get_property(&node, "remove") {
513            it.call_listener(&remove_fn, node.clone(), &[], "adoptNode: node.remove()");
514        }
515        Ok(node)
516    } else {
517        Ok(Value::Null)
518    }
519}
520
521/// `document.importNode(externalNode, deep)` (WHATWG DOM Standard §4.5)。
522pub(crate) fn doc_import_node(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
523    let node = arg(a, 0);
524    let deep = arg(a, 1).truthy();
525    if matches!(node, Value::Object(_)) {
526        if let Ok(clone_fn) = it.get_property(&node, "cloneNode") {
527            if let Ok(cloned) = it.call_value(&clone_fn, node.clone(), &[Value::Bool(deep)]) {
528                return Ok(cloned);
529            }
530        }
531        Ok(node)
532    } else {
533        Ok(Value::Null)
534    }
535}