1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 this.EXPORTED_SYMBOLS = ['Keyboard'];
9 const Cu = Components.utils;
10 const Cc = Components.classes;
11 const Ci = Components.interfaces;
13 Cu.import('resource://gre/modules/Services.jsm');
14 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
16 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
17 "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster");
19 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
20 "resource://gre/modules/SystemAppProxy.jsm");
23 _formMM: null, // The current web page message manager.
24 _keyboardMM: null, // The keyboard app message manager.
25 _keyboardID: -1, // The keyboard app's ID number. -1 = invalid
26 _nextKeyboardID: 0, // The ID number counter.
28 'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions'
33 'SetSelectionRange', 'ReplaceSurroundingText', 'ShowInputMethodPicker',
34 'SwitchToNextInputMethod', 'HideInputMethod',
35 'GetText', 'SendKey', 'GetContext',
36 'SetComposition', 'EndComposition',
37 'Register', 'Unregister'
41 if (this._formMM && !Cu.isDeadWrapper(this._formMM))
51 sendToForm: function(name, data) {
53 this.formMM.sendAsyncMessage(name, data);
57 sendToKeyboard: function(name, data) {
59 this._keyboardMM.sendAsyncMessage(name, data);
63 init: function keyboardInit() {
64 Services.obs.addObserver(this, 'inprocess-browser-shown', false);
65 Services.obs.addObserver(this, 'remote-browser-shown', false);
66 Services.obs.addObserver(this, 'oop-frameloader-crashed', false);
68 for (let name of this._messageNames) {
69 ppmm.addMessageListener('Keyboard:' + name, this);
72 for (let name of this._systemMessageName) {
73 ppmm.addMessageListener('System:' + name, this);
77 observe: function keyboardObserve(subject, topic, data) {
78 let frameLoader = subject.QueryInterface(Ci.nsIFrameLoader);
79 let mm = frameLoader.messageManager;
81 if (topic == 'oop-frameloader-crashed') {
82 if (this.formMM == mm) {
83 // The application has been closed unexpectingly. Let's tell the
84 // keyboard app that the focus has been lost.
85 this.sendToKeyboard('Keyboard:FocusChange', { 'type': 'blur' });
88 // Ignore notifications that aren't from a BrowserOrApp
89 if (!frameLoader.ownerIsBrowserOrAppFrame) {
92 this.initFormsFrameScript(mm);
96 initFormsFrameScript: function(mm) {
97 mm.addMessageListener('Forms:Input', this);
98 mm.addMessageListener('Forms:SelectionChange', this);
99 mm.addMessageListener('Forms:GetText:Result:OK', this);
100 mm.addMessageListener('Forms:GetText:Result:Error', this);
101 mm.addMessageListener('Forms:SetSelectionRange:Result:OK', this);
102 mm.addMessageListener('Forms:SetSelectionRange:Result:Error', this);
103 mm.addMessageListener('Forms:ReplaceSurroundingText:Result:OK', this);
104 mm.addMessageListener('Forms:ReplaceSurroundingText:Result:Error', this);
105 mm.addMessageListener('Forms:SendKey:Result:OK', this);
106 mm.addMessageListener('Forms:SendKey:Result:Error', this);
107 mm.addMessageListener('Forms:SequenceError', this);
108 mm.addMessageListener('Forms:GetContext:Result:OK', this);
109 mm.addMessageListener('Forms:SetComposition:Result:OK', this);
110 mm.addMessageListener('Forms:EndComposition:Result:OK', this);
113 receiveMessage: function keyboardReceiveMessage(msg) {
114 // If we get a 'Keyboard:XXX'/'System:XXX' message, check that the sender
115 // has the required permission.
117 let isKeyboardRegistration = msg.name == "Keyboard:Register" ||
118 msg.name == "Keyboard:Unregister";
119 if (msg.name.indexOf("Keyboard:") === 0 ||
120 msg.name.indexOf("System:") === 0) {
121 if (!this.formMM && !isKeyboardRegistration) {
126 mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
127 .frameLoader.messageManager;
132 // That should never happen.
134 dump("!! No message manager found for " + msg.name);
140 testing = Services.prefs.getBoolPref("dom.mozInputMethod.testing");
144 let perm = (msg.name.indexOf("Keyboard:") === 0) ? "input"
146 if (!isKeyboardRegistration && !testing &&
147 !mm.assertPermission(perm)) {
148 dump("Keyboard message " + msg.name +
149 " from a content process with no '" + perm + "' privileges.");
154 // we don't process kb messages (other than register)
155 // if they come from a kb that we're currently not regsitered for.
156 // this decision is made with the kbID kept by us and kb app
158 if ('kbID' in msg.data) {
159 kbID = msg.data.kbID;
162 if (0 === msg.name.indexOf('Keyboard:') &&
163 ('Keyboard:Register' !== msg.name && this._keyboardID !== kbID)
170 this.handleFocusChange(msg);
172 case 'Forms:SelectionChange':
173 case 'Forms:GetText:Result:OK':
174 case 'Forms:GetText:Result:Error':
175 case 'Forms:SetSelectionRange:Result:OK':
176 case 'Forms:ReplaceSurroundingText:Result:OK':
177 case 'Forms:SendKey:Result:OK':
178 case 'Forms:SendKey:Result:Error':
179 case 'Forms:SequenceError':
180 case 'Forms:GetContext:Result:OK':
181 case 'Forms:SetComposition:Result:OK':
182 case 'Forms:EndComposition:Result:OK':
183 case 'Forms:SetSelectionRange:Result:Error':
184 case 'Forms:ReplaceSurroundingText:Result:Error':
185 let name = msg.name.replace(/^Forms/, 'Keyboard');
186 this.forwardEvent(name, msg);
189 case 'System:SetValue':
192 case 'Keyboard:RemoveFocus':
193 case 'System:RemoveFocus':
196 case 'System:SetSelectedOption':
197 this.setSelectedOption(msg);
199 case 'System:SetSelectedOptions':
200 this.setSelectedOption(msg);
202 case 'Keyboard:SetSelectionRange':
203 this.setSelectionRange(msg);
205 case 'Keyboard:ReplaceSurroundingText':
206 this.replaceSurroundingText(msg);
208 case 'Keyboard:SwitchToNextInputMethod':
209 this.switchToNextInputMethod();
211 case 'Keyboard:ShowInputMethodPicker':
212 this.showInputMethodPicker();
214 case 'Keyboard:GetText':
217 case 'Keyboard:SendKey':
220 case 'Keyboard:GetContext':
221 this.getContext(msg);
223 case 'Keyboard:SetComposition':
224 this.setComposition(msg);
226 case 'Keyboard:EndComposition':
227 this.endComposition(msg);
229 case 'Keyboard:Register':
230 this._keyboardMM = mm;
232 // keyboard identifies itself, use its kbID
233 // this msg would be async, so no need to return
234 this._keyboardID = kbID;
236 // generate the id for the keyboard
237 this._keyboardID = this._nextKeyboardID;
238 this._nextKeyboardID++;
240 // and we want to return the id back to inputmethod
241 return this._keyboardID;
244 case 'Keyboard:Unregister':
245 this._keyboardMM = null;
246 this._keyboardID = -1;
251 forwardEvent: function keyboardForwardEvent(newEventName, msg) {
252 let mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
253 .frameLoader.messageManager;
254 if (newEventName === 'Keyboard:FocusChange' &&
255 msg.data.type === 'blur') {
256 // A blur message can't be sent to the keyboard if the focus has
257 // already taken away at first place.
258 // This check is here to prevent problem caused by out-of-order
259 // ipc messages from two processes.
260 if (mm !== this.formMM) {
264 this.sendToKeyboard(newEventName, msg.data);
270 this.sendToKeyboard(newEventName, msg.data);
274 handleFocusChange: function keyboardHandleFocusChange(msg) {
275 let isSent = this.forwardEvent('Keyboard:FocusChange', msg);
281 // Chrome event, used also to render value selectors; that's why we need
282 // the info about choices / min / max here as well...
283 SystemAppProxy.dispatchEvent({
284 type: 'inputmethod-contextchange',
285 inputType: msg.data.type,
286 value: msg.data.value,
287 choices: JSON.stringify(msg.data.choices),
293 setSelectedOption: function keyboardSetSelectedOption(msg) {
294 this.sendToForm('Forms:Select:Choice', msg.data);
297 setSelectedOptions: function keyboardSetSelectedOptions(msg) {
298 this.sendToForm('Forms:Select:Choice', msg.data);
301 setSelectionRange: function keyboardSetSelectionRange(msg) {
302 this.sendToForm('Forms:SetSelectionRange', msg.data);
305 setValue: function keyboardSetValue(msg) {
306 this.sendToForm('Forms:Input:Value', msg.data);
309 removeFocus: function keyboardRemoveFocus() {
310 this.sendToForm('Forms:Select:Blur', {});
313 replaceSurroundingText: function keyboardReplaceSurroundingText(msg) {
314 this.sendToForm('Forms:ReplaceSurroundingText', msg.data);
317 showInputMethodPicker: function keyboardShowInputMethodPicker() {
318 SystemAppProxy.dispatchEvent({
319 type: "inputmethod-showall"
323 switchToNextInputMethod: function keyboardSwitchToNextInputMethod() {
324 SystemAppProxy.dispatchEvent({
325 type: "inputmethod-next"
329 getText: function keyboardGetText(msg) {
330 this.sendToForm('Forms:GetText', msg.data);
333 sendKey: function keyboardSendKey(msg) {
334 this.sendToForm('Forms:Input:SendKey', msg.data);
337 getContext: function keyboardGetContext(msg) {
339 this.sendToKeyboard('Keyboard:LayoutsChange', this._layouts);
342 this.sendToForm('Forms:GetContext', msg.data);
345 setComposition: function keyboardSetComposition(msg) {
346 this.sendToForm('Forms:SetComposition', msg.data);
349 endComposition: function keyboardEndComposition(msg) {
350 this.sendToForm('Forms:EndComposition', msg.data);
354 * Get the number of keyboard layouts active from keyboard_manager
357 setLayouts: function keyboardSetLayoutCount(layouts) {
358 // The input method plugins may not have loaded yet,
359 // cache the layouts so on init we can respond immediately instead
360 // of going back and forth between keyboard_manager
361 this._layouts = layouts;
363 this.sendToKeyboard('Keyboard:LayoutsChange', layouts);
367 this.Keyboard.init();