refactor(mobile): extract the terminal message-bridge slice into a module

The script and the document end in the same slice, so the slice splits in two
at the point where the IIFE closes: the script half becomes a module, the
document half stays text. The byte pin proves the join is unchanged.

The second catch keeps its binding: it names the error and reports it.

Counts: qualified 1, scope declarations 0, rebindings 1, braced bodies 0,
unbound catches 1, number properties 0.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
Jinwoo-H
2026-09-20 09:27:52 -04:00
parent b113a85ad8
commit 8cb7381bc3
6 changed files with 115 additions and 4 deletions
@@ -98,3 +98,18 @@ export declare function isClickMouseTrackingMode(mode: string): boolean
/** `write-queue`: the trailing bytes a DECSET scan must carry into the next chunk. */
export declare function extractMouseModeScanTail(input: string): string
/** `host-message-router`: routes one decoded host message. */
export declare function handleMsg(msg: unknown): void
/** `terminal-init-and-write`: reports an engine failure to the host. */
export declare function reportEngineError(summary: string, cause: unknown, fatal: unknown): void
/** `terminal-init-and-write`: re-fits the row count to the current viewport. */
export declare function adjustRowsForViewport(): void
/** `smooth-scroll-and-cell-geometry`: clamps the pan offsets to the scaled surface. */
export declare function clampPan(): void
/** `smooth-scroll-and-cell-geometry`: writes the pan and scale onto the surface transform. */
export declare function updateTransform(): void
@@ -124,6 +124,8 @@ export type TerminalDocumentScope = {
initialOscLinkRowOffset: number
/** `runtime-state`: the escape byte every report is prefixed with. */
ESC: string
/** `terminal-init-and-write`: whether the terminal has ever reached ready. */
everReady: boolean
/** `runtime-state`: the C1 form of the control sequence introducer. */
C1_CSI: string
/** `runtime-state`: the tail of the last chunk, in case a DECSET straddles two writes. */
@@ -210,6 +212,7 @@ export function createTerminalDocumentScope(): TerminalDocumentScope {
initialOscLinks: [],
initialOscLinkRowOffset: 0,
ESC: String.fromCharCode(27),
everReady: false,
C1_CSI: String.fromCharCode(155),
mouseModeScanTail: '',
trackedMouseTrackingMode: 'none',
@@ -0,0 +1,26 @@
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { emitTerminalDocumentModule } from '../../../scripts/build-terminal-document-script.mjs'
import { TERMINAL_HTML_MESSAGE_BRIDGE } from '../terminal-webview-html/message-bridge-and-document-close'
import { compareTerminalDocumentScripts } from './terminal-document-equivalence.test-support'
const modulePath = fileURLToPath(new URL('./message-bridge.ts', import.meta.url))
describe('the message-bridge module', () => {
it('emits the script the document carries, modulo the five normalisations', async () => {
const emitted = await emitTerminalDocumentModule(modulePath)
expect(compareTerminalDocumentScripts(TERMINAL_HTML_MESSAGE_BRIDGE, emitted, 'scope')).toEqual({
equivalent: true,
normalisations: {
// Only the ready flag, read to decide whether a failed init is fatal.
qualifiedReferences: 1,
scopeFieldDeclarations: 0,
rebindings: 1,
bracedBodies: 0,
// The parse guard. The second catch names its error and reports it, so it keeps its binding.
unboundCatches: 1,
numberProperties: 0
}
})
})
})
@@ -0,0 +1,60 @@
import {
adjustRowsForViewport,
applyFitScale,
clampPan,
handleMsg,
notify,
reportEngineError,
repositionOverlay,
updateTransform
} from './document-externals'
import { scope } from './document-scope'
declare global {
interface Window {
Terminal?: unknown
}
}
/** The decoded host message; only its type is read here, by the error reporter. */
type TerminalHostMessage = { type?: unknown } | undefined
export function handleIncomingMessage(e: Event & { data?: TerminalHostMessage | string }) {
let msg: TerminalHostMessage
try {
msg = typeof e.data === 'string' ? JSON.parse(e.data) : e.data
} catch {
return
}
try {
handleMsg(msg)
} catch (ex) {
reportEngineError(
msg && msg.type === 'init' ? 'terminal init failed' : 'terminal message failed',
ex,
msg && msg.type === 'init' && !scope.everReady
)
}
}
window.addEventListener('message', handleIncomingMessage)
document.addEventListener('message', handleIncomingMessage)
window.addEventListener('resize', function () {
// Why: viewport changed (keyboard open/close, orientation, RN container
// size update). Re-fit so the scale matches the new vpWidth — without
// this, opening the keyboard leaves the terminal at the old scale even
// though there's now less vertical room and the fit ratio may differ.
applyFitScale('window-resize')
adjustRowsForViewport()
repositionOverlay()
clampPan()
updateTransform()
})
if (window.Terminal) {
notify({ type: 'web-ready' })
} else {
reportEngineError('terminal engine missing', 'xterm failed to load', true)
}
+6 -2
View File
@@ -11,7 +11,10 @@ import { TERMINAL_HTML_MOUSE_REPORT_AND_SCROLL_ROUTING } from './terminal-webvie
import { TERMINAL_HTML_SMOOTH_SCROLL_AND_CELL_GEOMETRY } from './terminal-webview-html/smooth-scroll-and-cell-geometry'
import { TERMINAL_HTML_SELECTION_OVERLAY } from './terminal-webview-html/selection-overlay'
import { TERMINAL_HTML_SURFACE_TOUCH_GESTURES } from './terminal-webview-html/surface-touch-gestures'
import { TERMINAL_HTML_MESSAGE_BRIDGE_AND_DOCUMENT_CLOSE } from './terminal-webview-html/message-bridge-and-document-close'
import {
TERMINAL_HTML_DOCUMENT_CLOSE,
TERMINAL_HTML_MESSAGE_BRIDGE
} from './terminal-webview-html/message-bridge-and-document-close'
export { MOBILE_TERMINAL_CARET_OPTIONS } from './terminal-webview-html/theme'
@@ -32,7 +35,8 @@ export const XTERM_HTML = [
TERMINAL_HTML_SMOOTH_SCROLL_AND_CELL_GEOMETRY,
TERMINAL_HTML_SELECTION_OVERLAY,
TERMINAL_HTML_SURFACE_TOUCH_GESTURES,
TERMINAL_HTML_MESSAGE_BRIDGE_AND_DOCUMENT_CLOSE
TERMINAL_HTML_MESSAGE_BRIDGE,
TERMINAL_HTML_DOCUMENT_CLOSE
].join('')
export const XTERM_WEBVIEW_SOURCE = { html: XTERM_HTML }
@@ -1,4 +1,5 @@
export const TERMINAL_HTML_MESSAGE_BRIDGE_AND_DOCUMENT_CLOSE = ` function handleIncomingMessage(e) {
// The script ends here and the document ends below: the boundary is where the IIFE closes.
export const TERMINAL_HTML_MESSAGE_BRIDGE = ` function handleIncomingMessage(e) {
var msg;
try {
msg = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
@@ -37,7 +38,9 @@ export const TERMINAL_HTML_MESSAGE_BRIDGE_AND_DOCUMENT_CLOSE = ` function handl
} else {
reportEngineError('terminal engine missing', 'xterm failed to load', true);
}
})();
`
export const TERMINAL_HTML_DOCUMENT_CLOSE = `})();
</script>
</body>
</html>`