2012-11-07 15:53:24 +04:00
|
|
|
/* 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");
|
|
|
|
|
2013-09-17 19:11:57 +04:00
|
|
|
this.Keyboard = {
|
2012-11-07 15:53:24 +04:00
|
|
|
_messageManager: null,
|
|
|
|
_messageNames: [
|
2013-03-15 16:28:51 +04:00
|
|
|
'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions',
|
2013-08-01 21:16:43 +04:00
|
|
|
'SetSelectionRange', 'ReplaceSurroundingText', 'ShowInputMethodPicker',
|
2013-08-08 04:07:41 +04:00
|
|
|
'SwitchToNextInputMethod', 'HideInputMethod',
|
2013-08-30 16:07:16 +04:00
|
|
|
'GetText', 'SendKey', 'GetContext',
|
|
|
|
'SetComposition', 'EndComposition'
|
2012-11-07 15:53:24 +04:00
|
|
|
],
|
|
|
|
|
|
|
|
get messageManager() {
|
|
|
|
if (this._messageManager && !Cu.isDeadWrapper(this._messageManager))
|
|
|
|
return this._messageManager;
|
|
|
|
|
2013-09-23 17:40:59 +04:00
|
|
|
return null;
|
2012-11-07 15:53:24 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
set messageManager(mm) {
|
|
|
|
this._messageManager = mm;
|
|
|
|
},
|
|
|
|
|
2013-09-06 22:18:48 +04:00
|
|
|
sendAsyncMessage: function(name, data) {
|
|
|
|
try {
|
|
|
|
this.messageManager.sendAsyncMessage(name, data);
|
|
|
|
} catch(e) { }
|
|
|
|
},
|
|
|
|
|
2012-11-07 15:53:24 +04:00
|
|
|
init: function keyboardInit() {
|
2013-12-15 21:47:09 +04:00
|
|
|
Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', false);
|
|
|
|
Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
|
2013-05-24 13:58:24 +04:00
|
|
|
Services.obs.addObserver(this, 'oop-frameloader-crashed', false);
|
2012-11-07 15:53:24 +04:00
|
|
|
|
|
|
|
for (let name of this._messageNames)
|
|
|
|
ppmm.addMessageListener('Keyboard:' + name, this);
|
|
|
|
},
|
|
|
|
|
|
|
|
observe: function keyboardObserve(subject, topic, data) {
|
|
|
|
let frameLoader = subject.QueryInterface(Ci.nsIFrameLoader);
|
|
|
|
let mm = frameLoader.messageManager;
|
2013-05-24 13:58:24 +04:00
|
|
|
|
|
|
|
if (topic == 'oop-frameloader-crashed') {
|
|
|
|
if (this.messageManager == mm) {
|
|
|
|
// The application has been closed unexpectingly. Let's tell the
|
|
|
|
// keyboard app that the focus has been lost.
|
|
|
|
ppmm.broadcastAsyncMessage('Keyboard:FocusChange', { 'type': 'blur' });
|
|
|
|
}
|
|
|
|
} else {
|
2013-11-11 18:11:43 +04:00
|
|
|
this.initFormsFrameScript(mm);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
initFormsFrameScript: function(mm) {
|
|
|
|
mm.addMessageListener('Forms:Input', this);
|
|
|
|
mm.addMessageListener('Forms:SelectionChange', this);
|
|
|
|
mm.addMessageListener('Forms:GetText:Result:OK', this);
|
|
|
|
mm.addMessageListener('Forms:GetText:Result:Error', this);
|
|
|
|
mm.addMessageListener('Forms:SetSelectionRange:Result:OK', this);
|
|
|
|
mm.addMessageListener('Forms:ReplaceSurroundingText:Result:OK', this);
|
|
|
|
mm.addMessageListener('Forms:SendKey:Result:OK', 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);
|
2012-11-07 15:53:24 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
receiveMessage: function keyboardReceiveMessage(msg) {
|
2013-02-16 00:35:18 +04:00
|
|
|
// If we get a 'Keyboard:XXX' message, check that the sender has the
|
2013-11-01 17:01:14 +04:00
|
|
|
// input permission.
|
2013-03-15 16:28:51 +04:00
|
|
|
if (msg.name.indexOf("Keyboard:") != -1) {
|
2013-09-23 17:40:59 +04:00
|
|
|
if (!this.messageManager) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-02-16 00:35:18 +04:00
|
|
|
let mm;
|
|
|
|
try {
|
|
|
|
mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
|
|
|
|
.frameLoader.messageManager;
|
|
|
|
} catch(e) {
|
|
|
|
mm = msg.target;
|
|
|
|
}
|
|
|
|
|
|
|
|
// That should never happen.
|
|
|
|
if (!mm) {
|
|
|
|
dump("!! No message manager found for " + msg.name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-19 21:07:28 +04:00
|
|
|
if (!mm.assertPermission("input")) {
|
2013-02-16 00:35:18 +04:00
|
|
|
dump("Keyboard message " + msg.name +
|
2013-11-01 17:01:14 +04:00
|
|
|
" from a content process with no 'input' privileges.");
|
2013-02-16 00:35:18 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-07 15:53:24 +04:00
|
|
|
switch (msg.name) {
|
|
|
|
case 'Forms:Input':
|
2013-08-09 22:01:28 +04:00
|
|
|
this.handleFocusChange(msg);
|
2012-11-07 15:53:24 +04:00
|
|
|
break;
|
2013-03-15 16:28:51 +04:00
|
|
|
case 'Forms:SelectionChange':
|
2013-08-08 04:07:41 +04:00
|
|
|
case 'Forms:GetText:Result:OK':
|
|
|
|
case 'Forms:GetText:Result:Error':
|
|
|
|
case 'Forms:SetSelectionRange:Result:OK':
|
|
|
|
case 'Forms:ReplaceSurroundingText:Result:OK':
|
|
|
|
case 'Forms:SendKey:Result:OK':
|
|
|
|
case 'Forms:SequenceError':
|
|
|
|
case 'Forms:GetContext:Result:OK':
|
2013-08-30 16:07:16 +04:00
|
|
|
case 'Forms:SetComposition:Result:OK':
|
|
|
|
case 'Forms:EndComposition:Result:OK':
|
2013-08-08 04:07:41 +04:00
|
|
|
let name = msg.name.replace(/^Forms/, 'Keyboard');
|
|
|
|
this.forwardEvent(name, msg);
|
2013-03-15 16:28:51 +04:00
|
|
|
break;
|
2013-08-08 04:07:41 +04:00
|
|
|
|
2012-11-07 15:53:24 +04:00
|
|
|
case 'Keyboard:SetValue':
|
|
|
|
this.setValue(msg);
|
|
|
|
break;
|
|
|
|
case 'Keyboard:RemoveFocus':
|
|
|
|
this.removeFocus();
|
|
|
|
break;
|
|
|
|
case 'Keyboard:SetSelectedOption':
|
|
|
|
this.setSelectedOption(msg);
|
|
|
|
break;
|
|
|
|
case 'Keyboard:SetSelectedOptions':
|
|
|
|
this.setSelectedOption(msg);
|
|
|
|
break;
|
2013-03-15 16:28:51 +04:00
|
|
|
case 'Keyboard:SetSelectionRange':
|
|
|
|
this.setSelectionRange(msg);
|
|
|
|
break;
|
2013-04-13 18:53:41 +04:00
|
|
|
case 'Keyboard:ReplaceSurroundingText':
|
|
|
|
this.replaceSurroundingText(msg);
|
|
|
|
break;
|
2013-08-01 21:16:43 +04:00
|
|
|
case 'Keyboard:SwitchToNextInputMethod':
|
|
|
|
this.switchToNextInputMethod();
|
|
|
|
break;
|
|
|
|
case 'Keyboard:ShowInputMethodPicker':
|
|
|
|
this.showInputMethodPicker();
|
|
|
|
break;
|
2013-08-08 04:07:41 +04:00
|
|
|
case 'Keyboard:GetText':
|
|
|
|
this.getText(msg);
|
|
|
|
break;
|
|
|
|
case 'Keyboard:SendKey':
|
|
|
|
this.sendKey(msg);
|
|
|
|
break;
|
|
|
|
case 'Keyboard:GetContext':
|
|
|
|
this.getContext(msg);
|
|
|
|
break;
|
2013-08-30 16:07:16 +04:00
|
|
|
case 'Keyboard:SetComposition':
|
|
|
|
this.setComposition(msg);
|
|
|
|
break;
|
|
|
|
case 'Keyboard:EndComposition':
|
|
|
|
this.endComposition(msg);
|
|
|
|
break;
|
2012-11-07 15:53:24 +04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-08-08 04:07:41 +04:00
|
|
|
forwardEvent: function keyboardForwardEvent(newEventName, msg) {
|
2012-11-07 15:53:24 +04:00
|
|
|
this.messageManager = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
|
|
|
|
.frameLoader.messageManager;
|
|
|
|
|
2013-08-08 04:07:41 +04:00
|
|
|
ppmm.broadcastAsyncMessage(newEventName, msg.data);
|
2013-03-15 16:28:51 +04:00
|
|
|
},
|
|
|
|
|
2013-08-09 22:01:28 +04:00
|
|
|
handleFocusChange: function keyboardHandleFocusChange(msg) {
|
|
|
|
this.forwardEvent('Keyboard:FocusChange', msg);
|
|
|
|
|
2013-12-19 21:07:28 +04:00
|
|
|
let browser = Services.wm.getMostRecentWindow("navigator:browser");
|
|
|
|
|
2013-10-17 19:08:55 +04:00
|
|
|
// Chrome event, used also to render value selectors; that's why we need
|
|
|
|
// the info about choices / min / max here as well...
|
2013-12-19 21:07:28 +04:00
|
|
|
browser.shell.sendChromeEvent({
|
2013-08-09 22:01:28 +04:00
|
|
|
type: 'inputmethod-contextchange',
|
2013-10-17 19:08:55 +04:00
|
|
|
inputType: msg.data.type,
|
|
|
|
value: msg.data.value,
|
|
|
|
choices: JSON.stringify(msg.data.choices),
|
|
|
|
min: msg.data.min,
|
|
|
|
max: msg.data.max
|
2013-08-09 22:01:28 +04:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2012-11-07 15:53:24 +04:00
|
|
|
setSelectedOption: function keyboardSetSelectedOption(msg) {
|
2013-09-06 22:18:48 +04:00
|
|
|
this.sendAsyncMessage('Forms:Select:Choice', msg.data);
|
2012-11-07 15:53:24 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
setSelectedOptions: function keyboardSetSelectedOptions(msg) {
|
2013-09-06 22:18:48 +04:00
|
|
|
this.sendAsyncMessage('Forms:Select:Choice', msg.data);
|
2012-11-07 15:53:24 +04:00
|
|
|
},
|
|
|
|
|
2013-03-15 16:28:51 +04:00
|
|
|
setSelectionRange: function keyboardSetSelectionRange(msg) {
|
2013-09-06 22:18:48 +04:00
|
|
|
this.sendAsyncMessage('Forms:SetSelectionRange', msg.data);
|
2013-03-15 16:28:51 +04:00
|
|
|
},
|
|
|
|
|
2012-11-07 15:53:24 +04:00
|
|
|
setValue: function keyboardSetValue(msg) {
|
2013-09-06 22:18:48 +04:00
|
|
|
this.sendAsyncMessage('Forms:Input:Value', msg.data);
|
2012-11-07 15:53:24 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
removeFocus: function keyboardRemoveFocus() {
|
2013-09-06 22:18:48 +04:00
|
|
|
this.sendAsyncMessage('Forms:Select:Blur', {});
|
2013-04-13 18:53:41 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
replaceSurroundingText: function keyboardReplaceSurroundingText(msg) {
|
2013-09-06 22:18:48 +04:00
|
|
|
this.sendAsyncMessage('Forms:ReplaceSurroundingText', msg.data);
|
2013-08-01 21:16:43 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
showInputMethodPicker: function keyboardShowInputMethodPicker() {
|
2013-12-19 21:07:28 +04:00
|
|
|
let browser = Services.wm.getMostRecentWindow("navigator:browser");
|
|
|
|
browser.shell.sendChromeEvent({
|
2013-08-09 22:01:28 +04:00
|
|
|
type: "inputmethod-showall"
|
2013-08-01 21:16:43 +04:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
switchToNextInputMethod: function keyboardSwitchToNextInputMethod() {
|
2013-12-19 21:07:28 +04:00
|
|
|
let browser = Services.wm.getMostRecentWindow("navigator:browser");
|
|
|
|
browser.shell.sendChromeEvent({
|
2013-08-09 22:01:28 +04:00
|
|
|
type: "inputmethod-next"
|
2013-08-01 21:16:43 +04:00
|
|
|
});
|
2013-08-08 04:07:41 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
getText: function keyboardGetText(msg) {
|
2013-09-06 22:18:48 +04:00
|
|
|
this.sendAsyncMessage('Forms:GetText', msg.data);
|
2013-08-08 04:07:41 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
sendKey: function keyboardSendKey(msg) {
|
2013-09-06 22:18:48 +04:00
|
|
|
this.sendAsyncMessage('Forms:Input:SendKey', msg.data);
|
2013-08-08 04:07:41 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
getContext: function keyboardGetContext(msg) {
|
2013-09-12 21:02:36 +04:00
|
|
|
if (this._layouts) {
|
|
|
|
ppmm.broadcastAsyncMessage('Keyboard:LayoutsChange', this._layouts);
|
|
|
|
}
|
|
|
|
|
2013-09-06 22:18:48 +04:00
|
|
|
this.sendAsyncMessage('Forms:GetContext', msg.data);
|
2013-08-30 16:07:16 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
setComposition: function keyboardSetComposition(msg) {
|
2013-09-06 22:18:48 +04:00
|
|
|
this.sendAsyncMessage('Forms:SetComposition', msg.data);
|
2013-08-30 16:07:16 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
endComposition: function keyboardEndComposition(msg) {
|
2013-09-06 22:18:48 +04:00
|
|
|
this.sendAsyncMessage('Forms:EndComposition', msg.data);
|
2013-09-12 21:02:36 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the number of keyboard layouts active from keyboard_manager
|
|
|
|
*/
|
|
|
|
_layouts: null,
|
|
|
|
setLayouts: function keyboardSetLayoutCount(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
|
|
|
|
this._layouts = layouts;
|
|
|
|
|
|
|
|
ppmm.broadcastAsyncMessage('Keyboard:LayoutsChange', layouts);
|
2012-11-07 15:53:24 +04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-09-17 19:11:57 +04:00
|
|
|
this.Keyboard.init();
|