(unstable: bool) -> Extension {
Extension::builder(env!("CARGO_PKG_NAME"))
.ops(vec![op_napi_open::decl::()])
.event_loop_middleware(|op_state_rc, cx| {
// `work` can call back into the runtime. It can also schedule an async task
// but we don't know that now. We need to make the runtime re-poll to make
// sure no pending NAPI tasks exist.
let mut maybe_scheduling = false;
{
let mut op_state = op_state_rc.borrow_mut();
let napi_state = op_state.borrow_mut::();
while let Poll::Ready(Some(async_work_fut)) =
napi_state.async_work_receiver.poll_next_unpin(cx)
{
napi_state.pending_async_work.push(async_work_fut);
}
while let Poll::Ready(Some(tsfn_status)) =
napi_state.threadsafe_function_receiver.poll_next_unpin(cx)
{
match tsfn_status {
ThreadSafeFunctionStatus::Alive => {
napi_state.active_threadsafe_functions += 1
}
ThreadSafeFunctionStatus::Dead => {
napi_state.active_threadsafe_functions -= 1
}
};
}
if napi_state.active_threadsafe_functions > 0 {
maybe_scheduling = true;
}
}
loop {
let maybe_work = {
let mut op_state = op_state_rc.borrow_mut();
let napi_state = op_state.borrow_mut::();
napi_state.pending_async_work.pop()
};
if let Some(work) = maybe_work {
work();
maybe_scheduling = true;
} else {
break;
}
}
maybe_scheduling
})
.state(move |state| {
let (async_work_sender, async_work_receiver) =
mpsc::unbounded::();
let (threadsafe_function_sender, threadsafe_function_receiver) =
mpsc::unbounded::();
state.put(NapiState {
pending_async_work: Vec::new(),
async_work_sender,
async_work_receiver,
threadsafe_function_sender,
threadsafe_function_receiver,
active_threadsafe_functions: 0,
env_cleanup_hooks: Rc::new(RefCell::new(vec![])),
});
state.put(Unstable(unstable));
Ok(())
})
.build()
}
pub trait NapiPermissions {
fn check(&mut self, path: Option<&Path>)
-> std::result::Result<(), AnyError>;
}
pub struct Unstable(pub bool);
fn check_unstable(state: &OpState) {
let unstable = state.borrow::();
if !unstable.0 {
eprintln!("Unstable API 'node-api'. The --unstable flag must be provided.");
std::process::exit(70);
}
}
#[op(v8)]
fn op_napi_open(
scope: &mut v8::HandleScope<'scope>,
op_state: &mut OpState,
path: String,
) -> std::result::Result, AnyError>
where
NP: NapiPermissions + 'static,
{
check_unstable(op_state);
let permissions = op_state.borrow_mut::();
permissions.check(Some(&PathBuf::from(&path)))?;
let (async_work_sender, tsfn_sender, isolate_ptr, cleanup_hooks) = {
let napi_state = op_state.borrow::();
let isolate_ptr = op_state.borrow::<*mut v8::OwnedIsolate>();
(
napi_state.async_work_sender.clone(),
napi_state.threadsafe_function_sender.clone(),
*isolate_ptr,
napi_state.env_cleanup_hooks.clone(),
)
};
let napi_wrap_name = v8::String::new(scope, "napi_wrap").unwrap();
let napi_wrap = v8::Private::new(scope, Some(napi_wrap_name));
let napi_wrap = v8::Global::new(scope, napi_wrap);
// The `module.exports` object.
let exports = v8::Object::new(scope);
let mut env_shared = EnvShared::new(napi_wrap);
let cstr = CString::new(&*path).unwrap();
env_shared.filename = cstr.as_ptr();
std::mem::forget(cstr);
let ctx = scope.get_current_context();
let mut env = Env::new(
isolate_ptr,
v8::Global::new(scope, ctx),
async_work_sender,
tsfn_sender,
cleanup_hooks,
);
env.shared = Box::into_raw(Box::new(env_shared));
let env_ptr = Box::into_raw(Box::new(env)) as _;
#[cfg(unix)]
let flags = RTLD_LAZY;
#[cfg(not(unix))]
let flags = 0x00000008;
// SAFETY: opening a DLL calls dlopen
#[cfg(unix)]
let library = match unsafe { Library::open(Some(&path), flags) } {
Ok(lib) => lib,
Err(e) => return Err(type_error(e.to_string())),
};
// SAFETY: opening a DLL calls dlopen
#[cfg(not(unix))]
let library = match unsafe { Library::load_with_flags(&path, flags) } {
Ok(lib) => lib,
Err(e) => return Err(type_error(e.to_string())),
};
MODULE.with(|cell| {
let slot = *cell.borrow();
let obj = match slot {
Some(nm) => {
// SAFETY: napi_register_module guarantees that `nm` is valid.
let nm = unsafe { &*nm };
assert_eq!(nm.nm_version, 1);
// SAFETY: we are going blind, calling the register function on the other side.
let exports = unsafe {
(nm.nm_register_func)(
env_ptr,
std::mem::transmute::, napi_value>(
exports.into(),
),
)
};
// SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer
// to a value, they have the same layout
let exports = unsafe {
std::mem::transmute::>(exports)
};
Ok(serde_v8::Value { v8_value: exports })
}
None => {
// Initializer callback.
// SAFETY: we are going blind, calling the register function on the other side.
unsafe {
let init = library
.get:: napi_value>(b"napi_register_module_v1")
.expect("napi_register_module_v1 not found");
init(
env_ptr,
std::mem::transmute::, napi_value>(
exports.into(),
),
)
};
Ok(serde_v8::Value {
v8_value: exports.into(),
})
}
};
// NAPI addons can't be unloaded, so we're going to "forget" the library
// object so it lives till the program exit.
std::mem::forget(library);
obj
})
}