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

@@ -0,0 +1,31 @@
/**
* Keyless built-artifact guard (the `dsh-workflow-workerthread` built-worker
* shape): plain `worker_threads` loads `lib/worker.cjs` and the bundle reaches
* its real koffi requires. POSIX hosts prove the load path end to end through
* the deterministic ole32 rejection; win32 skips (a real dialog would open),
* where the win32-only smoke in win32-dialog.spec.ts covers the source plane
* instead. Skips until a build produces the artifact.
*/
import { existsSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { Worker } from 'node:worker_threads'
import { describe, expect, it } from 'vitest'
import type { Win32DialogWorkerMessage } from '../src/win32-dialog-worker.ts'
const builtWorker = fileURLToPath(new URL('../lib/worker.cjs', import.meta.url))
describe.skipIf(!existsSync(builtWorker) || process.platform === 'win32')('built dialog worker (lib/worker.cjs)', () => {
it('loads under plain worker_threads and reports the native-surface failure', async () => {
const message = await new Promise<Win32DialogWorkerMessage>((resolve, reject) => {
const worker = new Worker(builtWorker, { workerData: { title: 'Built-artifact guard' } })
worker.on('message', resolve)
worker.on('error', reject)
worker.on('exit', (code) => {
reject(new Error(`worker exited (${code}) before reporting`))
})
})
expect(message.kind).toBe('error')
expect((message as { kind: 'error'; message: string }).message).toMatch(/ole32|koffi/i)
}, 30_000)
})

View File

@@ -13,6 +13,12 @@ import { HRESULT_CANCELLED, runFolderDialog } from '../src/win32-dialog-logic.ts
const E_FAIL = 0x80004005 | 0
const WM_CLOSE = 0x10
/**
* Deliberately NOT 8: the bindings must derive vtable offsets and out-buffer
* sizes from koffi.sizeof('void *'), and a hardcoded 8 anywhere fails against
* this width (the win32-ia32 bug class).
*/
const FAKE_POINTER_SIZE = 4
interface ComWorld {
coInitHr: number
@@ -21,6 +27,8 @@ interface ComWorld {
getResultHr: number
getDisplayNameHr: number
hasThreadDpi: boolean
/** Contexts `SetThreadDpiAwarenessContext` accepts; others return NULL. */
supportedDpiContexts: number[]
enumThrows: boolean
path: string
titles: string[]
@@ -37,7 +45,7 @@ interface ComWorld {
function comWorld(overrides: Partial<ComWorld> = {}): ComWorld {
return {
coInitHr: 0, coCreateHr: 0, showHr: 0, getResultHr: 0, getDisplayNameHr: 0,
hasThreadDpi: true, enumThrows: false,
hasThreadDpi: true, supportedDpiContexts: [-4], enumThrows: false,
path: 'C:\\选中\\directory',
titles: [], options: [], dpiContexts: [], freed: [], released: [], posted: [],
registered: 0, unregistered: 0, uninitialized: 0,
@@ -89,6 +97,10 @@ function installFakeKoffi(world: ComWorld): void {
case 'CoUninitialize': return () => { world.uninitialized += 1 }
case 'CoCreateInstance': return (...args: unknown[]) => {
if (world.coCreateHr < 0) return world.coCreateHr
// The out-pointer must be allocated at the fake's pointer width.
if ((args[4] as Buffer).length !== FAKE_POINTER_SIZE) {
throw new Error(`CoCreateInstance out buffer must be ${FAKE_POINTER_SIZE} bytes`)
}
outBuffers.set(args[4], dialogPtr)
return 0
}
@@ -96,7 +108,10 @@ function installFakeKoffi(world: ComWorld): void {
case 'GetCurrentThreadId': return () => 31337
case 'SetThreadDpiAwarenessContext': {
if (!world.hasThreadDpi) throw new Error(`${dll}: SetThreadDpiAwarenessContext not found`)
return (context: unknown) => { world.dpiContexts.push(context); return null }
return (context: unknown) => {
world.dpiContexts.push(context)
return world.supportedDpiContexts.includes(context as number) ? { kind: 'previous-context' } : null
}
}
case 'EnumThreadWindows': return (_tid: unknown, callback: { fn: (hwnd: unknown, lparam: unknown) => number }, lparam: unknown) => {
if (world.enumThrows) throw new Error('EnumThreadWindows refused')
@@ -111,15 +126,16 @@ function installFakeKoffi(world: ComWorld): void {
}),
proto: (declaration: string) => ({ declaration }),
pointer: (type: unknown) => type,
sizeof: (type: string) => { void type; return 8 },
sizeof: (type: string) => { void type; return FAKE_POINTER_SIZE },
register: (fn: (hwnd: unknown, lparam: unknown) => number) => { world.registered += 1; return { fn } },
unregister: () => { world.unregistered += 1 },
decode: (value: unknown, offsetOrType: unknown): unknown => {
if (offsetOrType === 'str16') return (value as FakePtr).text
if (typeof offsetOrType === 'number') {
// Vtable slot read: hand back a callable-reference sentinel.
// Vtable slot read: offsets must be multiples of the fake width.
if (offsetOrType % FAKE_POINTER_SIZE !== 0) throw new Error(`vtable offset ${offsetOrType} is not pointer-aligned`)
const owner = (value as { owner: FakePtr }).owner
return { call: (args: unknown[]) => dispatch(owner, offsetOrType / 8, args) }
return { call: (args: unknown[]) => dispatch(owner, offsetOrType / FAKE_POINTER_SIZE, args) }
}
// decode(x, 'void *'): out-buffer read or vtable read.
if (outBuffers.has(value)) return outBuffers.get(value)
@@ -159,17 +175,41 @@ describe('loadWin32DialogBindings over the fake COM world', () => {
expect(world.uninitialized).toBe(1)
})
it('maps dismissal, missing DPI support, and the S_FALSE CoInitializeEx', async () => {
const world = comWorld({ showHr: HRESULT_CANCELLED, hasThreadDpi: false, coInitHr: 1 })
it('maps dismissal and the S_FALSE CoInitializeEx', async () => {
const world = comWorld({ showHr: HRESULT_CANCELLED, coInitHr: 1 })
installFakeKoffi(world)
const { loadWin32DialogBindings } = await loadBindingsModule()
const bindings = await loadWin32DialogBindings()
expect(runFolderDialog(bindings, 'Pick', vi.fn())).toBeNull()
expect(world.dpiContexts).toEqual([])
expect(world.released).toEqual(['dialog'])
expect(world.uninitialized).toBe(1)
})
it('cascades DPI contexts to the first the host accepts', async () => {
const world = comWorld({ supportedDpiContexts: [-3] })
installFakeKoffi(world)
const bindings = await (await loadBindingsModule()).loadWin32DialogBindings()
expect(runFolderDialog(bindings, 'Pick', vi.fn())).toBe('C:\\选中\\directory')
expect(world.dpiContexts).toEqual([-4, -3])
})
it('keeps the tier when no DPI context is accepted or the symbol is absent', async () => {
// DPI is a cosmetic best-effort: the modern dialog still opens.
const rejecting = comWorld({ supportedDpiContexts: [] })
installFakeKoffi(rejecting)
let bindings = await (await loadBindingsModule()).loadWin32DialogBindings()
expect(runFolderDialog(bindings, 'Pick', vi.fn())).toBe('C:\\选中\\directory')
expect(rejecting.dpiContexts).toEqual([-4, -3, -2])
vi.doUnmock('koffi')
vi.resetModules()
const preThreadDpi = comWorld({ hasThreadDpi: false })
installFakeKoffi(preThreadDpi)
bindings = await (await loadBindingsModule()).loadWin32DialogBindings()
expect(runFolderDialog(bindings, 'Pick', vi.fn())).toBe('C:\\选中\\directory')
expect(preThreadDpi.dpiContexts).toEqual([])
})
it('surfaces creation and extraction failures as HRESULT errors', async () => {
const creationWorld = comWorld({ coCreateHr: E_FAIL })
installFakeKoffi(creationWorld)