mod module_semantics;
pub use module_semantics::*;
use crate::{
ecmascript::{
Agent, Array, BUILTIN_STRING_MEMORY, EnumerateKeysAndValues, ExceptionType, JsResult,
Object, Promise, PromiseCapability, PromiseReactionHandler, String, Value,
enumerable_own_properties, get, get_active_script_or_module, if_abrupt_reject_promise_m,
inner_promise_then, to_string, unwrap_try,
},
engine::{Bindable, GcScope, NoGcScope, Scopable, Scoped, typeof_operator},
};
pub(crate) fn evaluate_import_call<'gc>(
agent: &mut Agent,
specifier: Value,
options: Option<Value>,
mut gc: GcScope<'gc, '_>,
) -> Promise<'gc> {
let specifier = specifier.bind(gc.nogc());
let mut options = options.bind(gc.nogc());
if options.is_some_and(|opt| opt.is_undefined()) {
options.take();
}
let promise_capability = PromiseCapability::new(agent, gc.nogc());
let scoped_promise = promise_capability.promise.scope(agent, gc.nogc());
let specifier = if let Ok(specifier) = String::try_from(specifier) {
specifier
} else {
let scoped_options = options.map(|o| o.scope(agent, gc.nogc()));
let specifier = to_string(agent, specifier.unbind(), gc.reborrow())
.unbind()
.bind(gc.nogc());
options = scoped_options.map(|o| unsafe { o.take(agent) }.bind(gc.nogc()));
let promise_capability = PromiseCapability {
promise: scoped_promise.get(agent).bind(gc.nogc()),
must_be_unresolved: true,
};
if_abrupt_reject_promise_m!(agent, specifier, promise_capability, gc)
};
let (promise, specifier, attributes, gc) = if let Some(options) = options {
let Ok(options) = Object::try_from(options) else {
return reject_import_not_object_or_undefined(
agent,
scoped_promise,
options.unbind(),
gc.into_nogc(),
);
};
let specifier = specifier.scope(agent, gc.nogc());
let attributes_obj = get(
agent,
options.unbind(),
BUILTIN_STRING_MEMORY.with.to_property_key(),
gc.reborrow(),
)
.unbind()
.bind(gc.nogc());
let promise_capability = PromiseCapability {
promise: scoped_promise.get(agent).bind(gc.nogc()),
must_be_unresolved: true,
};
let attributes_obj =
if_abrupt_reject_promise_m!(agent, attributes_obj, promise_capability, gc);
if !attributes_obj.is_undefined() {
let Ok(attributes_obj) = Object::try_from(attributes_obj) else {
return reject_import_not_object_or_undefined(
agent,
scoped_promise,
attributes_obj.unbind(),
gc.into_nogc(),
);
};
let entries = enumerable_own_properties::<EnumerateKeysAndValues>(
agent,
attributes_obj.unbind(),
gc.reborrow(),
)
.unbind();
let gc = gc.into_nogc();
let entries = entries.bind(gc);
let promise = unsafe { scoped_promise.take(agent) }.bind(gc);
let promise_capability = PromiseCapability {
promise,
must_be_unresolved: true,
};
let entries = match entries {
Err(err) => {
promise_capability.reject(agent, err.value().unbind(), gc);
return promise_capability.promise;
}
Ok(value) => {
value
}
};
let mut attributes: Vec<ImportAttributeRecord> = Vec::with_capacity(entries.len());
for entry in entries {
let entry = Array::try_from(entry).unwrap();
let entry = entry.get_storage(agent).values;
let key = entry[0].unwrap();
let value = entry[0].unwrap();
if let Ok(key) = String::try_from(key) {
let Ok(value) = String::try_from(value) else {
return reject_unsupported_import_attribute(
agent,
promise_capability,
key.unbind(),
gc.into_nogc(),
);
};
attributes.push(ImportAttributeRecord { key, value });
}
}
if !all_import_attributes_supported(agent, &attributes) {
return reject_unsupported_import_attributes(agent, promise_capability, gc);
}
attributes.sort_by(|a, b| a.key.as_wtf8_(agent).cmp(b.key.as_wtf8_(agent)));
let specifier = unsafe { specifier.take(agent) }.bind(gc);
(promise, specifier, attributes.into_boxed_slice(), gc)
} else {
let gc = gc.into_nogc();
let specifier = unsafe { specifier.take(agent) }.bind(gc);
let promise = unsafe { scoped_promise.take(agent) }.bind(gc);
(promise, specifier, Default::default(), gc)
}
} else {
let specifier = specifier.unbind();
let gc = gc.into_nogc();
let specifier = specifier.bind(gc);
let promise = unsafe { scoped_promise.take(agent) }.bind(gc);
(promise, specifier, Default::default(), gc)
};
let module_request = ModuleRequest::new_dynamic(
agent, specifier, attributes, gc,
);
let referrer: Referrer = get_active_script_or_module(agent, gc)
.map(|m| m.into())
.unwrap_or_else(|| agent.current_realm(gc).into());
let mut payload = GraphLoadingStateRecord::from_promise(promise);
agent
.host_hooks
.load_imported_module(agent, referrer, module_request, None, &mut payload, gc);
promise
}
#[cold]
#[inline(never)]
fn reject_import_not_object_or_undefined<'gc>(
agent: &mut Agent,
scoped_promise: Scoped<Promise>,
value: Value,
gc: NoGcScope<'gc, '_>,
) -> Promise<'gc> {
let value = value.bind(gc);
let promise_capability = PromiseCapability {
promise: unsafe { scoped_promise.take(agent) }.bind(gc),
must_be_unresolved: true,
};
let message = format!(
"import: expected object or undefined, got {}",
typeof_operator(agent, value, gc).to_string_lossy_(agent)
);
let error = agent.throw_exception(ExceptionType::TypeError, message, gc);
promise_capability.reject(agent, error.value(), gc);
promise_capability.promise
}
#[cold]
#[inline(never)]
fn reject_unsupported_import_attribute<'gc>(
agent: &mut Agent,
promise_capability: PromiseCapability<'gc>,
key: String<'gc>,
gc: NoGcScope<'gc, '_>,
) -> Promise<'gc> {
let message = format!(
"Unsupported import attribute: {}",
key.to_string_lossy_(agent)
);
let error = agent.throw_exception(ExceptionType::TypeError, message, gc);
promise_capability.reject(agent, error.value(), gc);
promise_capability.promise
}
#[cold]
#[inline(never)]
fn reject_unsupported_import_attributes<'gc>(
agent: &mut Agent,
promise_capability: PromiseCapability<'gc>,
gc: NoGcScope<'gc, '_>,
) -> Promise<'gc> {
let error = agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Unsupported import attributes",
gc,
);
promise_capability.reject(agent, error.value(), gc);
promise_capability.promise
}
pub(super) fn continue_dynamic_import<'a>(
agent: &mut Agent,
promise_capability: PromiseCapability<'a>,
module_completion: JsResult<'a, AbstractModule<'a>>,
gc: NoGcScope<'a, '_>,
) {
let module = match module_completion {
Err(err) => {
promise_capability.reject(agent, err.value(), gc);
return;
}
Ok(module) => module,
};
let load_promise = module.load_requested_modules(agent, None, gc);
let promise = promise_capability.promise();
inner_promise_then(
agent,
load_promise,
PromiseReactionHandler::DynamicImport { promise, module },
PromiseReactionHandler::DynamicImport { promise, module },
None,
gc,
);
}
pub(crate) fn link_and_evaluate(
agent: &mut Agent,
promise: Promise,
module: AbstractModule,
mut gc: GcScope,
) {
let promise = promise.bind(gc.nogc());
let module = module.bind(gc.nogc());
let link = module.link(agent, gc.nogc());
if let Err(err) = link {
PromiseCapability::from_promise(promise, true).reject(agent, err.value(), gc.nogc());
return;
}
let scoped_module = module.scope(agent, gc.nogc());
let promise = promise.scope(agent, gc.nogc());
let evaluate_promise = module
.unbind()
.evaluate(agent, gc.reborrow())
.unbind()
.bind(gc.nogc());
let promise = unsafe { promise.take(agent) }.bind(gc.nogc());
let module = unsafe { scoped_module.take(agent) }.bind(gc.nogc());
if let Some(result) = evaluate_promise.try_get_result(agent, gc.nogc()) {
match result {
Ok(_) => {
let namespace = get_module_namespace(agent, module, gc.nogc());
unwrap_try(PromiseCapability::from_promise(promise, true).try_resolve(
agent,
namespace.into(),
gc.nogc(),
));
}
Err(err) => {
PromiseCapability::from_promise(promise, true).reject(
agent,
err.value(),
gc.nogc(),
);
}
}
return;
};
inner_promise_then(
agent,
evaluate_promise,
PromiseReactionHandler::DynamicImportEvaluate { promise, module },
PromiseReactionHandler::DynamicImportEvaluate { promise, module },
None,
gc.nogc(),
);
}
pub(crate) fn import_get_module_namespace(
agent: &mut Agent,
promise: Promise,
module: AbstractModule,
gc: NoGcScope,
) {
let promise = promise.bind(gc);
let module = module.bind(gc);
let namespace = get_module_namespace(agent, module, gc);
unwrap_try(PromiseCapability::from_promise(promise, true).try_resolve(
agent,
namespace.into(),
gc,
));
}