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 document の追加メソッド =====
6pub(crate) fn document_create_range(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
7    let r = Obj::plain();
8    {
9        let mut b = r.borrow_mut();
10        b.props.insert(
11            "selectNodeContents".into(),
12            nv("selectNodeContents", dom_noop),
13        );
14        b.props.insert("collapse".into(), nv("collapse", dom_noop));
15        b.props.insert(
16            "getBoundingClientRect".into(),
17            nv("getBoundingClientRect", |_, _, _| {
18                let rect = Obj::plain();
19                for k in &[
20                    "top", "left", "bottom", "right", "width", "height", "x", "y",
21                ] {
22                    rect.borrow_mut()
23                        .props
24                        .insert(k.to_string(), Value::Number(0.0));
25                }
26                Ok(Value::Object(rect))
27            }),
28        );
29        b.props.insert("startContainer".into(), Value::Null);
30        b.props.insert("endContainer".into(), Value::Null);
31        // `Range` の残りのメソッド群/プロパティ(丸ごと未対応だった)。この
32        // 処理系には実際のテキストレンジ編集機構が無いため、変更系メソッド
33        // は無害な no-op、`toString()` は常に空文字列、`cloneRange()` は
34        // 新しい空の `Range` を返す誠実な簡略実装(`Selection` と同方針)。
35        for m in [
36            "setStart",
37            "setEnd",
38            "setStartBefore",
39            "setStartAfter",
40            "setEndBefore",
41            "setEndAfter",
42            "selectNode",
43            "deleteContents",
44            "insertNode",
45            "surroundContents",
46            "detach",
47        ] {
48            b.props.insert(m.into(), nv(m, dom_noop));
49        }
50        b.props.insert(
51            "extractContents".into(),
52            nv("extractContents", |it: &mut Interp, _t: Value, _a: &[Value]| {
53                document_create_range(it, Value::Undefined, &[])
54            }),
55        );
56        b.props.insert(
57            "cloneContents".into(),
58            nv("cloneContents", |it: &mut Interp, _t: Value, _a: &[Value]| {
59                document_create_range(it, Value::Undefined, &[])
60            }),
61        );
62        b.props.insert(
63            "cloneRange".into(),
64            nv("cloneRange", |it: &mut Interp, _t: Value, _a: &[Value]| {
65                document_create_range(it, Value::Undefined, &[])
66            }),
67        );
68        b.props.insert(
69            "toString".into(),
70            nv("toString", |_, _, _| Ok(Value::str(""))),
71        );
72        b.props.insert(
73            "compareBoundaryPoints".into(),
74            nv("compareBoundaryPoints", |_, _, _| Ok(Value::Number(0.0))),
75        );
76        b.props
77            .insert("comparePoint".into(), nv("comparePoint", |_, _, _| Ok(Value::Number(0.0))));
78        b.props.insert(
79            "intersectsNode".into(),
80            nv("intersectsNode", |_, _, _| Ok(Value::Bool(false))),
81        );
82        b.props.insert(
83            "isPointInRange".into(),
84            nv("isPointInRange", |_, _, _| Ok(Value::Bool(false))),
85        );
86        b.props.insert("startOffset".into(), Value::Number(0.0));
87        b.props.insert("endOffset".into(), Value::Number(0.0));
88        b.props.insert("collapsed".into(), Value::Bool(true));
89        b.props.insert("commonAncestorContainer".into(), Value::Null);
90    }
91    Ok(Value::Object(r))
92}
93
94pub(crate) fn document_get_selection(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
95    let sel = Obj::plain();
96    {
97        let mut b = sel.borrow_mut();
98        b.props
99            .insert("removeAllRanges".into(), nv("removeAllRanges", dom_noop));
100        b.props.insert("addRange".into(), nv("addRange", dom_noop));
101        // `removeRange(range)`(`addRange`/`removeAllRanges`の隣で漏れて
102        // いた兄弟ギャップ。丸ごと未対応だった。2026-07-17 発見・実装)。
103        // 同じ「テキスト選択機構自体が無いno-op」の簡略方針。
104        b.props.insert("removeRange".into(), nv("removeRange", dom_noop));
105        b.props.insert(
106            "toString".into(),
107            nv("toString", |_, _, _| Ok(Value::str(""))),
108        );
109        b.props.insert("rangeCount".into(), Value::Number(0.0));
110        b.props.insert("isCollapsed".into(), Value::Bool(true));
111        // `Selection` の残りのメソッド群/プロパティ(丸ごと未対応だった)。
112        // この処理系にはテキスト選択機構自体が無いため、`removeAllRanges`等
113        // と同じ「常に何も選択していない」誠実な簡略実装(`getRangeAt`は
114        // 常に空の `rangeCount` のため範囲外アクセスとして例外を投げる)。
115        b.props.insert("collapse".into(), nv("collapse", dom_noop));
116        b.props.insert("collapseToStart".into(), nv("collapseToStart", dom_noop));
117        b.props.insert("collapseToEnd".into(), nv("collapseToEnd", dom_noop));
118        b.props.insert("extend".into(), nv("extend", dom_noop));
119        b.props.insert("empty".into(), nv("empty", dom_noop));
120        b.props
121            .insert("selectAllChildren".into(), nv("selectAllChildren", dom_noop));
122        b.props
123            .insert("setBaseAndExtent".into(), nv("setBaseAndExtent", dom_noop));
124        b.props
125            .insert("deleteFromDocument".into(), nv("deleteFromDocument", dom_noop));
126        b.props.insert(
127            "containsNode".into(),
128            nv("containsNode", |_, _, _| Ok(Value::Bool(false))),
129        );
130        b.props.insert(
131            "getRangeAt".into(),
132            nv("getRangeAt", |it: &mut Interp, _t: Value, a: &[Value]| {
133                Err(it.error(alloc::format!(
134                    "Failed to execute 'getRangeAt' on 'Selection': The index provided ({}) is greater than the maximum bound (0).",
135                    arg(a, 0).to_number() as i64
136                )))
137            }),
138        );
139        b.props.insert("anchorNode".into(), Value::Null);
140        b.props.insert("anchorOffset".into(), Value::Number(0.0));
141        b.props.insert("focusNode".into(), Value::Null);
142        b.props.insert("focusOffset".into(), Value::Number(0.0));
143        b.props.insert("type".into(), Value::str("None"));
144    }
145    Ok(Value::Object(sel))
146}
147
148pub(crate) fn document_get_elements_by_class_name(
149    it: &mut Interp,
150    _t: Value,
151    a: &[Value],
152) -> Result<Value, Value> {
153    let raw = arg(a, 0).to_js_string();
154    let sel_classes: Vec<String> = raw
155        .split_whitespace()
156        .map(|c| alloc::format!(".{}", c))
157        .collect();
158    let sel = if sel_classes.is_empty() {
159        alloc::format!(".__invalid_class__")
160    } else {
161        sel_classes.concat()
162    };
163    let items: Vec<Value> = it
164        .dom
165        .borrow()
166        .query_all(&sel)
167        .into_iter()
168        .map(|i| Value::Object(Obj::dom(i)))
169        .collect();
170    Ok(Value::Object(Obj::array(items)))
171}
172pub(crate) fn document_get_elements_by_tag_name(
173    it: &mut Interp,
174    _t: Value,
175    a: &[Value],
176) -> Result<Value, Value> {
177    let tag = arg(a, 0).to_js_string();
178    let items: Vec<Value> = it
179        .dom
180        .borrow()
181        .query_all(&tag)
182        .into_iter()
183        .map(|i| Value::Object(Obj::dom(i)))
184        .collect();
185    Ok(Value::Object(Obj::array(items)))
186}
187/// `document.getElementsByTagNameNS(namespace, localName)`(丸ごと未対応だった)。
188/// 他の `*AttributeNS`/`createElementNS` と同じく XML 名前空間をモデル化していないため、
189/// `namespace` 引数は無視し `localName` で要素を抽出する。
190pub(crate) fn document_get_elements_by_tag_name_ns(
191    it: &mut Interp,
192    _t: Value,
193    a: &[Value],
194) -> Result<Value, Value> {
195    let tag = if a.len() >= 2 {
196        arg(a, 1).to_js_string()
197    } else {
198        arg(a, 0).to_js_string()
199    };
200    let items: Vec<Value> = it
201        .dom
202        .borrow()
203        .query_all(&tag)
204        .into_iter()
205        .map(|i| Value::Object(Obj::dom(i)))
206        .collect();
207    Ok(Value::Object(Obj::array(items)))
208}
209/// `document.getElementsByName(name)`(丸ごと未対応だった。ラジオボタン
210/// グループ等、同じ `name` 属性を持つ要素群をまとめて取得する定番パターン)。
211/// 既存の属性セレクタ実装へ委譲する(`getElementsByClassName` と同型)。
212pub(crate) fn document_get_elements_by_name(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
213    let name = arg(a, 0).to_js_string();
214    let sel = alloc::format!("[name=\"{name}\"]");
215    let items: Vec<Value> = it
216        .dom
217        .borrow()
218        .query_all(&sel)
219        .into_iter()
220        .map(|i| Value::Object(Obj::dom(i)))
221        .collect();
222    Ok(Value::Object(Obj::array(items)))
223}
224
225/// `document.execCommand(commandName, showUI, value)`(HTML Standard §7.7)。
226pub(crate) fn document_exec_command(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
227    let cmd = arg(a, 0).to_js_string().to_ascii_lowercase();
228    match cmd.as_str() {
229        "copy" | "cut" | "paste" | "selectall" | "bold" | "italic" | "underline" | "delete" | "inserttext" => Ok(Value::Bool(true)),
230        _ => Ok(Value::Bool(false)),
231    }
232}
233
234/// `document.adoptNode(externalNode)` (WHATWG DOM Standard §4.5)。
235pub(crate) fn doc_adopt_node(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
236    let node = arg(a, 0);
237    if matches!(node, Value::Object(_)) {
238        if let Ok(remove_fn) = it.get_property(&node, "remove") {
239            it.call_listener(&remove_fn, node.clone(), &[], "adoptNode: node.remove()");
240        }
241        Ok(node)
242    } else {
243        Ok(Value::Null)
244    }
245}
246
247/// `document.importNode(externalNode, deep)` (WHATWG DOM Standard §4.5)。
248pub(crate) fn doc_import_node(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
249    let node = arg(a, 0);
250    let deep = arg(a, 1).truthy();
251    if matches!(node, Value::Object(_)) {
252        if let Ok(clone_fn) = it.get_property(&node, "cloneNode") {
253            if let Ok(cloned) = it.call_value(&clone_fn, node.clone(), &[Value::Bool(deep)]) {
254                return Ok(cloned);
255            }
256        }
257        Ok(node)
258    } else {
259        Ok(Value::Null)
260    }
261}