brep_script_core/
modules.rs1use crate::{Failure, Logs};
3use boa_engine::builtins::promise::PromiseState;
4use boa_engine::module::{ModuleLoader, ModuleRequest, Referrer};
5use boa_engine::{Context, JsNativeError, JsResult, JsValue, Module, Source};
6use std::{cell::RefCell, collections::BTreeMap, path::Path, rc::Rc};
7
8struct Loader {
9 sources: BTreeMap<String, String>,
10 parsed: RefCell<BTreeMap<String, Module>>,
11}
12impl ModuleLoader for Loader {
13 async fn load_imported_module(
14 self: Rc<Self>,
15 referrer: Referrer,
16 request: ModuleRequest,
17 context: &RefCell<&mut Context>,
18 ) -> JsResult<Module> {
19 let spec = request.specifier().to_std_string_escaped();
20 if !(spec.starts_with("./") || spec.starts_with("../"))
21 || spec.contains('\\')
22 || !request.attributes().is_empty()
23 {
24 return Err(JsNativeError::typ()
25 .with_message(format!("unsupported package import {spec}"))
26 .into());
27 }
28 let importer = referrer
31 .path()
32 .and_then(Path::to_str)
33 .ok_or_else(|| JsNativeError::typ().with_message("missing package referrer"))?;
34 let mut parts: Vec<&str> = importer.split('/').collect();
35 parts.pop();
36 for part in spec.split('/') {
37 match part {
38 "" | "." => {}
39 ".." => {
40 if parts.pop().is_none() {
41 return Err(JsNativeError::typ()
42 .with_message("module outside package")
43 .into());
44 }
45 }
46 part => parts.push(part),
47 }
48 }
49 let mut cx = context.borrow_mut();
50 self.parse(&parts.join("/"), &mut cx)
51 }
52}
53impl Loader {
54 fn parse(&self, key: &str, cx: &mut Context) -> JsResult<Module> {
55 if key.is_empty()
56 || key
57 .split('/')
58 .any(|p| p.is_empty() || p == "." || p == "..")
59 || key.contains('\\')
60 || key.contains(':')
61 {
62 return Err(JsNativeError::typ()
63 .with_message("invalid package module path")
64 .into());
65 }
66 if let Some(m) = self.parsed.borrow().get(key) {
67 return Ok(m.clone());
68 }
69 let source = self.sources.get(key).ok_or_else(|| {
70 JsNativeError::typ().with_message(format!("missing package module {key}"))
71 })?;
72 let module = Module::parse(
73 Source::from_bytes(source).with_path(Path::new(key)),
74 None,
75 cx,
76 )
77 .map_err(|e| JsNativeError::syntax().with_message(format!("{key}: {e}")))?;
78 self.parsed
79 .borrow_mut()
80 .insert(key.to_owned(), module.clone());
81 Ok(module)
82 }
83}
84pub fn synchronous(value: JsValue) -> JsResult<JsValue> {
86 if value
87 .as_object()
88 .is_some_and(|o| o.is::<boa_engine::builtins::promise::Promise>())
89 {
90 Err(JsNativeError::typ()
91 .with_message("Promise results are unsupported in synchronous plugin callbacks")
92 .into())
93 } else {
94 Ok(value)
95 }
96}
97pub fn with_module<T>(
100 sources: &BTreeMap<String, String>,
101 entry: &str,
102 setup: impl FnOnce(&mut Context, &Logs) -> JsResult<()>,
103 invoke: impl FnOnce(&Module, &mut Context) -> JsResult<T>,
104) -> Result<T, Failure> {
105 let loader = Rc::new(Loader {
106 sources: sources.clone(),
107 parsed: RefCell::new(BTreeMap::new()),
108 });
109 let mut cx = Context::builder()
110 .module_loader(loader.clone())
111 .build()
112 .map_err(|e| Failure {
113 message: e.to_string(),
114 logs: vec![],
115 })?;
116 cx.runtime_limits_mut()
117 .set_loop_iteration_limit(crate::LOOP_ITERATION_LIMIT);
118 cx.runtime_limits_mut()
119 .set_recursion_limit(crate::RECURSION_LIMIT);
120 let logs = Rc::new(RefCell::new(Vec::new()));
121 let result = (|| {
122 crate::install_console(&mut cx, &logs)?;
123 setup(&mut cx, &logs)?;
124 let module = loader.parse(entry, &mut cx)?;
125 let promise = module.load_link_evaluate(&mut cx);
126 cx.run_jobs()?;
127 match promise.state() {
128 PromiseState::Fulfilled(_) => {}
129 PromiseState::Rejected(value) => return Err(boa_engine::JsError::from_opaque(value)),
130 PromiseState::Pending => {
131 return Err(JsNativeError::typ()
132 .with_message("pending module evaluation is unsupported")
133 .into())
134 }
135 }
136 invoke(&module, &mut cx)
137 })();
138 result.map_err(|e| {
139 let trace = e.to_string();
140 let stack = trace.find("\n at ").map(|i| &trace[i..]).unwrap_or("");
141 Failure {
142 message: format!("{entry}: {}{stack}", crate::describe(e, &mut cx)),
143 logs: logs.borrow().clone(),
144 }
145 })
146}