'use strict'; /* global AppWindow */ (function(exports) { /** * This window inherits the AppWindow, and modifies some properties * different from the later. * * For some flow diagrams related to input management, please refer to * https://wiki.mozilla.org/Gaia/System/InputManagement#Flow_Diagrams . * * @class InputWindow * @param {OBject} configs The configuration of the input app * @augments AppWindow */ var InputWindow = function(configs) { // note: properties in configs will become this[properties] configs.isInputMethod = true; configs.name = 'InputMethods'; configs.url = configs.origin + configs.path; this.splashed = true; // we're waiting for _ready to fire to do something further this._pendingReady = false; AppWindow.call(this, configs); // input keyboard transition was not supposed to have a timeout before, // so we give this a much higher tolerance this.transitionController.OPENING_TRANSITION_TIMEOUT = 5000; this.transitionController.CLOSING_TRANSITION_TIMEOUT = 5000; // Set the ID onto the element so Gij tests // can probe it with Keyboard#getCurrentKeyboard. this.browser.element.dataset.frameName = configs.id; }; /** * @borrows AppWindow.prototype as InputWindow.prototype * @memberof InputWindow */ InputWindow.prototype = Object.create(AppWindow.prototype); InputWindow.prototype.constructor = InputWindow; InputWindow.REGISTERED_EVENTS = ['mozbrowsererror']; // use only the transition controller as the sub component InputWindow.SUB_COMPONENTS = { 'transitionController': 'AppTransitionController' }; InputWindow.prototype.containerElement = document.getElementById('keyboards'); InputWindow.prototype.view = function iw_view() { return `
`; }; InputWindow.prototype.eventPrefix = 'input-app'; /** * Different animation from the original window. * * @type string * @memberof InputWindow */ InputWindow.prototype.openAnimation = 'slide-from-bottom'; /** * Different animation from the original window. * * @type string * @memberof InputWindow */ InputWindow.prototype.closeAnimation = 'slide-to-bottom'; InputWindow.prototype._DEBUG = false; InputWindow.prototype.CLASS_LIST = 'inputWindow'; InputWindow.prototype.CLASS_NAME = 'InputWindow'; /** * Fired when the input app signals its readiness through window.resizeTo(), * which translates to mozbrowserresized; * or * Fired when the input app wants to resize itself * * @event InputWindow#mozbrowserresize */ InputWindow.prototype._handle_mozbrowserresize = function iw_handle_mozbrowserresize(evt) { var height = evt.detail.height; this._setHeight(height); this.publish('ready'); // we're already opened, so publish heightchanged if ('opened' === this.transitionController._transitionState) { this.publish('heightchanged'); } evt.stopPropagation(); }; /** * ready is a one-time event and is triggered when the keyboard app * signals its readiness through mozbrowserresize event. * we only listen to this one-time ready event when we're opening the * keyboard app through |setAsActiveInput()|; subsequent mozbrowserresize * events (like keyboard app wants to resize itself) would not trigger this * ready event. * * @event InputWindow#_ready */ InputWindow.prototype._handle__ready = function iw_handle__ready(evt) { this.element.removeEventListener('_ready', this); this._pendingReady = false; this._setHeight(evt.detail.height); AppWindow.prototype.open.call(this, this.immediateOpen ? 'immediate' : undefined ); }; InputWindow.prototype._setHeight = function iw_setHeight(height) { // bug 1059683: when we're on a HiDPI device with non-integer // devicePixelRatio the system may calculate (from available screen height // and keyboard height) the available height for current window/layout that // is a fraction smaller than the ideal value, which can result in a // 1-device-px gap between the current window/layout and keyboard, on such // devices. to mitigate this, the keyboard tries to report 1 less pixel of // height if it sees that the height of the keyboard is a fraction when // expressed in device pixel. var dpx = window.devicePixelRatio; if ((height * dpx) % 1 !== 0) { height = Math.floor(height * dpx) / dpx; } this.height = height; // Gecko 52 removed mozpasspointerevents. Clip the full-viewport iframe // to the actual keyboard area so it receives touches without covering // the input field above it. if (this.browser && this.browser.element && Number.isFinite(height) && height > 0) { this.browser.element.style.clip = 'rect(' + Math.max(0, window.innerHeight - height) + 'px, auto, auto, auto)'; } }; // Set the input method activeness of this InputWindow: // - mozbrowserresize event (for the readiness of the input app) // - setVisible & setInputMethodActive // - styling classes InputWindow.prototype._setAsActiveInput = function iw_setAsActiveInput(active) { this.debug('setAsActiveInput: ' + this.manifestURL + this.path + ', active: ' + active); this.setVisible(active); if (this.browser.element.setInputMethodActive) { this.browser.element.setInputMethodActive(active); } else { console.warn('setInputMethodActive is not available'); } if (active) { this.browser.element.addEventListener('mozbrowserresize', this, true); this.element.classList.add('top-most'); } else { this.browser.element.removeEventListener('mozbrowserresize', this, true); this.element.classList.remove('top-most'); this.height = 0; } }; /** * Input apps can never change orientation for whatever reasons. * * @override * @memberof InputWindow */ InputWindow.prototype.lockOrientation = function iw_setOrientation(){ }; InputWindow.prototype.setOrientation = function iw_setOrientation(){ }; /** * Close the input window. * * Also, remove the handler of ready event because our caller might be closing * this input window immediately after it has just called open() on this, and * the ready event (required by the whole opening process) hasn't been * triggered -- In that case, since we're closing, we must ignore that ready * event too. * * @override * @param {String} immediate If the window has to be closed without animation * @memberof InputWindow */ InputWindow.prototype.close = function iw_close(immediate){ this.element.removeEventListener('_ready', this); AppWindow.prototype.close.call(this, immediate); }; /** * Open the input window, optionally replacing the layout before doing so. * * @override * @param {Object} configs The configs of the layout * @memberof InputWindow */ InputWindow.prototype.open = function iw_open(configs){ var hashChanged = false; if (configs.hash !== this.hash) { this.browser.element.src = this.origin + this.pathInitial + configs.hash; this.debug(this.browser.element.frameName + ' is overwritten: ' + this.browser.element.src); this.browser.element.dataset.frameName = configs.id; this.hash = configs.hash; hashChanged = true; } this.immediateOpen = configs.immediateOpen; this.element.addEventListener('_ready', this); this._pendingReady = true; this._setAsActiveInput(true); // if we're not chaning the hash and we're currently closing, then we are // currently still the active input method (because setAsActiveInput(false) // is called at |closed| on the manager); as we still want to open the // window, and as we won't trigger the hashchange nor the input/visibility // events on the app, we need to trigger the readyhandler by ourselves. // (on top of that, we want to show the keyboard immediately.) if (!hashChanged && 'closing' === this.transitionController._transitionState){ this.immediateOpen = true; this.publish('ready'); } }; exports.InputWindow = InputWindow; })(window);