收口原生壳剪贴板读取边界

H5 HostBridge 读取剪贴板时将畸形原生返回统一收口为 false

补充原生 App 剪贴板读取归一化回归测试

原生壳门禁反查 clipboard.readText 返回值必须经过共享剪贴板边界
This commit is contained in:
2026-06-20 04:55:34 +08:00
parent 2d3331533a
commit 17c1dac76d
3 changed files with 54 additions and 1 deletions
+15
View File
@@ -1165,6 +1165,21 @@ function assertH5HostBridgePayloadBoundaries() {
'H5 HostBridge facade must normalize clipboard.writeText payloads with the shared clipboard boundary',
);
}
if (
!h5HostBridgeSource.includes(
'const clipboardText = normalizeHostBridgeClipboardText(',
) ||
!h5HostBridgeSource.includes(
"await requestNativeAppHostBridge<ClipboardReadTextResult>(\n 'clipboard.readText',",
) ||
!h5HostBridgeSource.includes(
'return clipboardText ?? false;',
)
) {
throw new Error(
'H5 HostBridge facade must normalize clipboard.readText results with the shared clipboard boundary',
);
}
if (
!h5HostBridgeSource.includes(
'const style = normalizeHostBridgeHapticsImpactStyle(params.style);',
@@ -4,6 +4,7 @@ import { afterEach, describe, expect, test, vi } from 'vitest';
import type { HostBridgeCapability } from '../../../packages/shared/src/contracts/hostBridge';
import {
HOST_BRIDGE_CLIPBOARD_TEXT_MAX_LENGTH,
HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS,
HOST_BRIDGE_SCANNER_TIMEOUT_MS,
HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS,
@@ -1485,6 +1486,42 @@ describe('hostBridge', () => {
});
});
test('原生 App HostBridge 读取剪贴板结果按共享清单归一化', async () => {
const oversizedText = 'a'.repeat(HOST_BRIDGE_CLIPBOARD_TEXT_MAX_LENGTH + 5);
const invoke = vi.fn(
async (_command: string, args?: Record<string, unknown>) => {
const request = (args as { request: { id: string } }).request;
return {
bridge: 'GenarrativeHostBridge',
version: 1,
id: request.id,
ok: true,
result: {
text:
invoke.mock.calls.length === 1
? 42
: oversizedText,
},
};
},
);
window.__TAURI__ = {
core: { invoke: asTauriInvoke(invoke) },
};
window.history.replaceState(
null,
'',
nativeAppPath(['clipboard.readText']),
);
await expect(readHostClipboardText()).resolves.toBe(false);
await expect(readHostClipboardText()).resolves.toEqual({
text: oversizedText.slice(0, HOST_BRIDGE_CLIPBOARD_TEXT_MAX_LENGTH),
});
expect(invoke).toHaveBeenCalledTimes(2);
});
test('原生 App 宿主不支持能力时回退到 H5 路径', async () => {
window.history.replaceState(null, '', nativeAppPath());
window.__TAURI__ = {
+2 -1
View File
@@ -889,13 +889,14 @@ export async function readHostClipboardText() {
}
try {
return normalizeHostBridgeClipboardText(
const clipboardText = normalizeHostBridgeClipboardText(
(
await requestNativeAppHostBridge<ClipboardReadTextResult>(
'clipboard.readText',
)
)?.text,
);
return clipboardText ?? false;
} catch {
return false;
}