() -> 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);
}
if napi_state.active_threadsafe_functions > 0 {
maybe_scheduling = true;
}
let tsfn_ref_counters = napi_state.tsfn_ref_counters.lock().clone();
for (_id, counter) in tsfn_ref_counters.iter() {
if counter.load(std::sync::atomic::Ordering::SeqCst) > 0 {
maybe_scheduling = true;
break;
}
}
}
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![])),
tsfn_ref_counters: Arc::new(Mutex::new(vec![])),
});
Ok(())
})
.build()
}
pub trait NapiPermissions {
fn check(&mut self, path: Option<&Path>)
-> std::result::Result<(), AnyError>;
}
#[op(v8)]
fn op_napi_open(
scope: &mut v8::HandleScope<'scope>,
op_state: &mut OpState,
path: String,
global: serde_v8::Value,
) -> std::result::Result, AnyError>
where
NP: NapiPermissions + 'static,
{
let permissions = op_state.borrow_mut::();
permissions.check(Some(&PathBuf::from(&path)))?;
let (
async_work_sender,
tsfn_sender,
isolate_ptr,
cleanup_hooks,
tsfn_ref_counters,
) = {
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(),
napi_state.tsfn_ref_counters.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),
v8::Global::new(scope, global.v8_value),
async_work_sender,
tsfn_sender,
cleanup_hooks,
tsfn_ref_counters,
);
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 maybe_exports = unsafe {
(nm.nm_register_func)(
env_ptr,
std::mem::transmute::, napi_value>(
exports.into(),
),
)
};
let exports = maybe_exports
.as_ref()
.map(|_| unsafe {
// SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer
// to a value, they have the same layout
std::mem::transmute::>(
maybe_exports,
)
})
.unwrap_or_else(|| {
// If the module didn't return anything, we use the exports object.
exports.into()
});
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");
let maybe_exports = init(
env_ptr,
std::mem::transmute::, napi_value>(
exports.into(),
),
);
let exports = maybe_exports
.as_ref()
.map(|_| {
// SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer
// to a value, they have the same layout
std::mem::transmute::>(
maybe_exports,
)
})
.unwrap_or_else(|| {
// If the module didn't return anything, we use the exports object.
exports.into()
});
Ok(serde_v8::Value { v8_value: exports })
}
}
};
// 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
})
}