diff --git a/core/input/devices.js b/core/input/devices.js index 36689b3c..954c89dc 100644 --- a/core/input/devices.js +++ b/core/input/devices.js @@ -44,6 +44,12 @@ function isMac() { function isWindows() { return navigator && !!(/win/i).exec(navigator.platform); } +function isIOS() { + return navigator && + (!!(/ipad/i).exec(navigator.platform) || + !!(/iphone/i).exec(navigator.platform) || + !!(/ipod/i).exec(navigator.platform)); +} Keyboard.prototype = { // private methods @@ -106,13 +112,15 @@ Keyboard.prototype = { // We cannot handle keys we cannot track, but we also need // to deal with virtual keyboards which omit key info - if (code === 'Unidentified') { + // (iOS omits tracking info on keyup events, which forces us to + // special treat that platform here) + if ((code === 'Unidentified') || isIOS()) { if (keysym) { // If it's a virtual keyboard then it should be // sufficient to just send press and release right // after each other - this._sendKeyEvent(keysym, 'Unidentified', true); - this._sendKeyEvent(keysym, 'Unidentified', false); + this._sendKeyEvent(keysym, code, true); + this._sendKeyEvent(keysym, code, false); } stopEvent(e); diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 332d88c4..f1f33af8 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -124,6 +124,77 @@ describe('Key Event Handling', function() { }); }); + describe('Fake keyup', function() { + it('should fake keyup events for virtual keyboards', function(done) { + if (isIE() || isEdge()) this.skip(); + var count = 0; + var kbd = new Keyboard({ + onKeyEvent: function(keysym, code, down) { + switch (count++) { + case 0: + expect(keysym).to.be.equal(0x61); + expect(code).to.be.equal('Unidentified'); + expect(down).to.be.equal(true); + break; + case 1: + expect(keysym).to.be.equal(0x61); + expect(code).to.be.equal('Unidentified'); + expect(down).to.be.equal(false); + done(); + } + }}); + kbd._handleKeyDown(keyevent('keydown', {code: 'Unidentified', key: 'a'})); + }); + + describe('iOS', function() { + var origNavigator; + beforeEach(function () { + // window.navigator is a protected read-only property in many + // environments, so we need to redefine it whilst running these + // tests. + origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); + if (origNavigator === undefined) { + // Object.getOwnPropertyDescriptor() doesn't work + // properly in any version of IE + this.skip(); + } + + Object.defineProperty(window, "navigator", {value: {}}); + if (window.navigator.platform !== undefined) { + // Object.defineProperty() doesn't work properly in old + // versions of Chrome + this.skip(); + } + + window.navigator.platform = "iPhone 9.0"; + }); + afterEach(function () { + Object.defineProperty(window, "navigator", origNavigator); + }); + + it('should fake keyup events on iOS', function(done) { + if (isIE() || isEdge()) this.skip(); + var count = 0; + var kbd = new Keyboard({ + onKeyEvent: function(keysym, code, down) { + switch (count++) { + case 0: + expect(keysym).to.be.equal(0x61); + expect(code).to.be.equal('KeyA'); + expect(down).to.be.equal(true); + break; + case 1: + expect(keysym).to.be.equal(0x61); + expect(code).to.be.equal('KeyA'); + expect(down).to.be.equal(false); + done(); + } + }}); + kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); + }); + }); + }); + describe('Track Key State', function() { beforeEach(function () { if (isIE() || isEdge()) this.skip();