atmos/os_lib/js/interp/
custom_elements.rs1use super::*;
16
17impl Interp {
18 fn ce_registry(&self) -> Option<ObjRef> {
20 match super::scope_get(&self.global, "customElements")? {
21 Value::Object(reg) => match reg.borrow().props.get("_registry") {
22 Some(Value::Object(m)) => Some(m.clone()),
23 _ => None,
24 },
25 _ => None,
26 }
27 }
28
29 pub(crate) fn ce_ctor_for(&self, tag: &str) -> Option<Value> {
31 if self.dom.borrow().custom_elements.is_empty() {
32 return None;
33 }
34 let reg = self.ce_registry()?;
35 let v = reg.borrow().props.get(tag).cloned();
36 v
37 }
38
39 fn ce_name_for_ctor(&self, func: &Value) -> Option<String> {
41 let Value::Object(f) = func else { return None };
42 let reg = self.ce_registry()?;
43 let b = reg.borrow();
44 b.props
45 .iter()
46 .find(|(_, v)| matches!(v, Value::Object(o) if Rc::ptr_eq(o, f)))
47 .map(|(k, _)| k.clone())
48 }
49
50 pub(crate) fn element_class_proto(&self, func: &Value) -> Option<ObjRef> {
52 let Value::Object(f) = func else { return None };
53 let proto = match f.borrow().props.get("prototype") {
54 Some(Value::Object(p)) => p.clone(),
55 _ => return None,
56 };
57 let html = match super::scope_get(&self.global, "HTMLElement")? {
58 Value::Object(h) => match h.borrow().props.get("prototype") {
59 Some(Value::Object(p)) => p.clone(),
60 _ => return None,
61 },
62 _ => return None,
63 };
64 let mut cur = Some(proto.clone());
65 let mut guard = 0;
66 while let Some(p) = cur {
67 if Rc::ptr_eq(&p, &html) {
68 return Some(proto);
69 }
70 guard += 1;
71 if guard > 64 {
72 break;
73 }
74 cur = p.borrow().proto.clone();
75 }
76 None
77 }
78
79 pub(crate) fn ce_construct_target(&mut self, new_target: &Value, proto: ObjRef) -> Result<(usize, bool), Value> {
86 if let (Some((i, nt)), Value::Object(t)) = (self.ce_stack.last(), new_target) {
87 if matches!(nt, Value::Object(o) if Rc::ptr_eq(o, t)) {
88 return Ok((*i, false));
89 }
90 }
91 let idx = match self.ce_upgrade_target.take() {
92 Some(i) => i,
93 None => {
94 let Some(name) = self.ce_name_for_ctor(new_target) else {
95 return Err(self.type_error("Failed to construct 'HTMLElement': Illegal constructor"));
96 };
97 self.dom.borrow_mut().create_element(&name)
98 }
99 };
100 self.dom.borrow_mut().node_protos.insert(idx, proto);
101 Ok((idx, true))
102 }
103
104 pub(crate) fn ce_try_upgrade(&mut self, idx: usize, connected: bool) -> bool {
107 let tag = {
108 let dom = self.dom.borrow();
109 if dom.node_protos.contains_key(&idx) {
110 return false;
111 }
112 match dom.nodes.get(idx) {
113 Some(n) if !n.is_text && n.tag.contains('-') => n.tag.to_lowercase(),
114 _ => return false,
115 }
116 };
117 let Some(ctor) = self.ce_ctor_for(&tag) else { return false };
118 self.ce_upgrade_target = Some(idx);
119 let r = self.construct_object(&ctor, &[]);
120 self.ce_upgrade_target = None;
121 if let Err(e) = r {
122 crate::warn!("[CE] <{}> の昇格で例外: {}", tag, super::calls::describe_thrown(&e, 2));
123 return false;
124 }
125 if !self.dom.borrow().node_protos.contains_key(&idx) {
126 return false;
127 }
128 let observed: Vec<(String, String)> = {
130 let dom = self.dom.borrow();
131 match (dom.custom_elements.get(&tag), dom.nodes.get(idx)) {
132 (Some(def), Some(n)) => {
133 let mut v: Vec<(String, String)> = n
134 .attrs
135 .iter()
136 .filter(|(k, _)| def.observes_attribute(k))
137 .map(|(k, v)| (k.clone(), v.clone()))
138 .collect();
139 if !n.id.is_empty() && def.observes_attribute("id") {
140 v.push((String::from("id"), n.id.clone()));
141 }
142 v
143 }
144 _ => Vec::new(),
145 }
146 };
147 let el = Value::Object(Obj::dom(idx));
148 if !observed.is_empty() {
149 if let Ok(cb) = self.get_property(&el, "attributeChangedCallback") {
150 if matches!(&cb, Value::Object(o) if o.borrow().is_callable()) {
151 for (k, v) in observed {
152 let _ = self.call_value(&cb, el.clone(), &[Value::str(k), Value::Null, Value::str(v)]);
153 }
154 }
155 }
156 }
157 if connected {
158 self.ce_callback(idx, "connectedCallback");
159 }
160 true
161 }
162
163 fn ce_callback(&mut self, idx: usize, name: &str) {
165 let el = Value::Object(Obj::dom(idx));
166 let Ok(cb) = self.get_property(&el, name) else { return };
167 if matches!(&cb, Value::Object(o) if o.borrow().is_callable()) {
168 if let Err(e) = self.call_value(&cb, el, &[]) {
169 crate::warn!("[CE] {} で例外: {}", name, super::calls::describe_thrown(&e, 2));
170 }
171 }
172 }
173
174 pub(crate) fn process_ce_reactions(&mut self) {
176 if self.ce_processing || self.dom.borrow().ce_queue.is_empty() {
177 return;
178 }
179 self.ce_processing = true;
180 let mut rounds = 0;
181 loop {
182 let events: Vec<crate::os_lib::js::dom_bridge::CeEvent> =
183 core::mem::take(&mut self.dom.borrow_mut().ce_queue);
184 if events.is_empty() {
185 break;
186 }
187 rounds += 1;
188 if rounds > 10_000 {
189 break;
190 }
191 for ev in events {
192 use crate::os_lib::js::dom_bridge::CeEvent;
193 match ev {
194 CeEvent::Created(root) => {
195 let els = self.dom.borrow().subtree_elements(root);
196 for i in els {
197 self.ce_try_upgrade(i, false);
198 }
199 }
200 CeEvent::Inserted(root) => {
201 let (connected, els) = {
202 let dom = self.dom.borrow();
203 (dom.is_attached(root), dom.subtree_elements(root))
204 };
205 if !connected {
206 continue;
207 }
208 for i in els {
209 let was = self.dom.borrow().node_protos.contains_key(&i);
211 if was {
212 self.ce_callback(i, "connectedCallback");
213 } else {
214 self.ce_try_upgrade(i, true);
215 }
216 }
217 }
218 CeEvent::Attr(idx, name, old, new) => {
219 let el = Value::Object(Obj::dom(idx));
220 if let Ok(cb) = self.get_property(&el, "attributeChangedCallback") {
221 if matches!(&cb, Value::Object(o) if o.borrow().is_callable()) {
222 let v = |x: Option<String>| x.map(Value::str).unwrap_or(Value::Null);
223 if let Err(e) = self.call_value(&cb, el, &[Value::str(name), v(old), v(new)]) {
224 crate::warn!("[CE] attributeChangedCallback で例外: {}", super::calls::describe_thrown(&e, 2));
225 }
226 }
227 }
228 }
229 CeEvent::Removed(root) => {
230 let els = self.dom.borrow().subtree_elements(root);
231 for i in els {
232 if self.dom.borrow().node_protos.contains_key(&i) {
233 self.ce_callback(i, "disconnectedCallback");
234 }
235 }
236 }
237 }
238 }
239 }
240 self.ce_processing = false;
241 }
242}