mirror of
https://github.com/vale981/tridactyl
synced 2025-03-04 17:11:40 -05:00
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:
commit
ec342c4839
5 changed files with 104 additions and 27 deletions
|
@ -80,6 +80,7 @@ const commandline_state = {
|
||||||
*/
|
*/
|
||||||
isVisible: false,
|
isVisible: false,
|
||||||
keyEvents: new Array<MinimalKey>(),
|
keyEvents: new Array<MinimalKey>(),
|
||||||
|
initialClInputValue: "",
|
||||||
refresh_completions,
|
refresh_completions,
|
||||||
state,
|
state,
|
||||||
}
|
}
|
||||||
|
@ -91,7 +92,6 @@ theme(document.querySelector(":root"))
|
||||||
function resizeArea() {
|
function resizeArea() {
|
||||||
if (commandline_state.isVisible) {
|
if (commandline_state.isVisible) {
|
||||||
Messaging.messageOwnTab("commandline_content", "show")
|
Messaging.messageOwnTab("commandline_content", "show")
|
||||||
Messaging.messageOwnTab("commandline_content", "focus")
|
|
||||||
focus()
|
focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,9 +162,31 @@ const noblur = () => setTimeout(() => commandline_state.clInput.focus(), 0)
|
||||||
|
|
||||||
/** @hidden **/
|
/** @hidden **/
|
||||||
export function focus() {
|
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.focus()
|
||||||
commandline_state.clInput.removeEventListener("blur", noblur)
|
commandline_state.clInput.removeEventListener("blur", noblur)
|
||||||
commandline_state.clInput.addEventListener("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 **/
|
/** @hidden **/
|
||||||
|
@ -188,6 +210,7 @@ commandline_state.clInput.addEventListener(
|
||||||
"keydown",
|
"keydown",
|
||||||
function (keyevent: KeyboardEvent) {
|
function (keyevent: KeyboardEvent) {
|
||||||
if (!keyevent.isTrusted) return
|
if (!keyevent.isTrusted) return
|
||||||
|
logger.debug("commandline_frame clInput keydown event listener", keyevent)
|
||||||
commandline_state.keyEvents.push(minimalKeyFromKeyboardEvent(keyevent))
|
commandline_state.keyEvents.push(minimalKeyFromKeyboardEvent(keyevent))
|
||||||
const response = keyParser(commandline_state.keyEvents)
|
const response = keyParser(commandline_state.keyEvents)
|
||||||
if (response.isMatch) {
|
if (response.isMatch) {
|
||||||
|
@ -264,6 +287,12 @@ export function refresh_completions(exstr) {
|
||||||
let onInputPromise: Promise<any> = Promise.resolve()
|
let onInputPromise: Promise<any> = Promise.resolve()
|
||||||
/** @hidden **/
|
/** @hidden **/
|
||||||
commandline_state.clInput.addEventListener("input", () => {
|
commandline_state.clInput.addEventListener("input", () => {
|
||||||
|
logger.debug("commandline_frame clInput input event listener")
|
||||||
|
clInputValueChanged();
|
||||||
|
})
|
||||||
|
|
||||||
|
/** @hidden **/
|
||||||
|
function clInputValueChanged() {
|
||||||
const exstr = commandline_state.clInput.value
|
const exstr = commandline_state.clInput.value
|
||||||
contentState.current_cmdline = exstr
|
contentState.current_cmdline = exstr
|
||||||
contentState.cmdline_filter = ""
|
contentState.cmdline_filter = ""
|
||||||
|
@ -282,7 +311,7 @@ commandline_state.clInput.addEventListener("input", () => {
|
||||||
contentState.cmdline_filter = exstr
|
contentState.cmdline_filter = exstr
|
||||||
})
|
})
|
||||||
}, 100)
|
}, 100)
|
||||||
})
|
}
|
||||||
|
|
||||||
/** @hidden **/
|
/** @hidden **/
|
||||||
let cmdline_history_current = ""
|
let cmdline_history_current = ""
|
||||||
|
@ -341,8 +370,10 @@ export function fillcmdline(
|
||||||
trailspace = true,
|
trailspace = true,
|
||||||
ffocus = true,
|
ffocus = true,
|
||||||
) {
|
) {
|
||||||
|
logger.debug("commandline_frame fillcmdline(newcommand = " + newcommand + " trailspace = " + trailspace + " ffocus = " + ffocus + ")")
|
||||||
if (trailspace) commandline_state.clInput.value = newcommand + " "
|
if (trailspace) commandline_state.clInput.value = newcommand + " "
|
||||||
else commandline_state.clInput.value = newcommand
|
else commandline_state.clInput.value = newcommand
|
||||||
|
commandline_state.initialClInputValue = commandline_state.clInput.value
|
||||||
commandline_state.isVisible = true
|
commandline_state.isVisible = true
|
||||||
let result = Promise.resolve([])
|
let result = Promise.resolve([])
|
||||||
// Focus is lost for some reason.
|
// 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))
|
Messaging.addListener("commandline_frame", Messaging.attributeCaller(SELF))
|
||||||
|
logger.debug("Added commandline_frame message listener")
|
||||||
|
|
||||||
commandline_state.fns = getCommandlineFns(commandline_state)
|
commandline_state.fns = getCommandlineFns(commandline_state)
|
||||||
Messaging.addListener(
|
Messaging.addListener(
|
||||||
|
@ -388,3 +420,5 @@ Messaging.addListener(
|
||||||
;(window as any).tri = Object.assign(window.tri || {}, {
|
;(window as any).tri = Object.assign(window.tri || {}, {
|
||||||
perfObserver: perf.listenForCounters(),
|
perfObserver: perf.listenForCounters(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Messaging.messageOwnTab("commandline_frame_ready_to_receive_messages")
|
||||||
|
|
|
@ -63,7 +63,7 @@ export function show(hidehover = false) {
|
||||||
*
|
*
|
||||||
* Inspired by VVimpulation: https://github.com/amedama41/vvimpulation/commit/53065d015d1e9a892496619b51be83771f57b3d5
|
* Inspired by VVimpulation: https://github.com/amedama41/vvimpulation/commit/53065d015d1e9a892496619b51be83771f57b3d5
|
||||||
*/
|
*/
|
||||||
|
logger.debug("commandline_content show()")
|
||||||
if (hidehover) {
|
if (hidehover) {
|
||||||
const a = window.document.createElement("A")
|
const a = window.document.createElement("A")
|
||||||
;(a as any).href = ""
|
;(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() {
|
export function blur() {
|
||||||
try {
|
try {
|
||||||
cmdline_iframe.blur()
|
cmdline_iframe.blur()
|
||||||
|
|
|
@ -14,6 +14,7 @@ import * as hinting from "@src/content/hinting"
|
||||||
import * as gobblemode from "@src/parsers/gobblemode"
|
import * as gobblemode from "@src/parsers/gobblemode"
|
||||||
import * as generic from "@src/parsers/genericmode"
|
import * as generic from "@src/parsers/genericmode"
|
||||||
import * as nmode from "@src/parsers/nmode"
|
import * as nmode from "@src/parsers/nmode"
|
||||||
|
import * as Messaging from "@src/lib/messaging";
|
||||||
|
|
||||||
const logger = new Logger("controller")
|
const logger = new Logger("controller")
|
||||||
|
|
||||||
|
@ -64,6 +65,8 @@ class KeyCanceller {
|
||||||
}
|
}
|
||||||
|
|
||||||
push(ke: KeyboardEvent) {
|
push(ke: KeyboardEvent) {
|
||||||
|
ke.preventDefault()
|
||||||
|
ke.stopImmediatePropagation()
|
||||||
this.keyPress.push(ke)
|
this.keyPress.push(ke)
|
||||||
this.keyUp.push(ke)
|
this.keyUp.push(ke)
|
||||||
}
|
}
|
||||||
|
@ -99,6 +102,26 @@ class KeyCanceller {
|
||||||
|
|
||||||
export const canceller = new 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 */
|
/** Accepts keyevents, resolves them to maps, maps to exstrs, executes exstrs */
|
||||||
function* ParserController() {
|
function* ParserController() {
|
||||||
const parsers: {
|
const parsers: {
|
||||||
|
@ -184,13 +207,17 @@ function* ParserController() {
|
||||||
)
|
)
|
||||||
|
|
||||||
if (response.isMatch && keyevent instanceof KeyboardEvent) {
|
if (response.isMatch && keyevent instanceof KeyboardEvent) {
|
||||||
keyevent.preventDefault()
|
|
||||||
keyevent.stopImmediatePropagation()
|
|
||||||
canceller.push(keyevent)
|
canceller.push(keyevent)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.exstr) {
|
if (response.exstr) {
|
||||||
exstr = 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
|
break
|
||||||
} else {
|
} else {
|
||||||
keyEvents = response.keys
|
keyEvents = response.keys
|
||||||
|
@ -215,7 +242,32 @@ function* ParserController() {
|
||||||
export const generator = ParserController() // var rather than let stops weirdness in repl.
|
export const generator = ParserController() // var rather than let stops weirdness in repl.
|
||||||
generator.next()
|
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) {
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3892,11 +3892,11 @@ export function sleep(time_ms: number) {
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
//#content
|
//#content
|
||||||
export function showcmdline(focus = true) {
|
export function showcmdline(focus = true) {
|
||||||
|
logger.debug("excmds showcmdline()")
|
||||||
const hidehover = true
|
const hidehover = true
|
||||||
CommandLineContent.show(hidehover)
|
CommandLineContent.show(hidehover)
|
||||||
let done = Promise.resolve()
|
let done = Promise.resolve()
|
||||||
if (focus) {
|
if (focus) {
|
||||||
CommandLineContent.focus()
|
|
||||||
done = Messaging.messageOwnTab("commandline_frame", "focus")
|
done = Messaging.messageOwnTab("commandline_frame", "focus")
|
||||||
}
|
}
|
||||||
return done
|
return done
|
||||||
|
@ -3912,16 +3912,17 @@ export function hidecmdline() {
|
||||||
//#content
|
//#content
|
||||||
export function fillcmdline(...strarr: string[]) {
|
export function fillcmdline(...strarr: string[]) {
|
||||||
const str = strarr.join(" ")
|
const str = strarr.join(" ")
|
||||||
showcmdline()
|
showcmdline(false)
|
||||||
return Messaging.messageOwnTab("commandline_frame", "fillcmdline", [str])
|
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 */
|
/** Set the current value of the commandline to string *without* a trailing space */
|
||||||
//#content
|
//#content
|
||||||
export function fillcmdline_notrail(...strarr: string[]) {
|
export function fillcmdline_notrail(...strarr: string[]) {
|
||||||
const str = strarr.join(" ")
|
const str = strarr.join(" ")
|
||||||
showcmdline()
|
showcmdline(false)
|
||||||
return Messaging.messageOwnTab("commandline_frame", "fillcmdline", [str, false])
|
return Messaging.messageOwnTab("commandline_frame", "fillcmdline", [str, false/*trailspace*/, true/*focus*/])
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Show and fill the command line without focusing it */
|
/** Show and fill the command line without focusing it */
|
||||||
|
|
|
@ -15,6 +15,8 @@ export type TabMessageType =
|
||||||
| "lock"
|
| "lock"
|
||||||
| "alive"
|
| "alive"
|
||||||
| "tab_changes"
|
| "tab_changes"
|
||||||
|
| "stop_buffering_page_keys"
|
||||||
|
| "commandline_frame_ready_to_receive_messages"
|
||||||
|
|
||||||
export type NonTabMessageType =
|
export type NonTabMessageType =
|
||||||
| "owntab_background"
|
| "owntab_background"
|
||||||
|
@ -132,7 +134,7 @@ export async function message<
|
||||||
/** Message the active tab of the currentWindow */
|
/** Message the active tab of the currentWindow */
|
||||||
export async function messageActiveTab(
|
export async function messageActiveTab(
|
||||||
type: TabMessageType,
|
type: TabMessageType,
|
||||||
command: string,
|
command?: string,
|
||||||
args?: any[],
|
args?: any[],
|
||||||
) {
|
) {
|
||||||
return messageTab(await activeTabId(), type, command, args)
|
return messageTab(await activeTabId(), type, command, args)
|
||||||
|
@ -153,7 +155,7 @@ export async function messageTab(
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ownTabId
|
let _ownTabId
|
||||||
export async function messageOwnTab(type: TabMessageType, command, args?) {
|
export async function messageOwnTab(type: TabMessageType, command?, args?) {
|
||||||
if (_ownTabId === undefined) {
|
if (_ownTabId === undefined) {
|
||||||
_ownTabId = await ownTabId()
|
_ownTabId = await ownTabId()
|
||||||
}
|
}
|
||||||
|
@ -164,7 +166,7 @@ export async function messageOwnTab(type: TabMessageType, command, args?) {
|
||||||
|
|
||||||
export async function messageAllTabs(
|
export async function messageAllTabs(
|
||||||
type: TabMessageType,
|
type: TabMessageType,
|
||||||
command: string,
|
command?: string,
|
||||||
args?: any[],
|
args?: any[],
|
||||||
) {
|
) {
|
||||||
const responses = []
|
const responses = []
|
||||||
|
|
Loading…
Add table
Reference in a new issue