Browse Source

Revert Windows dialog smoke acceptance of unavailable COM classes

07akioni 1 month ago
parent
commit
6d103f8f7c

+ 2 - 2
packages/host/directory-picker-native/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/host/directory-picker-native/README.md
 #   pnpm run verify-translation-pairing --write packages/host/directory-picker-native/README.md
-README.md: ecd66dedf19236cc56db35ec46eb440061b42a26
-README.zh.md: 9c16a145a2e78145980babfcf883d11e9b881646
+README.md: 3f980831848965b976e32b0e5ef25abb77fe4e6e
+README.zh.md: 7a221cbbfebf3d79534e049800e26dab6ca39ad3

+ 1 - 1
packages/host/directory-picker-native/README.md

@@ -97,7 +97,7 @@ None; this package neither assembles nor sends a provider request.
 These limits define when the native interaction is unavailable or fragile. They are current package constraints, not a task backlog.
 These limits define when the native interaction is unavailable or fragile. They are current package constraints, not a task backlog.
 
 
 - **Linux requires desktop tooling** — with neither Zenity nor KDialog installed, `pick` rejects with an actionable error; it does not fall back to a typed-path prompt (the browse backend is that fallback at the composition level).
 - **Linux requires desktop tooling** — with neither Zenity nor KDialog installed, `pick` rejects with an actionable error; it does not fall back to a typed-path prompt (the browse backend is that fallback at the composition level).
-- **Windows has no mechanism fallback** — the child-process picker through packaged koffi is the only native tier, so a COM refusal or dialog crash surfaces the failure; the browse backend remains the fallback at the composition level. The real-worker smoke accepts normal abort closure or the specific FileOpenDialog class-unavailable HRESULT `0x80040111`; that refusal validates error propagation, not successful dialog display. Other native errors and forced termination fail the smoke.
+- **Windows has no mechanism fallback** — the child-process picker through packaged koffi is the only native tier, so a COM refusal or dialog crash surfaces the failure; the browse backend remains the fallback at the composition level.
 - **Windows foreground grant relies on injected input** — the child synthesizes an Alt press before `Show` so the dialog can take the foreground from a background host; where synthesized input is suppressed (secure desktops, restricted remote sessions, an elevated foreground window), the dialog may still open behind other windows. The technique is validated on Windows 11 only.
 - **Windows foreground grant relies on injected input** — the child synthesizes an Alt press before `Show` so the dialog can take the foreground from a background host; where synthesized input is suppressed (secure desktops, restricted remote sessions, an elevated foreground window), the dialog may still open behind other windows. The technique is validated on Windows 11 only.
 
 
 <a id="dev-note"></a>
 <a id="dev-note"></a>

+ 1 - 1
packages/host/directory-picker-native/README.zh.md

@@ -97,7 +97,7 @@ kind: "package-reference"
 这些限制说明原生交互何时不可用或不稳定。它们是当前包约束,不是任务积压。
 这些限制说明原生交互何时不可用或不稳定。它们是当前包约束,不是任务积压。
 
 
 - **Linux 依赖桌面工具**——Zenity 与 KDialog 均未安装时,`pick` 以包含解决建议的错误拒绝;它不会回退为手输路径提示(组合层面的回退是浏览后端)。
 - **Linux 依赖桌面工具**——Zenity 与 KDialog 均未安装时,`pick` 以包含解决建议的错误拒绝;它不会回退为手输路径提示(组合层面的回退是浏览后端)。
-- **Windows 没有机制级回退**——通过打包依赖 koffi 运行的子进程选择器是唯一原生层级,因此 COM 拒绝或对话框崩溃会直接上报失败;组合层面的回退仍是浏览后端。真实 worker smoke 接受正常取消关闭,或 FileOpenDialog 类不可用的特定 HRESULT `0x80040111`;该拒绝结果只验证错误传递,不证明对话框成功显示。其他原生错误和强制终止均使 smoke 失败。
+- **Windows 没有机制级回退**——通过打包依赖 koffi 运行的子进程选择器是唯一原生层级,因此 COM 拒绝或对话框崩溃会直接上报失败;组合层面的回退仍是浏览后端。
 - **Windows 前台授权依赖注入的输入**——子进程在 `Show` 之前合成一次 Alt 按键,对话框才能从后台宿主取得前台;在合成输入被抑制的环境(安全桌面、受限远程会话、提权前台窗口)中,对话框仍可能在其他窗口后面打开。该技术仅在 Windows 11 上验证过。
 - **Windows 前台授权依赖注入的输入**——子进程在 `Show` 之前合成一次 Alt 按键,对话框才能从后台宿主取得前台;在合成输入被抑制的环境(安全桌面、受限远程会话、提权前台窗口)中,对话框仍可能在其他窗口后面打开。该技术仅在 Windows 11 上验证过。
 
 
 <a id="dev-note"></a>
 <a id="dev-note"></a>

+ 6 - 22
packages/host/directory-picker-native/tests/win32-dialog.spec.ts

@@ -3,7 +3,7 @@
  * the WM_CLOSE abort service (including the show-race retry and the kill
  * the WM_CLOSE abort service (including the show-race retry and the kill
  * last resort) against fakes, plus the real spawn plumbing — POSIX hosts
  * last resort) against fakes, plus the real spawn plumbing — POSIX hosts
  * prove the default path rejects cleanly (koffi cannot load ole32 there),
  * prove the default path rejects cleanly (koffi cannot load ole32 there),
- * and win32 hosts auto-abort a real dialog or report COM class unavailability.
+ * and win32 hosts briefly open and auto-abort a real dialog.
  */
  */
 
 
 import { EventEmitter } from 'node:events'
 import { EventEmitter } from 'node:events'
@@ -40,10 +40,6 @@ function harness(overrides: Partial<Win32DialogInternals> = {}): Harness {
 }
 }
 
 
 const live = (): AbortSignal => new AbortController().signal
 const live = (): AbortSignal => new AbortController().signal
-const REAL_DIALOG_REJECTION = new RegExp([
-  '^(?:native directory picker aborted|',
-  String.raw`win32 folder dialog failed: Error: CoCreateInstance\(FileOpenDialog\) failed: HRESULT 0x80040111)(?:\r?\n|$)`,
-].join(''))
 
 
 describe('pickWin32Directory', () => {
 describe('pickWin32Directory', () => {
   it('resolves the selected path and the cancellation null', async () => {
   it('resolves the selected path and the cancellation null', async () => {
@@ -155,25 +151,13 @@ describe('pickWin32Directory', () => {
     await expect(pickWin32Directory(live())).rejects.toThrow('win32 folder dialog failed')
     await expect(pickWin32Directory(live())).rejects.toThrow('win32 folder dialog failed')
   }, 30_000)
   }, 30_000)
 
 
-  it('rejects unrelated native failures and forced termination as dialog smoke outcomes', () => {
-    expect('native directory picker aborted').toMatch(REAL_DIALOG_REJECTION)
-    expect('win32 folder dialog failed: Error: CoCreateInstance(FileOpenDialog) failed: HRESULT 0x80040111\n    at createFolderDialog').toMatch(REAL_DIALOG_REJECTION)
-    for (const message of [
-      'win32 folder dialog failed: Error: CoCreateInstance(FileOpenDialog) failed: HRESULT 0x80004005',
-      'win32 folder dialog worker exited before reporting a result',
-      'native directory picker aborted (dialog unresponsive; worker killed)',
-    ]) expect(message).not.toMatch(REAL_DIALOG_REJECTION)
-  })
-
-  it.skipIf(process.platform !== 'win32')('abort-closes a real dialog or reports its unavailable COM class', async () => {
+  // win32 hosts run the true COM smoke instead: a real dialog opens briefly
+  // and the abort service closes it (the same lever a disconnecting client pulls).
+  it.skipIf(process.platform !== 'win32')('opens and abort-closes a real dialog', async () => {
     const controller = new AbortController()
     const controller = new AbortController()
-    const timer = setTimeout(() => {
+    setTimeout(() => {
       controller.abort()
       controller.abort()
     }, 400)
     }, 400)
-    try {
-      await expect(pickWin32Directory(controller.signal)).rejects.toThrow(REAL_DIALOG_REJECTION)
-    } finally {
-      clearTimeout(timer)
-    }
+    await expect(pickWin32Directory(controller.signal)).rejects.toThrow('native directory picker aborted')
   }, 30_000)
   }, 30_000)
 })
 })