diff --git a/common/web/input-processor/src/text/inputProcessor.ts b/common/web/input-processor/src/text/inputProcessor.ts index b45098e538..a3481fe4b5 100644 --- a/common/web/input-processor/src/text/inputProcessor.ts +++ b/common/web/input-processor/src/text/inputProcessor.ts @@ -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; diff --git a/common/web/keyboard-processor/src/text/keyEvent.ts b/common/web/keyboard-processor/src/text/keyEvent.ts index ba108f7d24..1a16dc94bc 100644 --- a/common/web/keyboard-processor/src/text/keyEvent.ts +++ b/common/web/keyboard-processor/src/text/keyEvent.ts @@ -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) diff --git a/web/source/osk/preProcessor.ts b/web/source/osk/preProcessor.ts index 0c431ff8be..40bab7ade0 100644 --- a/web/source/osk/preProcessor.ts +++ b/web/source/osk/preProcessor.ts @@ -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) { diff --git a/web/source/osk/visualKeyboard.ts b/web/source/osk/visualKeyboard.ts index 74d735da56..19ad425c12 100644 --- a/web/source/osk/visualKeyboard.ts +++ b/web/source/osk/visualKeyboard.ts @@ -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) {