123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629 |
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
- 'use strict';
- this.EXPORTED_SYMBOLS = ['Keyboard'];
- const Cu = Components.utils;
- const Cc = Components.classes;
- const Ci = Components.interfaces;
- Cu.import('resource://gre/modules/Services.jsm');
- Cu.import("resource://gre/modules/XPCOMUtils.jsm");
- XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
- "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster");
- XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
- "resource://gre/modules/SystemAppProxy.jsm");
- XPCOMUtils.defineLazyGetter(this, "appsService", function() {
- return Cc["@mozilla.org/AppsService;1"].getService(Ci.nsIAppsService);
- });
- XPCOMUtils.defineLazyGetter(this, "hardwareKeyHandler", function() {
- return null;
- });
- var Utils = {
- getMMFromMessage: function u_getMMFromMessage(msg) {
- let mm;
- try {
- mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
- .frameLoader.messageManager;
- } catch(e) {
- mm = msg.target;
- }
- return mm;
- },
- checkPermissionForMM: function u_checkPermissionForMM(mm, permName) {
- return mm.assertPermission(permName);
- }
- };
- this.Keyboard = {
- _isConnectedToHardwareKeyHandler: false,
- _formMM: null, // The current web page message manager.
- _keyboardMM: null, // The keyboard app message manager.
- _keyboardID: -1, // The keyboard app's ID number. -1 = invalid
- _nextKeyboardID: 0, // The ID number counter.
- _systemMMs: [], // The message managers registered to handle system async
- // messages.
- _supportsSwitchingTypes: [],
- _systemMessageNames: [
- 'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions',
- 'SetSupportsSwitchingTypes', 'RegisterSync', 'Unregister'
- ],
- _messageNames: [
- 'RemoveFocus',
- 'SetSelectionRange', 'ReplaceSurroundingText', 'ShowInputMethodPicker',
- 'SwitchToNextInputMethod', 'HideInputMethod',
- 'SendKey', 'GetContext',
- 'SetComposition', 'EndComposition',
- 'RegisterSync', 'Unregister',
- 'ReplyHardwareKeyEvent'
- ],
- get formMM() {
- if (this._formMM && !Cu.isDeadWrapper(this._formMM))
- return this._formMM;
- return null;
- },
- set formMM(mm) {
- this._formMM = mm;
- },
- sendToForm: function(name, data) {
- if (!this.formMM) {
- dump("Keyboard.jsm: Attempt to send message " + name +
- " to form but no message manager exists.\n");
- return;
- }
- try {
- this.formMM.sendAsyncMessage(name, data);
- } catch(e) { }
- },
- sendToKeyboard: function(name, data) {
- try {
- this._keyboardMM.sendAsyncMessage(name, data);
- } catch(e) {
- return false;
- }
- return true;
- },
- sendToSystem: function(name, data) {
- if (!this._systemMMs.length) {
- dump("Keyboard.jsm: Attempt to send message " + name +
- " to system but no message manager registered.\n");
- return;
- }
- this._systemMMs.forEach((mm, i) => {
- data.inputManageId = i;
- mm.sendAsyncMessage(name, data);
- });
- },
- init: function keyboardInit() {
- Services.obs.addObserver(this, 'inprocess-browser-shown', false);
- Services.obs.addObserver(this, 'remote-browser-shown', false);
- Services.obs.addObserver(this, 'oop-frameloader-crashed', false);
- Services.obs.addObserver(this, 'message-manager-close', false);
- // For receiving the native hardware keyboard event
- if (hardwareKeyHandler) {
- hardwareKeyHandler.registerListener(this);
- }
- for (let name of this._messageNames) {
- ppmm.addMessageListener('Keyboard:' + name, this);
- }
- for (let name of this._systemMessageNames) {
- ppmm.addMessageListener('System:' + name, this);
- }
- this.inputRegistryGlue = new InputRegistryGlue();
- },
- // This method will be registered into nsIHardwareKeyHandler:
- // Send the initialized dictionary retrieved from the native keyboard event
- // to input-method-app for generating a new event.
- onHardwareKey: function onHardwareKeyReceived(evt) {
- return this.sendToKeyboard('Keyboard:ReceiveHardwareKeyEvent', {
- type: evt.type,
- keyDict: evt.initDict
- });
- },
- observe: function keyboardObserve(subject, topic, data) {
- let frameLoader = null;
- let mm = null;
- if (topic == 'message-manager-close') {
- mm = subject;
- } else {
- frameLoader = subject.QueryInterface(Ci.nsIFrameLoader);
- mm = frameLoader.messageManager;
- }
- if (topic == 'oop-frameloader-crashed' ||
- topic == 'message-manager-close') {
- if (this.formMM == mm) {
- // The application has been closed unexpectingly. Let's tell the
- // keyboard app that the focus has been lost.
- this.sendToKeyboard('Keyboard:Blur', {});
- // Notify system app to hide keyboard.
- this.sendToSystem('System:Blur', {});
- // XXX: To be removed when content migrate away from mozChromeEvents.
- SystemAppProxy.dispatchEvent({
- type: 'inputmethod-contextchange',
- inputType: 'blur'
- });
- this.formMM = null;
- }
- } else {
- // Ignore notifications that aren't from a BrowserOrApp
- if (!frameLoader.ownerIsMozBrowserOrAppFrame) {
- return;
- }
- this.initFormsFrameScript(mm);
- }
- },
- initFormsFrameScript: function(mm) {
- mm.addMessageListener('Forms:Focus', this);
- mm.addMessageListener('Forms:Blur', this);
- mm.addMessageListener('Forms:SelectionChange', this);
- mm.addMessageListener('Forms:SetSelectionRange:Result:OK', this);
- mm.addMessageListener('Forms:SetSelectionRange:Result:Error', this);
- mm.addMessageListener('Forms:ReplaceSurroundingText:Result:OK', this);
- mm.addMessageListener('Forms:ReplaceSurroundingText:Result:Error', this);
- mm.addMessageListener('Forms:SendKey:Result:OK', this);
- mm.addMessageListener('Forms:SendKey:Result:Error', this);
- mm.addMessageListener('Forms:SequenceError', this);
- mm.addMessageListener('Forms:GetContext:Result:OK', this);
- mm.addMessageListener('Forms:SetComposition:Result:OK', this);
- mm.addMessageListener('Forms:EndComposition:Result:OK', this);
- },
- receiveMessage: function keyboardReceiveMessage(msg) {
- // If we get a 'Keyboard:XXX'/'System:XXX' message, check that the sender
- // has the required permission.
- let mm;
- // Assert the permission based on the prefix of the message.
- let permName;
- if (msg.name.startsWith("Keyboard:")) {
- permName = "input";
- } else if (msg.name.startsWith("System:")) {
- permName = "input-manage";
- }
- // There is no permission to check (nor we need to get the mm)
- // for Form: messages.
- if (permName) {
- mm = Utils.getMMFromMessage(msg);
- if (!mm) {
- dump("Keyboard.jsm: Message " + msg.name + " has no message manager.");
- return;
- }
- if (!Utils.checkPermissionForMM(mm, permName)) {
- dump("Keyboard.jsm: Message " + msg.name +
- " from a content process with no '" + permName + "' privileges.\n");
- return;
- }
- }
- // we don't process kb messages (other than register)
- // if they come from a kb that we're currently not regsitered for.
- // this decision is made with the kbID kept by us and kb app
- let kbID = null;
- if ('kbID' in msg.data) {
- kbID = msg.data.kbID;
- }
- if (0 === msg.name.indexOf('Keyboard:') &&
- ('Keyboard:RegisterSync' !== msg.name && this._keyboardID !== kbID)
- ) {
- return;
- }
- switch (msg.name) {
- case 'Forms:Focus':
- this.handleFocus(msg);
- break;
- case 'Forms:Blur':
- this.handleBlur(msg);
- break;
- case 'Forms:SelectionChange':
- case 'Forms:SetSelectionRange:Result:OK':
- case 'Forms:ReplaceSurroundingText:Result:OK':
- case 'Forms:SendKey:Result:OK':
- case 'Forms:SendKey:Result:Error':
- case 'Forms:SequenceError':
- case 'Forms:GetContext:Result:OK':
- case 'Forms:SetComposition:Result:OK':
- case 'Forms:EndComposition:Result:OK':
- case 'Forms:SetSelectionRange:Result:Error':
- case 'Forms:ReplaceSurroundingText:Result:Error':
- let name = msg.name.replace(/^Forms/, 'Keyboard');
- this.forwardEvent(name, msg);
- break;
- case 'System:SetValue':
- this.setValue(msg);
- break;
- case 'Keyboard:RemoveFocus':
- case 'System:RemoveFocus':
- this.removeFocus();
- break;
- case 'System:RegisterSync': {
- if (this._systemMMs.length !== 0) {
- dump('Keyboard.jsm Warning: There are more than one content page ' +
- 'with input-manage permission. There will be undeterministic ' +
- 'responses to addInput()/removeInput() if both content pages are ' +
- 'trying to respond to the same request event.\n');
- }
- let id = this._systemMMs.length;
- this._systemMMs.push(mm);
- return id;
- }
- case 'System:Unregister':
- this._systemMMs.splice(msg.data.id, 1);
- break;
- case 'System:SetSelectedOption':
- this.setSelectedOption(msg);
- break;
- case 'System:SetSelectedOptions':
- this.setSelectedOption(msg);
- break;
- case 'System:SetSupportsSwitchingTypes':
- this.setSupportsSwitchingTypes(msg);
- break;
- case 'Keyboard:SetSelectionRange':
- this.setSelectionRange(msg);
- break;
- case 'Keyboard:ReplaceSurroundingText':
- this.replaceSurroundingText(msg);
- break;
- case 'Keyboard:SwitchToNextInputMethod':
- this.switchToNextInputMethod();
- break;
- case 'Keyboard:ShowInputMethodPicker':
- this.showInputMethodPicker();
- break;
- case 'Keyboard:SendKey':
- this.sendKey(msg);
- break;
- case 'Keyboard:GetContext':
- this.getContext(msg);
- break;
- case 'Keyboard:SetComposition':
- this.setComposition(msg);
- break;
- case 'Keyboard:EndComposition':
- this.endComposition(msg);
- break;
- case 'Keyboard:RegisterSync':
- this._keyboardMM = mm;
- if (kbID) {
- // keyboard identifies itself, use its kbID
- // this msg would be async, so no need to return
- this._keyboardID = kbID;
- }else{
- // generate the id for the keyboard
- this._keyboardID = this._nextKeyboardID;
- this._nextKeyboardID++;
- // this msg is sync,
- // and we want to return the id back to inputmethod
- return this._keyboardID;
- }
- break;
- case 'Keyboard:Unregister':
- this._keyboardMM = null;
- this._keyboardID = -1;
- break;
- case 'Keyboard:ReplyHardwareKeyEvent':
- if (hardwareKeyHandler) {
- let reply = msg.data;
- hardwareKeyHandler.onHandledByInputMethodApp(reply.type,
- reply.defaultPrevented);
- }
- break;
- }
- },
- handleFocus: function keyboardHandleFocus(msg) {
- // Set the formMM to the new message manager received.
- let mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
- .frameLoader.messageManager;
- this.formMM = mm;
- // Notify the nsIHardwareKeyHandler that the input-method-app is active now.
- if (hardwareKeyHandler && !this._isConnectedToHardwareKeyHandler) {
- this._isConnectedToHardwareKeyHandler = true;
- hardwareKeyHandler.onInputMethodAppConnected();
- }
- // Notify the current active input app to gain focus.
- this.forwardEvent('Keyboard:Focus', msg);
- // Notify System app, used also to render value selectors for now;
- // that's why we need the info about choices / min / max here as well...
- this.sendToSystem('System:Focus', msg.data);
- // XXX: To be removed when content migrate away from mozChromeEvents.
- SystemAppProxy.dispatchEvent({
- type: 'inputmethod-contextchange',
- inputType: msg.data.inputType,
- value: msg.data.value,
- choices: JSON.stringify(msg.data.choices),
- min: msg.data.min,
- max: msg.data.max
- });
- },
- handleBlur: function keyboardHandleBlur(msg) {
- let mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
- .frameLoader.messageManager;
- // A blur message can't be sent to the keyboard if the focus has
- // already been taken away at first place.
- // This check is here to prevent problem caused by out-of-order
- // ipc messages from two processes.
- if (mm !== this.formMM) {
- return;
- }
- // unset formMM
- this.formMM = null;
- // Notify the nsIHardwareKeyHandler that
- // the input-method-app is disabled now.
- if (hardwareKeyHandler && this._isConnectedToHardwareKeyHandler) {
- this._isConnectedToHardwareKeyHandler = false;
- hardwareKeyHandler.onInputMethodAppDisconnected();
- }
- this.forwardEvent('Keyboard:Blur', msg);
- this.sendToSystem('System:Blur', {});
- // XXX: To be removed when content migrate away from mozChromeEvents.
- SystemAppProxy.dispatchEvent({
- type: 'inputmethod-contextchange',
- inputType: 'blur'
- });
- },
- forwardEvent: function keyboardForwardEvent(newEventName, msg) {
- this.sendToKeyboard(newEventName, msg.data);
- },
- setSelectedOption: function keyboardSetSelectedOption(msg) {
- this.sendToForm('Forms:Select:Choice', msg.data);
- },
- setSelectedOptions: function keyboardSetSelectedOptions(msg) {
- this.sendToForm('Forms:Select:Choice', msg.data);
- },
- setSelectionRange: function keyboardSetSelectionRange(msg) {
- this.sendToForm('Forms:SetSelectionRange', msg.data);
- },
- setValue: function keyboardSetValue(msg) {
- this.sendToForm('Forms:Input:Value', msg.data);
- },
- removeFocus: function keyboardRemoveFocus() {
- if (!this.formMM) {
- return;
- }
- this.sendToForm('Forms:Select:Blur', {});
- },
- replaceSurroundingText: function keyboardReplaceSurroundingText(msg) {
- this.sendToForm('Forms:ReplaceSurroundingText', msg.data);
- },
- showInputMethodPicker: function keyboardShowInputMethodPicker() {
- this.sendToSystem('System:ShowAll', {});
- // XXX: To be removed with mozContentEvent support from shell.js
- SystemAppProxy.dispatchEvent({
- type: "inputmethod-showall"
- });
- },
- switchToNextInputMethod: function keyboardSwitchToNextInputMethod() {
- this.sendToSystem('System:Next', {});
- // XXX: To be removed with mozContentEvent support from shell.js
- SystemAppProxy.dispatchEvent({
- type: "inputmethod-next"
- });
- },
- sendKey: function keyboardSendKey(msg) {
- this.sendToForm('Forms:Input:SendKey', msg.data);
- },
- getContext: function keyboardGetContext(msg) {
- if (!this.formMM) {
- return;
- }
- this.sendToKeyboard('Keyboard:SupportsSwitchingTypesChange', {
- types: this._supportsSwitchingTypes
- });
- this.sendToForm('Forms:GetContext', msg.data);
- },
- setComposition: function keyboardSetComposition(msg) {
- this.sendToForm('Forms:SetComposition', msg.data);
- },
- endComposition: function keyboardEndComposition(msg) {
- this.sendToForm('Forms:EndComposition', msg.data);
- },
- setSupportsSwitchingTypes: function setSupportsSwitchingTypes(msg) {
- this._supportsSwitchingTypes = msg.data.types;
- this.sendToKeyboard('Keyboard:SupportsSwitchingTypesChange', msg.data);
- },
- // XXX: To be removed with mozContentEvent support from shell.js
- setLayouts: function keyboardSetLayouts(layouts) {
- // The input method plugins may not have loaded yet,
- // cache the layouts so on init we can respond immediately instead
- // of going back and forth between keyboard_manager
- var types = [];
- Object.keys(layouts).forEach((type) => {
- if (layouts[type] > 1) {
- types.push(type);
- }
- });
- this._supportsSwitchingTypes = types;
- this.sendToKeyboard('Keyboard:SupportsSwitchingTypesChange', {
- types: types
- });
- }
- };
- function InputRegistryGlue() {
- this._messageId = 0;
- this._msgMap = new Map();
- ppmm.addMessageListener('InputRegistry:Add', this);
- ppmm.addMessageListener('InputRegistry:Remove', this);
- ppmm.addMessageListener('System:InputRegistry:Add:Done', this);
- ppmm.addMessageListener('System:InputRegistry:Remove:Done', this);
- };
- InputRegistryGlue.prototype.receiveMessage = function(msg) {
- let mm = Utils.getMMFromMessage(msg);
- let permName = msg.name.startsWith("System:") ? "input-mgmt" : "input";
- if (!Utils.checkPermissionForMM(mm, permName)) {
- dump("InputRegistryGlue message " + msg.name +
- " from a content process with no " + permName + " privileges.");
- return;
- }
- switch (msg.name) {
- case 'InputRegistry:Add':
- this.addInput(msg, mm);
- break;
- case 'InputRegistry:Remove':
- this.removeInput(msg, mm);
- break;
- case 'System:InputRegistry:Add:Done':
- case 'System:InputRegistry:Remove:Done':
- this.returnMessage(msg.data);
- break;
- }
- };
- InputRegistryGlue.prototype.addInput = function(msg, mm) {
- let msgId = this._messageId++;
- this._msgMap.set(msgId, {
- mm: mm,
- requestId: msg.data.requestId
- });
- let manifestURL = appsService.getManifestURLByLocalId(msg.data.appId);
- Keyboard.sendToSystem('System:InputRegistry:Add', {
- id: msgId,
- manifestURL: manifestURL,
- inputId: msg.data.inputId,
- inputManifest: msg.data.inputManifest
- });
- // XXX: To be removed when content migrate away from mozChromeEvents.
- SystemAppProxy.dispatchEvent({
- type: 'inputregistry-add',
- id: msgId,
- manifestURL: manifestURL,
- inputId: msg.data.inputId,
- inputManifest: msg.data.inputManifest
- });
- };
- InputRegistryGlue.prototype.removeInput = function(msg, mm) {
- let msgId = this._messageId++;
- this._msgMap.set(msgId, {
- mm: mm,
- requestId: msg.data.requestId
- });
- let manifestURL = appsService.getManifestURLByLocalId(msg.data.appId);
- Keyboard.sendToSystem('System:InputRegistry:Remove', {
- id: msgId,
- manifestURL: manifestURL,
- inputId: msg.data.inputId
- });
- // XXX: To be removed when content migrate away from mozChromeEvents.
- SystemAppProxy.dispatchEvent({
- type: 'inputregistry-remove',
- id: msgId,
- manifestURL: manifestURL,
- inputId: msg.data.inputId
- });
- };
- InputRegistryGlue.prototype.returnMessage = function(detail) {
- if (!this._msgMap.has(detail.id)) {
- dump('InputRegistryGlue: Ignoring already handled message response. ' +
- 'id=' + detail.id + '\n');
- return;
- }
- let { mm, requestId } = this._msgMap.get(detail.id);
- this._msgMap.delete(detail.id);
- if (Cu.isDeadWrapper(mm)) {
- dump('InputRegistryGlue: Message manager has already died.\n');
- return;
- }
- if (!('error' in detail)) {
- mm.sendAsyncMessage('InputRegistry:Result:OK', {
- requestId: requestId
- });
- } else {
- mm.sendAsyncMessage('InputRegistry:Result:Error', {
- error: detail.error,
- requestId: requestId
- });
- }
- };
- this.Keyboard.init();
|