Skip to content

Commit 277480a

Browse files
authored
fix(dweb): enable keyboard overlay to prevent viewport resize (#452)
1 parent 71ec17f commit 277480a

3 files changed

Lines changed: 111 additions & 0 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { describe, expect, it, vi } from 'vitest'
2+
import { applyDwebKeyboardOverlay, type DwebPluginsModule } from './dweb-keyboard-overlay'
3+
4+
describe('dweb keyboard overlay', () => {
5+
it('skips when current environment is not dweb', async () => {
6+
const loadPlugins = vi.fn<() => Promise<DwebPluginsModule>>()
7+
8+
const result = await applyDwebKeyboardOverlay({
9+
isDweb: () => false,
10+
loadPlugins,
11+
})
12+
13+
expect(result).toBe(false)
14+
expect(loadPlugins).not.toHaveBeenCalled()
15+
})
16+
17+
it('applies overlay in dweb environment', async () => {
18+
const setOverlay = vi.fn<(overlay: boolean) => Promise<void>>().mockResolvedValue()
19+
const loadPlugins = vi.fn<() => Promise<DwebPluginsModule>>().mockResolvedValue({
20+
virtualKeyboardPlugin: { setOverlay },
21+
})
22+
23+
const result = await applyDwebKeyboardOverlay({
24+
isDweb: () => true,
25+
loadPlugins,
26+
})
27+
28+
expect(result).toBe(true)
29+
expect(loadPlugins).toHaveBeenCalledTimes(1)
30+
expect(setOverlay).toHaveBeenCalledWith(true)
31+
})
32+
33+
it('returns false when virtual keyboard plugin is unavailable', async () => {
34+
const loadPlugins = vi.fn<() => Promise<DwebPluginsModule>>().mockResolvedValue({})
35+
36+
const result = await applyDwebKeyboardOverlay({
37+
isDweb: () => true,
38+
loadPlugins,
39+
})
40+
41+
expect(result).toBe(false)
42+
expect(loadPlugins).toHaveBeenCalledTimes(1)
43+
})
44+
45+
it('returns false when plugin loading fails', async () => {
46+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
47+
const loadPlugins = vi.fn<() => Promise<DwebPluginsModule>>().mockRejectedValue(new Error('load-failed'))
48+
49+
const result = await applyDwebKeyboardOverlay({
50+
isDweb: () => true,
51+
loadPlugins,
52+
})
53+
54+
expect(result).toBe(false)
55+
expect(warnSpy).toHaveBeenCalledTimes(1)
56+
57+
warnSpy.mockRestore()
58+
})
59+
})

src/lib/dweb-keyboard-overlay.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { isDwebEnvironment } from './crypto/secure-storage'
2+
3+
export interface DwebVirtualKeyboardPlugin {
4+
setOverlay(overlay: boolean): Promise<unknown>
5+
}
6+
7+
export interface DwebPluginsModule {
8+
virtualKeyboardPlugin?: DwebVirtualKeyboardPlugin
9+
}
10+
11+
export interface ApplyDwebKeyboardOverlayOptions {
12+
isDweb?: () => boolean
13+
loadPlugins?: () => Promise<DwebPluginsModule>
14+
}
15+
16+
async function defaultLoadPlugins(): Promise<DwebPluginsModule> {
17+
const moduleName = '@plaoc/plugins'
18+
const module = await import(/* @vite-ignore */ moduleName)
19+
return module as DwebPluginsModule
20+
}
21+
22+
/**
23+
* 在 DWEB 环境启用键盘 overlay,避免输入法弹出导致 document 发生 resize。
24+
*/
25+
export async function applyDwebKeyboardOverlay(
26+
options: ApplyDwebKeyboardOverlayOptions = {},
27+
): Promise<boolean> {
28+
const isDweb = options.isDweb ?? isDwebEnvironment
29+
const loadPlugins = options.loadPlugins ?? defaultLoadPlugins
30+
31+
if (!isDweb()) {
32+
return false
33+
}
34+
35+
try {
36+
const plugins = await loadPlugins()
37+
const virtualKeyboardPlugin = plugins.virtualKeyboardPlugin
38+
if (!virtualKeyboardPlugin || typeof virtualKeyboardPlugin.setOverlay !== 'function') {
39+
return false
40+
}
41+
42+
await virtualKeyboardPlugin.setOverlay(true)
43+
return true
44+
} catch (error) {
45+
console.warn('[dweb-keyboard-overlay] apply failed', error)
46+
return false
47+
}
48+
}

src/service-main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
installLegacyAuthorizeHashRewriter,
44
rewriteLegacyAuthorizeHashInPlace,
55
} from '@/services/authorize/deep-link'
6+
import { applyDwebKeyboardOverlay } from '@/lib/dweb-keyboard-overlay'
67

78
export type ServiceMainCleanup = () => void
89

@@ -17,6 +18,9 @@ export function startServiceMain(): ServiceMainCleanup {
1718
// Normalize legacy mpay-style authorize deep links before Stackflow reads URL.
1819
rewriteLegacyAuthorizeHashInPlace()
1920

21+
// DWEB: keep viewport stable when soft keyboard appears.
22+
void applyDwebKeyboardOverlay()
23+
2024
// Initialize preference side effects (i18n + RTL) as early as possible.
2125
preferencesActions.initialize()
2226

0 commit comments

Comments
 (0)