Is it impossible to properly accept results of f.bind(...) from JavaScript?
JohnScience opened this issue · 2 comments
Hello, I'm writing a neon
module for electron
app. In order to invoke console.log
function in renderer context I implemented Window::log
in a way that would allow me to do this.
electron-client/main.js
:
// ...
class Window extends BrowserWindow {
//...
log(msg) {
this.webContents.send('log', msg);
}
// ...
}
// ...
(modulo irrelevant details)
However, when I try to supply the result of binding the first argument of init_app_dir
function with Window::log
,
electron-client/main.js
:
// ...
app.whenReady().then(() => {
//...
const win = new Window();
const init_app_dir_handler = init_app_dir.bind(null, win.log);
ipcMain.handle('init_app_dir', init_app_dir_handler);
win.main_loop();
})
and invoke init_app_dir_handler
, which the result of binding js_init_app_dir
exported as init_app_dir
,
// ...
fn js_init_app_dir(mut cx: FunctionContext) -> JsResult<JsNumber> {
let log = cx.argument::<JsFunction>(0)?;
let app_data_path = cx.argument::<JsValue>(1)?;
log
.call_with(&mut cx)
.arg(app_data_path)
// The return type of console.log() is that of the parameter
.apply::<JsValue,_>(&mut cx)?;
//let app_data_path = PathBuf::from(app_data_path);
//Ok(cx.number(init_app_dir(app_data_path)))
Ok(cx.number(1))
}
#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
cx.export_function("init_app_dir", js_init_app_dir)?;
Outcome::export(&mut cx)?;
Ok(())
}
I get the following error:
Uncaught (in promise) Error: Error invoking remote method 'init_app_dir': TypeError: Cannot read properties of undefined (reading 'webContents')
As far as I understand, the problem is that in
log
.call_with(&mut cx)
.arg(app_data_path)
the line .call_with(...)
does the following:
impl JsFunction {
/// Create a [`CallOptions`](function::CallOptions) for calling this function.
pub fn call_with<'a, C: Context<'a>>(&self, _cx: &C) -> CallOptions<'a> {
CallOptions {
this: None,
// # Safety
// Only a single context may be used at a time because parent scopes
// are locked with `&mut self`. Therefore, the lifetime of `CallOptions`
// will always be the most narrow scope possible.
callee: Handle::new_internal(unsafe { self.clone() }),
args: smallvec![],
}
}
// ...
}
i.e. it presumably overwrites this
, which presumably must have remained intact. As a result, this.webContents.send(...)
is not evaluated properly. I don't know the internals of V8 well anymore, but it seems that another type for results of f.bind(...) needed.
I can see a workaround for this particular case (namely, supplying an instance of Window
itself), but it looks like an area where neon
can be improved.
In this code, the problem is up related to Neon, native modules of V8 internals.
Neon will operate similar to JavaScript when calling a function created from bind
.
In this example, it seems like the issue is because win.log
isn't bound to win
and causing this
to be undefined.
This same problem happens with pure JavaScript. Try calling init_app_dir_handler()
directly. It's also possible to reproduce with only the Window.
const win = new Window();
const log = win.log;
log(); // this throws
This is how this
typically works in JS and also why it's a frequent source of bugs in the language.
Thank you a lot!