Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions integration-tests/tests/new_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,69 @@ fn win32_pointer_argument_rejects_plain_objects() {
);
}

#[test]
fn win32_callback_runs_during_the_call() {
let mut rt = Runtime::new(".");
assert_js(
&mut rt,
r#"(function () {
var user32 = NSWinRT.win32.define('user32.dll', { EnumWindows: { args: ['pointer', 'pointer'], ret: 'bool' } });
var seen = 0, lparam = -1;
var proc = NSWinRT.win32.callback('bool', ['pointer', 'pointer'], function (hwnd, lp) {
seen++; lparam = lp; return seen < 3;
});
if (typeof proc.pointer !== 'number' || proc.pointer === 0) return 'no pointer';
user32.EnumWindows(proc.pointer, 42);
proc.release();
proc.release();
var throwing = NSWinRT.win32.callback('bool', ['pointer', 'pointer'], function () { throw new Error('boom'); });
var r = user32.EnumWindows(throwing.pointer, 0);
throwing.release();
return seen >= 1 && seen <= 3 && lparam === 42 && r === false ? true : 'seen ' + seen + ' lparam ' + lparam + ' r ' + r;
})()"#,
"win32.callback",
);
assert!(runtime::get_last_js_error().unwrap_or_default().contains("boom"));
}

#[test]
fn win32_message_window_delivers_messages() {
let mut rt = Runtime::new(".");
assert_js(
&mut rt,
r#"(function () {
var user32 = NSWinRT.win32.define('user32.dll', {
IsWindow: { args: ['pointer'], ret: 'bool' },
PostMessageW: { args: ['pointer', 'u32', 'pointer', 'pointer'], ret: 'bool' },
});
globalThis.__received = [];
globalThis.__win = NSWinRT.win32.messageWindow(function (msg, w, l) { __received.push([msg, w, l]); });
return user32.IsWindow(__win.hwnd) && user32.PostMessageW(__win.hwnd, 0x8001, 7, 9);
})()"#,
"win32.messageWindow create + post",
);
// The listener runs from the message loop (a dispatcher work item when one exists).
for _ in 0..20 {
runtime::pump_messages();
if eval(&mut rt, "__received.length > 0") == "true" {
break;
}
std::thread::sleep(std::time::Duration::from_millis(10));
}
assert_js(
&mut rt,
r#"(function () {
var user32 = NSWinRT.win32.define('user32.dll', { IsWindow: { args: ['pointer'], ret: 'bool' } });
var hit = __received.filter(function (m) { return m[0] === 0x8001; })[0];
__win.close();
var closed = !user32.IsWindow(__win.hwnd);
__win.close();
return hit && hit[1] === 7 && hit[2] === 9 && closed ? true : JSON.stringify(__received) + ' closed ' + closed;
})()"#,
"win32.messageWindow delivery + close",
);
}

#[test]
fn win32_exports_returns_array() {
let mut rt = Runtime::new(".");
Expand Down
29 changes: 29 additions & 0 deletions runtime/src/global_fns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2496,6 +2496,29 @@ include_str!("js/crypto.js"), r#"
});
},

/**
* A native function pointer that calls `fn`:
* const proc = NSWinRT.win32.callback('bool', ['pointer', 'pointer'], (hwnd, lp) => true);
* user32.EnumWindows(proc.pointer, 0);
* proc.release(); // only once native code can no longer call it
*/
callback: function (ret, argTypes, fn) {
if (typeof fn !== 'function') throw new TypeError('win32.callback: fn must be a function');
return globalThis.__nsWin32Callback(ret, argTypes || [], fn);
},

/**
* A hidden window whose messages go to listener(msg, wParam, lParam): for tray
* icons, hotkeys and menus. Returns { hwnd, close() }.
*/
messageWindow: function (listener) {
if (typeof listener !== 'function') throw new TypeError('win32.messageWindow: listener must be a function');
var hwnd = globalThis.__nsWin32MessageWindow(listener);
return {
hwnd: hwnd,
close: function () { globalThis.__nsWin32MessageWindowClose(hwnd); },
};
},
};
})();

Expand Down Expand Up @@ -4394,6 +4417,12 @@ pub(crate) fn init_async_helpers(
register!("__nsWin32CallRaw", handle_win32_call_raw);
register!("__nsWin32BindFast", handle_win32_bind_fast);
register!("__nsWin32Exports", handle_win32_exports);
register!("__nsWin32Callback", crate::win32_callback::handle_win32_callback);
register!("__nsWin32MessageWindow", crate::win32_callback::handle_win32_message_window);
register!(
"__nsWin32MessageWindowClose",
crate::win32_callback::handle_win32_message_window_close
);
register!("__ns__setTimeout", crate::timers::handle_ns_set_timeout);
register!("__ns__setInterval", crate::timers::handle_ns_set_interval);
register!("__ns__clearTimeout", crate::timers::handle_ns_clear_timeout);
Expand Down
2 changes: 2 additions & 0 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ mod value;
pub(crate) mod win32;
pub(crate) mod win32_call;
#[cfg(feature = "classic")]
pub(crate) mod win32_callback;
#[cfg(feature = "classic")]
pub(crate) mod win32_fast;
#[cfg(feature = "classic")]
pub(crate) mod win32_known_fns;
Expand Down
6 changes: 4 additions & 2 deletions runtime/src/napi_engine/win32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -589,9 +589,11 @@ unsafe extern "system" fn wnd_proc(
if msg == WM_NCDESTROY {
forget(key);
} else if LISTENERS.with(|l| l.borrow().contains_key(&key)) {
// JS never runs inside the window procedure.
// Off the UI thread, the listener runs on the window's own thread.
let (w, l) = (wparam.0, lparam.0);
if !crate::ui_dispatcher::enqueue_on_ui_thread(move || deliver(key, msg, w, l)) {
if !crate::ui_dispatcher::is_ui_thread()
|| !crate::ui_dispatcher::enqueue_on_ui_thread(move || deliver(key, msg, w, l))
{
deliver(key, msg, w, l);
}
}
Expand Down
Loading