Merge pull request #4809 from petoncle/attempt-to-buffer-keyboard-events-until-commandline-opened

Reduce likelihood of losing characters while command line is opening
This commit is contained in:
Oliver Blanthorn 2023-12-16 19:01:00 +00:00 committed by GitHub
commit ec342c4839
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 104 additions and 27 deletions

View file

@ -80,6 +80,7 @@ const commandline_state = {
*/
isVisible: false,
keyEvents: new Array<MinimalKey>(),
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<any> = 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")

View file

@ -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()

View file

@ -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)
}

View file

@ -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 */

View file

@ -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 = []