diff --git a/src/commandline_frame.ts b/src/commandline_frame.ts index 7f54e586..70298720 100644 --- a/src/commandline_frame.ts +++ b/src/commandline_frame.ts @@ -80,6 +80,7 @@ const commandline_state = { */ isVisible: false, keyEvents: new Array(), + initialClInputValue: "", refresh_completions, state, } @@ -91,7 +92,6 @@ theme(document.querySelector(":root")) function resizeArea() { if (commandline_state.isVisible) { Messaging.messageOwnTab("commandline_content", "show") - Messaging.messageOwnTab("commandline_content", "focus") focus() } } @@ -162,9 +162,31 @@ const noblur = () => setTimeout(() => commandline_state.clInput.focus(), 0) /** @hidden **/ export function focus() { + function consumeBufferedPageKeys(bufferedPageKeys: string[]) { + const clInputStillFocused = window.document.activeElement === commandline_state.clInput; + logger.debug("stop_buffering_page_keys response received, bufferedPageKeys = ", bufferedPageKeys, + "clInputStillFocused = " + clInputStillFocused) + if (bufferedPageKeys.length !== 0) { + const currentClInputValue = commandline_state.clInput.value; + const initialClInputValue = commandline_state.initialClInputValue; + logger.debug("Consuming buffered page keys", bufferedPageKeys, + "initialClInputValue = " + initialClInputValue, + "currentClInputValue = " + currentClInputValue); + // Native events are assumed to be character keydown events, + // i.e. characters appended at the end of clInput. + commandline_state.clInput.value = + initialClInputValue + + bufferedPageKeys.join("") + + currentClInputValue.substring(initialClInputValue.length) + // Update completion. + clInputValueChanged() + } + } commandline_state.clInput.focus() commandline_state.clInput.removeEventListener("blur", noblur) commandline_state.clInput.addEventListener("blur", noblur) + logger.debug("commandline_frame clInput focus(), activeElement is clInput: " + (window.document.activeElement === commandline_state.clInput)) + Messaging.messageOwnTab("stop_buffering_page_keys").then(consumeBufferedPageKeys) } /** @hidden **/ @@ -188,6 +210,7 @@ commandline_state.clInput.addEventListener( "keydown", function (keyevent: KeyboardEvent) { if (!keyevent.isTrusted) return + logger.debug("commandline_frame clInput keydown event listener", keyevent) commandline_state.keyEvents.push(minimalKeyFromKeyboardEvent(keyevent)) const response = keyParser(commandline_state.keyEvents) if (response.isMatch) { @@ -264,6 +287,12 @@ export function refresh_completions(exstr) { let onInputPromise: Promise = Promise.resolve() /** @hidden **/ commandline_state.clInput.addEventListener("input", () => { + logger.debug("commandline_frame clInput input event listener") + clInputValueChanged(); +}) + +/** @hidden **/ +function clInputValueChanged() { const exstr = commandline_state.clInput.value contentState.current_cmdline = exstr contentState.cmdline_filter = "" @@ -282,7 +311,7 @@ commandline_state.clInput.addEventListener("input", () => { contentState.cmdline_filter = exstr }) }, 100) -}) +} /** @hidden **/ let cmdline_history_current = "" @@ -341,8 +370,10 @@ export function fillcmdline( trailspace = true, ffocus = true, ) { + logger.debug("commandline_frame fillcmdline(newcommand = " + newcommand + " trailspace = " + trailspace + " ffocus = " + ffocus + ")") if (trailspace) commandline_state.clInput.value = newcommand + " " else commandline_state.clInput.value = newcommand + commandline_state.initialClInputValue = commandline_state.clInput.value commandline_state.isVisible = true let result = Promise.resolve([]) // Focus is lost for some reason. @@ -373,6 +404,7 @@ export function editor_function(fn_name: keyof typeof tri_editor, ...args) { } Messaging.addListener("commandline_frame", Messaging.attributeCaller(SELF)) +logger.debug("Added commandline_frame message listener") commandline_state.fns = getCommandlineFns(commandline_state) Messaging.addListener( @@ -388,3 +420,5 @@ Messaging.addListener( ;(window as any).tri = Object.assign(window.tri || {}, { perfObserver: perf.listenForCounters(), }) + +Messaging.messageOwnTab("commandline_frame_ready_to_receive_messages") diff --git a/src/content/commandline_content.ts b/src/content/commandline_content.ts index e12c3e70..52d4fd8f 100644 --- a/src/content/commandline_content.ts +++ b/src/content/commandline_content.ts @@ -63,7 +63,7 @@ export function show(hidehover = false) { * * Inspired by VVimpulation: https://github.com/amedama41/vvimpulation/commit/53065d015d1e9a892496619b51be83771f57b3d5 */ - + logger.debug("commandline_content show()") if (hidehover) { const a = window.document.createElement("A") ;(a as any).href = "" @@ -96,18 +96,6 @@ export function hide() { } } -export function focus() { - try { - cmdline_iframe.focus() - } catch (e) { - // Note: We can't use cmdline_logger.error because it will try to log - // the error in the commandline, which will need to focus() it again, - // which will throw again... - // cmdline_logger.error(e) - console.error(e) - } -} - export function blur() { try { cmdline_iframe.blur() diff --git a/src/content/controller_content.ts b/src/content/controller_content.ts index 56491f6f..eff685dc 100644 --- a/src/content/controller_content.ts +++ b/src/content/controller_content.ts @@ -14,6 +14,7 @@ import * as hinting from "@src/content/hinting" import * as gobblemode from "@src/parsers/gobblemode" import * as generic from "@src/parsers/genericmode" import * as nmode from "@src/parsers/nmode" +import * as Messaging from "@src/lib/messaging"; const logger = new Logger("controller") @@ -64,6 +65,8 @@ class KeyCanceller { } push(ke: KeyboardEvent) { + ke.preventDefault() + ke.stopImmediatePropagation() this.keyPress.push(ke) this.keyUp.push(ke) } @@ -99,6 +102,26 @@ class KeyCanceller { export const canceller = new KeyCanceller() +let commandlineFrameReadyToReceiveMessages = false +Messaging.addListener("commandline_frame_ready_to_receive_messages", () => { + logger.debug("Received commandline_frame_ready_to_receive_messages") + commandlineFrameReadyToReceiveMessages = true +}) + +let mustBufferPageKeysForClInput = false +let bufferedPageKeys: string[] = [] +let bufferingPageKeysBeginTime: number +Messaging.addListener("stop_buffering_page_keys", (message, sender, sendResponse) => { + const bufferingDuration = performance.now() - bufferingPageKeysBeginTime; + logger.debug("stop_buffering_page_keys request received, responding with bufferedPageKeys = ", bufferedPageKeys + + " bufferingDuration = " + bufferingDuration + "ms") + sendResponse(Promise.resolve(bufferedPageKeys)) + // At this point, clInput is focused and the page cannot get any more keyboard events + // until it is refocused. + mustBufferPageKeysForClInput = false + bufferedPageKeys = [] +}) + /** Accepts keyevents, resolves them to maps, maps to exstrs, executes exstrs */ function* ParserController() { const parsers: { @@ -184,13 +207,17 @@ function* ParserController() { ) if (response.isMatch && keyevent instanceof KeyboardEvent) { - keyevent.preventDefault() - keyevent.stopImmediatePropagation() canceller.push(keyevent) } if (response.exstr) { exstr = response.exstr + if (exstr === "fillcmdline" || exstr === "fillcmdline_notrail") { + logger.debug("Starting buffering of page keys") + bufferingPageKeysBeginTime = performance.now() + mustBufferPageKeysForClInput = true + bufferedPageKeys = [] + } break } else { keyEvents = response.keys @@ -215,7 +242,32 @@ function* ParserController() { export const generator = ParserController() // var rather than let stops weirdness in repl. generator.next() -/** Feed keys to the ParserController */ +/** Feed keys to the ParserController, unless they should be buffered to be later fed to clInput */ export function acceptKey(keyevent: KeyboardEvent) { - return generator.next(keyevent) + function tryBufferingPageKeyForClInput(keyevent: KeyboardEvent) { + if (!mustBufferPageKeysForClInput) + return false; + const bufferingDuration = performance.now() - bufferingPageKeysBeginTime; + logger.debug("controller_content mustBufferPageKeysForClInput = " + mustBufferPageKeysForClInput + + " bufferingDuration = " + bufferingDuration + "ms"); + const isCharacterKey = keyevent.key.length == 1 + && !keyevent.metaKey && !keyevent.ctrlKey && !keyevent.altKey && !keyevent.metaKey; + if (isCharacterKey) { + bufferedPageKeys.push(keyevent.key); + logger.debug("Buffering page keys", bufferedPageKeys) + } + canceller.push(keyevent) + return true + } + if (!commandlineFrameReadyToReceiveMessages) { + // If the commandline frame cannot receive messages, the fillcmdline message sent by excmds.fillcmdline() to the + // commandline frame will never be received. As a result, commandline_frame.focus() will not be called, which + // in turn means that the stop_buffering_page_keys message will never be sent to the content/page process. + // If the content/page process starts buffering keys for clInput, but the stop_buffering_page_keys message is never received, + // it will keep buffering (and eating events) forever. + logger.debug("controller_content Ignoring key event ", keyevent, " since commandline frame is not yet ready to receive messages", keyevent) + return + } + if (!tryBufferingPageKeyForClInput(keyevent)) + return generator.next(keyevent) } diff --git a/src/excmds.ts b/src/excmds.ts index aee5da3e..0ad2a0aa 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -3892,11 +3892,11 @@ export function sleep(time_ms: number) { /** @hidden */ //#content export function showcmdline(focus = true) { + logger.debug("excmds showcmdline()") const hidehover = true CommandLineContent.show(hidehover) let done = Promise.resolve() if (focus) { - CommandLineContent.focus() done = Messaging.messageOwnTab("commandline_frame", "focus") } return done @@ -3912,16 +3912,17 @@ export function hidecmdline() { //#content export function fillcmdline(...strarr: string[]) { const str = strarr.join(" ") - showcmdline() - return Messaging.messageOwnTab("commandline_frame", "fillcmdline", [str]) + showcmdline(false) + logger.debug("excmds fillcmdline sending fillcmdline to commandline_frame") + return Messaging.messageOwnTab("commandline_frame", "fillcmdline", [str, true/*trailspace*/, true/*focus*/]) } /** Set the current value of the commandline to string *without* a trailing space */ //#content export function fillcmdline_notrail(...strarr: string[]) { const str = strarr.join(" ") - showcmdline() - return Messaging.messageOwnTab("commandline_frame", "fillcmdline", [str, false]) + showcmdline(false) + return Messaging.messageOwnTab("commandline_frame", "fillcmdline", [str, false/*trailspace*/, true/*focus*/]) } /** Show and fill the command line without focusing it */ diff --git a/src/lib/messaging.ts b/src/lib/messaging.ts index 3920810d..de2946a6 100644 --- a/src/lib/messaging.ts +++ b/src/lib/messaging.ts @@ -15,6 +15,8 @@ export type TabMessageType = | "lock" | "alive" | "tab_changes" + | "stop_buffering_page_keys" + | "commandline_frame_ready_to_receive_messages" export type NonTabMessageType = | "owntab_background" @@ -132,7 +134,7 @@ export async function message< /** Message the active tab of the currentWindow */ export async function messageActiveTab( type: TabMessageType, - command: string, + command?: string, args?: any[], ) { return messageTab(await activeTabId(), type, command, args) @@ -153,7 +155,7 @@ export async function messageTab( } let _ownTabId -export async function messageOwnTab(type: TabMessageType, command, args?) { +export async function messageOwnTab(type: TabMessageType, command?, args?) { if (_ownTabId === undefined) { _ownTabId = await ownTabId() } @@ -164,7 +166,7 @@ export async function messageOwnTab(type: TabMessageType, command, args?) { export async function messageAllTabs( type: TabMessageType, - command: string, + command?: string, args?: any[], ) { const responses = []