fix(picker): cascade thread DPI contexts and harden the round-three review points

- setThreadDpiAwareness checks SetThreadDpiAwarenessContext's return value
  and cascades per-monitor-v2 -> per-monitor -> system-aware; DPI stays a
  deliberate cosmetic best-effort - a host accepting none (or lacking the
  API, pre-1607) still gets the modern dialog instead of a downgrade to the
  legacy fallback chain over a cosmetic concern.
- The mocked-koffi world now uses a distinctive 4-byte pointer width and
  rejects mis-sized out-buffers and mis-divided vtable offsets, so a
  regression to hardcoded 8s fails the suite (the ia32 bug class).
- A keyless built-worker e2e guard loads lib/worker.cjs under plain
  worker_threads on POSIX (the workflow-workerthread shape).
- The 'loaded lazily' module claims are reworded to attribute laziness to
  the dynamic import('koffi') calls, and the discarded close-attempt
  rejection is named at its catch.
This commit is contained in:
Huanqi Cao
2026-08-03 21:42:05 +08:00
parent 8500a21658
commit e182f03230
12 changed files with 128 additions and 35 deletions

View File

@@ -1,9 +1,10 @@
/**
* koffi-backed Win32 bindings for the folder dialog: the COM vtable calls
* behind {@link Win32DialogBindings} plus the cross-thread window closer the
* driver uses to service aborts. Loaded lazily and only on win32 (the dialog
* worker and the driver's abort path), so non-Windows processes never load
* koffi — the same containment as the repo's other `win32.ts` modules.
* driver uses to service aborts. The module loads on every platform; koffi
* itself is imported lazily inside each function, so non-Windows processes
* never load it — the same containment as the repo's other `win32.ts`
* modules.
*
* The COM surface used here (IModalWindow/IFileDialog/IFileOpenDialog and
* IShellItem vtable order, the GUIDs, `FOS_*` and `SIGDN_FILESYSPATH`) is
@@ -29,7 +30,14 @@ interface Koffi {
const COINIT_APARTMENTTHREADED = 0x2
const CLSCTX_INPROC_SERVER = 0x1
const SIGDN_FILESYSPATH = 0x80058000 | 0
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4
/**
* Thread DPI awareness contexts, best first: per-monitor-v2 (Windows 10
* 1703+), per-monitor (1607+), then system-aware. `SetThreadDpiAwarenessContext`
* returns NULL for an unsupported context instead of throwing, so the caller
* cascades to the best one the host accepts; DPI stays a cosmetic
* best-effort — an unsupported host still gets the modern dialog.
*/
const DPI_AWARENESS_CONTEXTS = [-4, -3, -2]
const WM_CLOSE = 0x10
/** IFileOpenDialog vtable slots (IUnknown 0-2, IModalWindow 3, IFileDialog 4+). */
@@ -94,14 +102,22 @@ export async function loadWin32DialogBindings(): Promise<Win32DialogBindings> {
return {
setThreadDpiAwareness: () => {
let setContext: KoffiFunction
try {
const setThreadDpiAwarenessContext = user32.func('__stdcall', 'SetThreadDpiAwarenessContext', 'void *', ['intptr'])
setThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
setContext = user32.func('__stdcall', 'SetThreadDpiAwarenessContext', 'void *', ['intptr'])
} catch {
// SetThreadDpiAwarenessContext absent (Windows 10 pre-1703): the
// dialog renders at system DPI; nothing else can fail here because
// user32 itself loaded above.
// Symbol absent (pre-1607 Windows): no per-thread DPI control exists.
// Proceed anyway — the cost is a blurry dialog above 100 % scaling on
// museum hosts, and the modern picker still beats dropping to the
// legacy 5.1 tree over a cosmetic concern.
return
}
for (const context of DPI_AWARENESS_CONTEXTS) {
if (setContext(context) !== null) return
}
// Unreachable in practice (SYSTEM_AWARE is accepted wherever the symbol
// exists); if a host ever refuses everything, the dialog still works —
// just without a DPI opt-in.
},
coInitializeSta: () => coInitializeEx(null, COINIT_APARTMENTTHREADED) as number,
coUninitialize: () => {

View File

@@ -1,9 +1,10 @@
/**
* Real-process half of the Win32 dialog driver: spawn the dialog worker
* (source or built plane) and close a dialog thread's windows. Loaded lazily
* and only on the win32 default path, so non-Windows processes never touch
* worker or koffi machinery; the driver's logic is tested against fakes of
* this surface instead.
* (source or built plane) and close a dialog thread's windows. The module
* itself loads everywhere (the import chain from native-picker.ts is
* static); what stays win32-only is koffi, imported dynamically inside the
* bindings' functions. The driver's logic is tested against fakes of this
* surface instead.
*/
import { fileURLToPath } from 'node:url'

View File

@@ -49,9 +49,12 @@ export interface Win32FolderDialog {
/** The thread-level native surface the dialog sequencing runs against. */
export interface Win32DialogBindings {
/**
* Best-effort per-monitor-v2 DPI opt-in for the calling thread. Absent
* before Windows 10 1703; implementations swallow only that absence, so an
* old host merely renders the dialog at system DPI.
* Opt the calling thread into the best supported DPI awareness
* (per-monitor-v2, then per-monitor, then system-aware), checking each
* call's result. Best-effort on purpose: a host accepting none of them
* (or lacking the API, pre-1607) still shows the modern dialog — possibly
* blurry above 100 % scaling — because a cosmetic degradation must not
* cost the tier.
*/
setThreadDpiAwareness(): void
/**

View File

@@ -83,7 +83,9 @@ export async function pickWin32Directory(
const postClose = (): void => {
// Before `showing` there is no window to close; the budget below still
// runs so a worker that never reports cannot dangle the pick.
// runs so a worker that never reports cannot dangle the pick. A
// rejected close attempt (EnumThreadWindows/PostMessageW refusing) is
// discarded: the interval retries it and terminate is the backstop.
if (dialogThreadId !== undefined) void closeWindows(dialogThreadId).catch(() => undefined)
}