From 0878a8fc5cf0df3783673b1c579af1605d861260 Mon Sep 17 00:00:00 2001 From: Gaubee Date: Sun, 15 Feb 2026 22:23:51 +0800 Subject: [PATCH] fix(dweb): enable keyboard overlay to prevent viewport resize --- src/lib/dweb-keyboard-overlay.test.ts | 59 +++++++++++++++++++++++++++ src/lib/dweb-keyboard-overlay.ts | 48 ++++++++++++++++++++++ src/service-main.ts | 4 ++ 3 files changed, 111 insertions(+) create mode 100644 src/lib/dweb-keyboard-overlay.test.ts create mode 100644 src/lib/dweb-keyboard-overlay.ts diff --git a/src/lib/dweb-keyboard-overlay.test.ts b/src/lib/dweb-keyboard-overlay.test.ts new file mode 100644 index 000000000..b3748d3b6 --- /dev/null +++ b/src/lib/dweb-keyboard-overlay.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, it, vi } from 'vitest' +import { applyDwebKeyboardOverlay, type DwebPluginsModule } from './dweb-keyboard-overlay' + +describe('dweb keyboard overlay', () => { + it('skips when current environment is not dweb', async () => { + const loadPlugins = vi.fn<() => Promise>() + + const result = await applyDwebKeyboardOverlay({ + isDweb: () => false, + loadPlugins, + }) + + expect(result).toBe(false) + expect(loadPlugins).not.toHaveBeenCalled() + }) + + it('applies overlay in dweb environment', async () => { + const setOverlay = vi.fn<(overlay: boolean) => Promise>().mockResolvedValue() + const loadPlugins = vi.fn<() => Promise>().mockResolvedValue({ + virtualKeyboardPlugin: { setOverlay }, + }) + + const result = await applyDwebKeyboardOverlay({ + isDweb: () => true, + loadPlugins, + }) + + expect(result).toBe(true) + expect(loadPlugins).toHaveBeenCalledTimes(1) + expect(setOverlay).toHaveBeenCalledWith(true) + }) + + it('returns false when virtual keyboard plugin is unavailable', async () => { + const loadPlugins = vi.fn<() => Promise>().mockResolvedValue({}) + + const result = await applyDwebKeyboardOverlay({ + isDweb: () => true, + loadPlugins, + }) + + expect(result).toBe(false) + expect(loadPlugins).toHaveBeenCalledTimes(1) + }) + + it('returns false when plugin loading fails', async () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + const loadPlugins = vi.fn<() => Promise>().mockRejectedValue(new Error('load-failed')) + + const result = await applyDwebKeyboardOverlay({ + isDweb: () => true, + loadPlugins, + }) + + expect(result).toBe(false) + expect(warnSpy).toHaveBeenCalledTimes(1) + + warnSpy.mockRestore() + }) +}) diff --git a/src/lib/dweb-keyboard-overlay.ts b/src/lib/dweb-keyboard-overlay.ts new file mode 100644 index 000000000..96b52c484 --- /dev/null +++ b/src/lib/dweb-keyboard-overlay.ts @@ -0,0 +1,48 @@ +import { isDwebEnvironment } from './crypto/secure-storage' + +export interface DwebVirtualKeyboardPlugin { + setOverlay(overlay: boolean): Promise +} + +export interface DwebPluginsModule { + virtualKeyboardPlugin?: DwebVirtualKeyboardPlugin +} + +export interface ApplyDwebKeyboardOverlayOptions { + isDweb?: () => boolean + loadPlugins?: () => Promise +} + +async function defaultLoadPlugins(): Promise { + const moduleName = '@plaoc/plugins' + const module = await import(/* @vite-ignore */ moduleName) + return module as DwebPluginsModule +} + +/** + * 在 DWEB 环境启用键盘 overlay,避免输入法弹出导致 document 发生 resize。 + */ +export async function applyDwebKeyboardOverlay( + options: ApplyDwebKeyboardOverlayOptions = {}, +): Promise { + const isDweb = options.isDweb ?? isDwebEnvironment + const loadPlugins = options.loadPlugins ?? defaultLoadPlugins + + if (!isDweb()) { + return false + } + + try { + const plugins = await loadPlugins() + const virtualKeyboardPlugin = plugins.virtualKeyboardPlugin + if (!virtualKeyboardPlugin || typeof virtualKeyboardPlugin.setOverlay !== 'function') { + return false + } + + await virtualKeyboardPlugin.setOverlay(true) + return true + } catch (error) { + console.warn('[dweb-keyboard-overlay] apply failed', error) + return false + } +} diff --git a/src/service-main.ts b/src/service-main.ts index 527682f87..ab7c46c88 100644 --- a/src/service-main.ts +++ b/src/service-main.ts @@ -3,6 +3,7 @@ import { installLegacyAuthorizeHashRewriter, rewriteLegacyAuthorizeHashInPlace, } from '@/services/authorize/deep-link' +import { applyDwebKeyboardOverlay } from '@/lib/dweb-keyboard-overlay' export type ServiceMainCleanup = () => void @@ -17,6 +18,9 @@ export function startServiceMain(): ServiceMainCleanup { // Normalize legacy mpay-style authorize deep links before Stackflow reads URL. rewriteLegacyAuthorizeHashInPlace() + // DWEB: keep viewport stable when soft keyboard appears. + void applyDwebKeyboardOverlay() + // Initialize preference side effects (i18n + RTL) as early as possible. preferencesActions.initialize()