1use crate::host::{is_callable, take_exc_or_error, type_error, with_host, JsObj};
17use fusevm::Value;
18use indexmap::IndexMap;
19use std::cell::RefCell;
20
21pub const METHODS: &[&str] = &["create", "createDomain"];
23
24pub const DOMAIN_METHODS: &[&str] = &[
27 "run",
28 "add",
29 "remove",
30 "bind",
31 "intercept",
32 "enter",
33 "exit",
34 "dispose",
35];
36
37thread_local! {
38 static STACK: RefCell<Vec<Value>> = const { RefCell::new(Vec::new()) };
41}
42
43pub fn new_domain() -> Value {
48 let members = with_host(|h| h.new_array(Vec::new()));
49 let mut extra = IndexMap::new();
50 extra.insert("@@members".to_string(), members);
51 super::net::new_emitter_object("Domain", extra)
52}
53
54pub fn call(method: &str, _args: &[Value]) -> Option<Result<Value, String>> {
56 match method {
57 "create" | "createDomain" => Some(Ok(new_domain())),
58 _ => None,
59 }
60}
61
62pub fn construct(_args: &[Value]) -> Result<Value, String> {
65 Ok(new_domain())
66}
67
68pub fn constant(name: &str) -> Option<Value> {
70 match name {
71 "active" => Some(active().unwrap_or_else(|| with_host(|h| h.null()))),
72 _ => None,
73 }
74}
75
76pub fn instance_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
79 if let Some(r) = emitter_dispatch(recv, method, &args) {
81 return r;
82 }
83 match method {
84 "run" => {
85 let f = args.first().cloned().unwrap_or(Value::Undef);
86 let call_args = args.get(1..).map(|s| s.to_vec()).unwrap_or_default();
87 domain_run(recv, &f, call_args)
88 }
89 "add" => {
90 if let Some(e) = args.into_iter().next() {
91 track(recv, e, true);
92 }
93 Ok(Value::Undef)
94 }
95 "remove" => {
96 if let Some(e) = args.into_iter().next() {
97 track(recv, e, false);
98 }
99 Ok(Value::Undef)
100 }
101 "bind" => Ok(make_wrapper(
102 recv,
103 args.into_iter().next().unwrap_or(Value::Undef),
104 "@@bound",
105 )),
106 "intercept" => Ok(make_wrapper(
107 recv,
108 args.into_iter().next().unwrap_or(Value::Undef),
109 "@@intercept",
110 )),
111 "enter" => {
112 enter(recv);
113 Ok(Value::Undef)
114 }
115 "exit" => {
116 exit(recv);
117 Ok(Value::Undef)
118 }
119 "dispose" => Ok(Value::Undef),
121 "@@bound" => {
123 let domain = get_prop(recv, "@@boundDomain").unwrap_or_else(|| recv.clone());
124 let f = get_prop(recv, "@@boundFn").unwrap_or(Value::Undef);
125 domain_run(&domain, &f, args)
126 }
127 "@@intercept" => {
128 let domain = get_prop(recv, "@@boundDomain").unwrap_or_else(|| recv.clone());
129 let f = get_prop(recv, "@@boundFn").unwrap_or(Value::Undef);
130 let err = args.first().cloned().unwrap_or(Value::Undef);
131 let is_err = with_host(|h| !matches!(err, Value::Undef) && !h.is_null(&err));
132 if is_err {
133 emit_error(&domain, err);
134 Ok(Value::Undef)
135 } else {
136 let rest = args.get(1..).map(|s| s.to_vec()).unwrap_or_default();
137 domain_run(&domain, &f, rest)
138 }
139 }
140 _ => Err(type_error(&format!("domain.{method} is not a function"))),
141 }
142}
143
144fn domain_run(domain: &Value, f: &Value, call_args: Vec<Value>) -> Result<Value, String> {
150 if !with_host(|h| is_callable(h, f)) {
151 return Err(type_error("domain.run requires a function"));
152 }
153 enter(domain);
154 let r = crate::host::invoke(f, call_args, None);
155 exit(domain);
156 match r {
157 Ok(v) => Ok(v),
158 Err(e) => {
159 let err = take_exc_or_error(&e);
163 with_host(|h| h.signal = None);
164 emit_error(domain, err);
165 Ok(Value::Undef)
166 }
167 }
168}
169
170fn emit_error(domain: &Value, err: Value) {
172 let name = with_host(|h| h.new_str("error"));
173 let _ = super::events::instance_call(domain, "emit", vec![name, err]);
174}
175
176fn make_wrapper(domain: &Value, f: Value, kind: &str) -> Value {
180 let mut extra = IndexMap::new();
181 extra.insert("@@boundFn".to_string(), f);
182 extra.insert("@@boundDomain".to_string(), domain.clone());
183 let holder = super::net::new_emitter_object("Domain", extra);
184 with_host(|h| {
185 h.alloc(JsObj::BoundMethod {
186 recv: holder,
187 name: kind.to_string(),
188 })
189 })
190}
191
192fn enter(domain: &Value) {
195 STACK.with(|s| s.borrow_mut().push(domain.clone()));
196}
197
198fn exit(domain: &Value) {
199 STACK.with(|s| {
200 let mut s = s.borrow_mut();
201 if let Some(pos) = s.iter().rposition(|x| x == domain) {
204 s.remove(pos);
205 }
206 });
207}
208
209fn active() -> Option<Value> {
210 STACK.with(|s| s.borrow().last().cloned())
211}
212
213fn track(recv: &Value, emitter: Value, add: bool) {
216 with_host(|h| {
217 let arr = match h.get(recv) {
218 Some(JsObj::Object(p)) => p.get("@@members").cloned(),
219 _ => None,
220 };
221 if let Some(a) = arr {
222 if let Some(JsObj::Array(items)) = h.get_mut(&a) {
223 if add {
224 if !items.iter().any(|x| x == &emitter) {
225 items.push(emitter);
226 }
227 } else if let Some(pos) = items.iter().position(|x| x == &emitter) {
228 items.remove(pos);
229 }
230 }
231 }
232 });
233}
234
235fn get_prop(recv: &Value, key: &str) -> Option<Value> {
238 with_host(|h| match h.get(recv) {
239 Some(JsObj::Object(p)) => p.get(key).cloned(),
240 _ => None,
241 })
242}
243
244fn emitter_dispatch(recv: &Value, method: &str, args: &[Value]) -> Option<Result<Value, String>> {
246 super::events::METHODS
247 .contains(&method)
248 .then(|| super::events::instance_call(recv, method, args.to_vec()))
249}