'use strict'; /* global SettingsCache, InputAppsTransitionManager, ImeMenu, InputFrameManager, InputLayouts, LazyLoader, KeyboardHelper */ // If we get a inputmethod-contextchange chrome event for an element with // one of these types, we'll just ignore it. // XXX we won't skip these types in the future when we move value selector // to an app. const IGNORED_INPUT_TYPES = { 'select-one': true, 'select-multiple': true, 'date': true, 'time': true, 'datetime': true, 'datetime-local': true }; const TYPE_GROUP_MAPPING = { // text 'text': 'text', 'textarea': 'text', 'url': 'url', 'email': 'email', 'password': 'password', 'search': 'text', // number 'number': 'number', 'tel': 'number', // option 'select-one': 'option', 'select-multiple': 'option', 'time': 'option', 'week': 'option', 'month': 'option', 'date': 'option', 'datetime': 'option', 'datetime-local': 'option', 'color': 'option' }; // How long to wait before we actually switch layouts const SWITCH_CHANGE_DELAY = 20; var KeyboardManager = { keyboardFrameContainer: null, // this info keeps the current keyboard layout's information, // including its group, its index in the group array in InputLayouts.layouts, // its occupying height and its "layout" as kept in InputLayouts.layouts _showingLayoutInfo: { group: 'text', index: 0, layout: null, height: 0 }, _switchChangeTimeout: 0, _onDebug: false, _debug: function km_debug(msg) { if (this._onDebug) { console.log('[Keyboard Manager] ' + msg); } }, _hasActiveKeyboard: false, isOutOfProcessEnabled: false, totalMemory: 0, init: function km_init() { // 3rd-party keyboard apps must be run out-of-process. SettingsCache.observe('keyboard.3rd-party-app.enabled', true, function(value) { this.isOutOfProcessEnabled = value; }.bind(this)); if ('getFeature' in navigator) { navigator.getFeature('hardware.memory').then(function(mem) { this.totalMemory = mem; }.bind(this), function() { console.error('KeyboardManager: ' + 'Failed to retrive total memory of the device.'); }); } this.keyboardFrameContainer = document.getElementById('keyboards'); // For Bug 812115: hide the keyboard when the app is closed here, // since it would take a longer round-trip to receive focuschange // Also in Bug 856692 we realise that we need to close the keyboard // when an inline activity goes away. window.addEventListener('activityrequesting', this); window.addEventListener('activityopening', this); window.addEventListener('activityclosing', this); window.addEventListener('attentionrequestopen', this); window.addEventListener('attentionrecovering', this); window.addEventListener('attentionopening', this); window.addEventListener('attentionopened', this); window.addEventListener('attentionclosing', this); window.addEventListener('attentionclosed', this); window.addEventListener('mozbrowsererror', this); window.addEventListener('applicationsetupdialogshow', this); window.addEventListener('mozmemorypressure', this); window.addEventListener('sheets-gesture-begin', this); window.addEventListener('screenchange', this); // To handle keyboard layout switching window.addEventListener('mozChromeEvent', this); this.transitionManager = new InputAppsTransitionManager(); this.transitionManager.onstatechange = this._onTransitionStateChange.bind(this); this.transitionManager.start(); this.inputFrameManager = new InputFrameManager(this); this.inputFrameManager.start(); this.inputLayouts = new InputLayouts(this, TYPE_GROUP_MAPPING); this.inputLayouts.start(); // get enabled keyboard from mozSettings, parse their manifest LazyLoader.load([ 'shared/js/input_mgmt/input_app_list.js', 'shared/js/keyboard_helper.js' ], function() { KeyboardHelper.watchLayouts( { enabled: true }, this._updateLayouts.bind(this) ); }.bind(this)); }, getHeight: function kn_getHeight() { return this.transitionManager.occupyingHeight; }, _tryLaunchOnBoot: function km_launchOnBoot() { if (Object.keys(this.inputFrameManager.runningLayouts).length) { // There are already keyboard(s) being launched. We don't really care // if a default keyboard should be launch-on-boot. return; } SettingsCache.get('keyboard.launch-on-boot', (function(value) { // If the value is not set or it is set to true, // launch the keyboad in background var launchOnBoot = value; if (typeof launchOnBoot !== 'boolean') { launchOnBoot = true; } // if there are still no keyboards running at this point - // set text to show, but don't bring it to the foreground. if (launchOnBoot && !Object.keys(this.inputFrameManager.runningLayouts).length) { this._setKeyboardToShow('text', undefined, true); } }).bind(this)); }, _updateLayouts: function km_updateLayouts(layouts) { var enabledApps = this.inputLayouts.processLayouts(layouts); // Remove apps that are no longer enabled to clean up. Object.keys(this.inputFrameManager.runningLayouts).forEach( function removeApp(manifestURL) { if (!enabledApps.has(manifestURL)) { this.removeKeyboard(manifestURL); } }, this); this._tryLaunchOnBoot(); }, resizeKeyboard: function km_resizeKeyboard(evt) { // Ignore mozbrowserresize event while keyboard Frame is transitioning out. var transitionState = this.transitionManager.currentState; if (transitionState === this.transitionManager.STATE_TRANSITION_OUT) { return; } var height = evt.detail.height; this._debug('resizeKeyboard: ' + height); this._showingLayoutInfo.height = height; this.transitionManager.handleResize(height); evt.stopPropagation(); }, // Decide the keyboard layout for the specific group and show it _activateKeyboard: function km_activateKeyboard(group) { // if we already have layouts for the group, no need to check default if (!this.inputLayouts.layouts[group]) { KeyboardHelper.checkDefaults(function changedDefaults() { KeyboardHelper.getLayouts({ enabled: true }, this._updateLayouts.bind(this)); KeyboardHelper.saveToSettings(); }.bind(this)); } // if there are still no keyboards to use, use text if (!this.inputLayouts.layouts[group]) { group = 'text'; } var previousLayout = this._showingLayoutInfo.layout; var resetPreviousFrame = function() { // We need to reset the previous frame only when we switch to a new frame // this "frame" is decided by layout properties if (previousLayout && (previousLayout.manifestURL !== this._showingLayoutInfo.layout.manifestURL || previousLayout.id !== this._showingLayoutInfo.layout.id) ) { this._debug('reset previousFrame.'); this.inputFrameManager.resetFrame(previousLayout); } }.bind(this); if (this.inputLayouts.layouts[group].activeLayout !== undefined) { this._setKeyboardToShow(group); resetPreviousFrame(); } else { this.inputLayouts.getGroupCurrentActiveLayoutIndexAsync(group) .then(currentActiveLayoutIdx => { this._setKeyboardToShow(group, currentActiveLayoutIdx); resetPreviousFrame(); }) .catch(e => { console.error(`KeyboardManager: failed to retrieve currentActiveLayoutIdx`, e); // launch keyboard anyway, just don't assign a default layout this._setKeyboardToShow(group); resetPreviousFrame(); }); } }, _inputFocusChange: function km_inputFocusChange(evt) { var type = evt.detail.inputType; // Skip the