Merge pull request #7543 from keymanapp/fix/web/oskEvent-keyboard-synch

fix(web): ensures keyboard does not change underneath OSK keystroke
This commit is contained in:
Joshua Horton 2022-10-26 14:13:54 +07:00 • committed by GitHub
commit a060ccb6fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 58 additions and 9 deletions

View file

@ -89,6 +89,32 @@ namespace com.keyman.text {
* all matched keyboard rules.
*/
processKeyEvent(keyEvent: KeyEvent, outputTarget: OutputTarget): RuleBehavior {
const kbdMismatch = keyEvent.srcKeyboard && this.activeKeyboard != keyEvent.srcKeyboard;
const trueActiveKeyboard = this.activeKeyboard;
try {
if(kbdMismatch) {
// This will force-reset the context per our setter above.
this.activeKeyboard = keyEvent.srcKeyboard;
}
return this._processKeyEvent(keyEvent, outputTarget);
} finally {
if(kbdMismatch) {
// Restore our "current" activeKeyboard to its setting before the mismatching KeyEvent.
this.activeKeyboard = trueActiveKeyboard;
}
}
}
/**
* Acts as the core of `processKeyEvent` once we're comfortable asserting that the incoming
* keystroke matches the current `activeKeyboard`.
* @param keyEvent
* @param outputTarget
* @returns
*/
private _processKeyEvent(keyEvent: KeyEvent, outputTarget: OutputTarget): RuleBehavior {
let formFactor = keyEvent.device.formFactor;
let fromOSK = keyEvent.isSynthetic;

View file

@ -21,6 +21,16 @@ namespace com.keyman.text {
kbdLayer?: string; // The virtual keyboard's active layer
kNextLayer?: string;
/**
* Marks the active keyboard at the time that this KeyEvent was generated by the user.
*
* Note: this is NOT equivalent to the active keyboard at the time that the event handler begins
* processing! It should be set via closure (or similar) on the event handler that can 100%
* guarantee that the keyboard instance known to the handler has not changed during JS execution
* since the user's interaction that raised the event.
*/
srcKeyboard?: keyboards.Keyboard;
// Holds relevant event properties leading to construction of this KeyEvent.
source?: any; // Technically, KeyEvent|MouseEvent|Touch - but those are DOM types that must be kept out of headless mode.
// Holds a generated fat-finger distribution (when appropriate)

View file

@ -6,7 +6,7 @@ namespace com.keyman.osk {
* Note that the test-case oriented 'recorder' stubs this method to facilitate OSK-based input
* recording for use in test cases. If changing this function, please ensure the recorder is
* not affected.
*
*
* @param {Object} e element touched (or clicked)
*/
static clickKey(e: osk.KeyElement, input?: InputEventCoordinate) {
@ -27,7 +27,7 @@ namespace com.keyman.osk {
// Handle any DOM state management related to click inputs.
let outputTarget = dom.Utils.getOutputTarget(Lelem);
keyman.domManager.initActiveElement(Lelem);
// Clear any cached codepoint data; we can rebuild it if it's unchanged.
outputTarget.invalidateSelection();
// Deadkey matching continues to be troublesome.
@ -51,8 +51,8 @@ namespace com.keyman.osk {
}
}
// Serves to hold DOM-dependent code that affects both 'native' and 'embedded' mode OSK use
// after the KeyEvent object has been properly instantiated. This should help catch any
// Serves to hold DOM-dependent code that affects both 'native' and 'embedded' mode OSK use
// after the KeyEvent object has been properly instantiated. This should help catch any
// mutual last-minute DOM-side interactions before passing control to the processor... such as
// the UI-control command keys as seen below.
static handleClick(Lkc: text.KeyEvent, outputTarget: text.OutputTarget, e: osk.KeyElement) {

View file

@ -102,6 +102,9 @@ namespace com.keyman.osk {
// Multi-tap gesture management
pendingMultiTap: PendingMultiTap;
// The keyboard object corresponding to this VisualKeyboard.
private layoutKeyboard: keyboards.Keyboard;
get layerId(): string {
return this._layerId;
}
@ -172,19 +175,19 @@ namespace com.keyman.osk {
// Now to build the actual layout.
const formFactor = device.formFactor as utils.FormFactor;
let layoutKeyboard = keyboard;
if (!layoutKeyboard) {
this.layoutKeyboard = keyboard;
if (!this.layoutKeyboard) {
// May occasionally be null in embedded contexts; have seen this when iOS engine sets
// keyboard height during change of keyboards.
layoutKeyboard = new keyboards.Keyboard(null);
this.layoutKeyboard = new keyboards.Keyboard(null);
}
this.layerGroup = new OSKLayerGroup(this, layoutKeyboard, formFactor);
this.layerGroup = new OSKLayerGroup(this, this.layoutKeyboard, formFactor);
// Now that we've properly processed the keyboard's layout, mark it as calibrated.
// TODO: drop the whole 'calibration' thing. The newer layout system supersedes the
// need for it. (Is no longer really used, so the drop ought be clean.)
layoutKeyboard.markLayoutCalibrated(formFactor);
this.layoutKeyboard.markLayoutCalibrated(formFactor);
// Append the OSK layer group container element to the containing element
//osk.keyMap = divLayerContainer;
@ -1005,6 +1008,16 @@ namespace com.keyman.osk {
// First check the virtual key, and process shift, control, alt or function keys
let Lkc = keySpec.constructKeyEvent(core.keyboardProcessor, this.device);
/* In case of "fun" edge cases caused by JS's single-threadedness & event processing queue.
*
* Should a touch occur on an OSK key during active JS execution that results in a change
* of the active keyboard, it's possible for an OSK key to be evaluated against an
* unexpected, non-matching keyboard - one that could even be `null`!
*
* So, we mark the keyboard backing the OSK as the 'correct' keyboard for this key.
*/
Lkc.srcKeyboard = this.layoutKeyboard;
// End - mirrors _GetKeyEventProperties
if (core.languageProcessor.isActive && input) {