michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: 'use strict'; michael@0: michael@0: this.EXPORTED_SYMBOLS = ['Keyboard']; michael@0: michael@0: const Cu = Components.utils; michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: michael@0: Cu.import('resource://gre/modules/Services.jsm'); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "ppmm", michael@0: "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy", michael@0: "resource://gre/modules/SystemAppProxy.jsm"); michael@0: michael@0: this.Keyboard = { michael@0: _formMM: null, // The current web page message manager. michael@0: _keyboardMM: null, // The keyboard app message manager. michael@0: _systemMessageName: [ michael@0: 'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions' michael@0: ], michael@0: michael@0: _messageNames: [ michael@0: 'SetSelectionRange', 'ReplaceSurroundingText', 'ShowInputMethodPicker', michael@0: 'SwitchToNextInputMethod', 'HideInputMethod', michael@0: 'GetText', 'SendKey', 'GetContext', michael@0: 'SetComposition', 'EndComposition', michael@0: 'Register', 'Unregister' michael@0: ], michael@0: michael@0: get formMM() { michael@0: if (this._formMM && !Cu.isDeadWrapper(this._formMM)) michael@0: return this._formMM; michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: set formMM(mm) { michael@0: this._formMM = mm; michael@0: }, michael@0: michael@0: sendToForm: function(name, data) { michael@0: try { michael@0: this.formMM.sendAsyncMessage(name, data); michael@0: } catch(e) { } michael@0: }, michael@0: michael@0: sendToKeyboard: function(name, data) { michael@0: try { michael@0: this._keyboardMM.sendAsyncMessage(name, data); michael@0: } catch(e) { } michael@0: }, michael@0: michael@0: init: function keyboardInit() { michael@0: Services.obs.addObserver(this, 'inprocess-browser-shown', false); michael@0: Services.obs.addObserver(this, 'remote-browser-shown', false); michael@0: Services.obs.addObserver(this, 'oop-frameloader-crashed', false); michael@0: michael@0: for (let name of this._messageNames) { michael@0: ppmm.addMessageListener('Keyboard:' + name, this); michael@0: } michael@0: michael@0: for (let name of this._systemMessageName) { michael@0: ppmm.addMessageListener('System:' + name, this); michael@0: } michael@0: }, michael@0: michael@0: observe: function keyboardObserve(subject, topic, data) { michael@0: let frameLoader = subject.QueryInterface(Ci.nsIFrameLoader); michael@0: let mm = frameLoader.messageManager; michael@0: michael@0: if (topic == 'oop-frameloader-crashed') { michael@0: if (this.formMM == mm) { michael@0: // The application has been closed unexpectingly. Let's tell the michael@0: // keyboard app that the focus has been lost. michael@0: this.sendToKeyboard('Keyboard:FocusChange', { 'type': 'blur' }); michael@0: } michael@0: } else { michael@0: // Ignore notifications that aren't from a BrowserOrApp michael@0: if (!frameLoader.ownerIsBrowserOrAppFrame) { michael@0: return; michael@0: } michael@0: this.initFormsFrameScript(mm); michael@0: } michael@0: }, michael@0: michael@0: initFormsFrameScript: function(mm) { michael@0: mm.addMessageListener('Forms:Input', this); michael@0: mm.addMessageListener('Forms:SelectionChange', this); michael@0: mm.addMessageListener('Forms:GetText:Result:OK', this); michael@0: mm.addMessageListener('Forms:GetText:Result:Error', this); michael@0: mm.addMessageListener('Forms:SetSelectionRange:Result:OK', this); michael@0: mm.addMessageListener('Forms:SetSelectionRange:Result:Error', this); michael@0: mm.addMessageListener('Forms:ReplaceSurroundingText:Result:OK', this); michael@0: mm.addMessageListener('Forms:ReplaceSurroundingText:Result:Error', this); michael@0: mm.addMessageListener('Forms:SendKey:Result:OK', this); michael@0: mm.addMessageListener('Forms:SendKey:Result:Error', this); michael@0: mm.addMessageListener('Forms:SequenceError', this); michael@0: mm.addMessageListener('Forms:GetContext:Result:OK', this); michael@0: mm.addMessageListener('Forms:SetComposition:Result:OK', this); michael@0: mm.addMessageListener('Forms:EndComposition:Result:OK', this); michael@0: }, michael@0: michael@0: receiveMessage: function keyboardReceiveMessage(msg) { michael@0: // If we get a 'Keyboard:XXX'/'System:XXX' message, check that the sender michael@0: // has the required permission. michael@0: let mm; michael@0: let isKeyboardRegistration = msg.name == "Keyboard:Register" || michael@0: msg.name == "Keyboard:Unregister"; michael@0: if (msg.name.indexOf("Keyboard:") === 0 || michael@0: msg.name.indexOf("System:") === 0) { michael@0: if (!this.formMM && !isKeyboardRegistration) { michael@0: return; michael@0: } michael@0: michael@0: try { michael@0: mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner) michael@0: .frameLoader.messageManager; michael@0: } catch(e) { michael@0: mm = msg.target; michael@0: } michael@0: michael@0: // That should never happen. michael@0: if (!mm) { michael@0: dump("!! No message manager found for " + msg.name); michael@0: return; michael@0: } michael@0: michael@0: let testing = false; michael@0: try { michael@0: testing = Services.prefs.getBoolPref("dom.mozInputMethod.testing"); michael@0: } catch (e) { michael@0: } michael@0: michael@0: let perm = (msg.name.indexOf("Keyboard:") === 0) ? "input" michael@0: : "input-manage"; michael@0: if (!isKeyboardRegistration && !testing && michael@0: !mm.assertPermission(perm)) { michael@0: dump("Keyboard message " + msg.name + michael@0: " from a content process with no '" + perm + "' privileges."); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: switch (msg.name) { michael@0: case 'Forms:Input': michael@0: this.handleFocusChange(msg); michael@0: break; michael@0: case 'Forms:SelectionChange': michael@0: case 'Forms:GetText:Result:OK': michael@0: case 'Forms:GetText:Result:Error': michael@0: case 'Forms:SetSelectionRange:Result:OK': michael@0: case 'Forms:ReplaceSurroundingText:Result:OK': michael@0: case 'Forms:SendKey:Result:OK': michael@0: case 'Forms:SendKey:Result:Error': michael@0: case 'Forms:SequenceError': michael@0: case 'Forms:GetContext:Result:OK': michael@0: case 'Forms:SetComposition:Result:OK': michael@0: case 'Forms:EndComposition:Result:OK': michael@0: case 'Forms:SetSelectionRange:Result:Error': michael@0: case 'Forms:ReplaceSurroundingText:Result:Error': michael@0: let name = msg.name.replace(/^Forms/, 'Keyboard'); michael@0: this.forwardEvent(name, msg); michael@0: break; michael@0: michael@0: case 'System:SetValue': michael@0: this.setValue(msg); michael@0: break; michael@0: case 'Keyboard:RemoveFocus': michael@0: case 'System:RemoveFocus': michael@0: this.removeFocus(); michael@0: break; michael@0: case 'System:SetSelectedOption': michael@0: this.setSelectedOption(msg); michael@0: break; michael@0: case 'System:SetSelectedOptions': michael@0: this.setSelectedOption(msg); michael@0: break; michael@0: case 'Keyboard:SetSelectionRange': michael@0: this.setSelectionRange(msg); michael@0: break; michael@0: case 'Keyboard:ReplaceSurroundingText': michael@0: this.replaceSurroundingText(msg); michael@0: break; michael@0: case 'Keyboard:SwitchToNextInputMethod': michael@0: this.switchToNextInputMethod(); michael@0: break; michael@0: case 'Keyboard:ShowInputMethodPicker': michael@0: this.showInputMethodPicker(); michael@0: break; michael@0: case 'Keyboard:GetText': michael@0: this.getText(msg); michael@0: break; michael@0: case 'Keyboard:SendKey': michael@0: this.sendKey(msg); michael@0: break; michael@0: case 'Keyboard:GetContext': michael@0: this.getContext(msg); michael@0: break; michael@0: case 'Keyboard:SetComposition': michael@0: this.setComposition(msg); michael@0: break; michael@0: case 'Keyboard:EndComposition': michael@0: this.endComposition(msg); michael@0: break; michael@0: case 'Keyboard:Register': michael@0: this._keyboardMM = mm; michael@0: break; michael@0: case 'Keyboard:Unregister': michael@0: this._keyboardMM = null; michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: forwardEvent: function keyboardForwardEvent(newEventName, msg) { michael@0: this.formMM = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner) michael@0: .frameLoader.messageManager; michael@0: michael@0: this.sendToKeyboard(newEventName, msg.data); michael@0: }, michael@0: michael@0: handleFocusChange: function keyboardHandleFocusChange(msg) { michael@0: this.forwardEvent('Keyboard:FocusChange', msg); michael@0: michael@0: // Chrome event, used also to render value selectors; that's why we need michael@0: // the info about choices / min / max here as well... michael@0: SystemAppProxy.dispatchEvent({ michael@0: type: 'inputmethod-contextchange', michael@0: inputType: msg.data.type, michael@0: value: msg.data.value, michael@0: choices: JSON.stringify(msg.data.choices), michael@0: min: msg.data.min, michael@0: max: msg.data.max michael@0: }); michael@0: }, michael@0: michael@0: setSelectedOption: function keyboardSetSelectedOption(msg) { michael@0: this.sendToForm('Forms:Select:Choice', msg.data); michael@0: }, michael@0: michael@0: setSelectedOptions: function keyboardSetSelectedOptions(msg) { michael@0: this.sendToForm('Forms:Select:Choice', msg.data); michael@0: }, michael@0: michael@0: setSelectionRange: function keyboardSetSelectionRange(msg) { michael@0: this.sendToForm('Forms:SetSelectionRange', msg.data); michael@0: }, michael@0: michael@0: setValue: function keyboardSetValue(msg) { michael@0: this.sendToForm('Forms:Input:Value', msg.data); michael@0: }, michael@0: michael@0: removeFocus: function keyboardRemoveFocus() { michael@0: this.sendToForm('Forms:Select:Blur', {}); michael@0: }, michael@0: michael@0: replaceSurroundingText: function keyboardReplaceSurroundingText(msg) { michael@0: this.sendToForm('Forms:ReplaceSurroundingText', msg.data); michael@0: }, michael@0: michael@0: showInputMethodPicker: function keyboardShowInputMethodPicker() { michael@0: SystemAppProxy.dispatchEvent({ michael@0: type: "inputmethod-showall" michael@0: }); michael@0: }, michael@0: michael@0: switchToNextInputMethod: function keyboardSwitchToNextInputMethod() { michael@0: SystemAppProxy.dispatchEvent({ michael@0: type: "inputmethod-next" michael@0: }); michael@0: }, michael@0: michael@0: getText: function keyboardGetText(msg) { michael@0: this.sendToForm('Forms:GetText', msg.data); michael@0: }, michael@0: michael@0: sendKey: function keyboardSendKey(msg) { michael@0: this.sendToForm('Forms:Input:SendKey', msg.data); michael@0: }, michael@0: michael@0: getContext: function keyboardGetContext(msg) { michael@0: if (this._layouts) { michael@0: this.sendToKeyboard('Keyboard:LayoutsChange', this._layouts); michael@0: } michael@0: michael@0: this.sendToForm('Forms:GetContext', msg.data); michael@0: }, michael@0: michael@0: setComposition: function keyboardSetComposition(msg) { michael@0: this.sendToForm('Forms:SetComposition', msg.data); michael@0: }, michael@0: michael@0: endComposition: function keyboardEndComposition(msg) { michael@0: this.sendToForm('Forms:EndComposition', msg.data); michael@0: }, michael@0: michael@0: /** michael@0: * Get the number of keyboard layouts active from keyboard_manager michael@0: */ michael@0: _layouts: null, michael@0: setLayouts: function keyboardSetLayoutCount(layouts) { michael@0: // The input method plugins may not have loaded yet, michael@0: // cache the layouts so on init we can respond immediately instead michael@0: // of going back and forth between keyboard_manager michael@0: this._layouts = layouts; michael@0: michael@0: this.sendToKeyboard('Keyboard:LayoutsChange', layouts); michael@0: } michael@0: }; michael@0: michael@0: this.Keyboard.init();