Bumping manifests a=b2g-bump
[gecko.git] / dom / inputmethod / Keyboard.jsm
blob04ac1f2bf62248eade4b5f097b20c3f3e602fc44
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/. */
5 'use strict';
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");
22 this.Keyboard = {
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.
27   _systemMessageName: [
28     'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions'
29   ],
31   _messageNames: [
32     'RemoveFocus',
33     'SetSelectionRange', 'ReplaceSurroundingText', 'ShowInputMethodPicker',
34     'SwitchToNextInputMethod', 'HideInputMethod',
35     'GetText', 'SendKey', 'GetContext',
36     'SetComposition', 'EndComposition',
37     'Register', 'Unregister'
38   ],
40   get formMM() {
41     if (this._formMM && !Cu.isDeadWrapper(this._formMM))
42       return this._formMM;
44     return null;
45   },
47   set formMM(mm) {
48     this._formMM = mm;
49   },
51   sendToForm: function(name, data) {
52     try {
53       this.formMM.sendAsyncMessage(name, data);
54     } catch(e) { }
55   },
57   sendToKeyboard: function(name, data) {
58     try {
59       this._keyboardMM.sendAsyncMessage(name, data);
60     } catch(e) { }
61   },
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);
70     }
72     for (let name of this._systemMessageName) {
73       ppmm.addMessageListener('System:' + name, this);
74     }
75   },
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' });
86       }
87     } else {
88       // Ignore notifications that aren't from a BrowserOrApp
89       if (!frameLoader.ownerIsBrowserOrAppFrame) {
90         return;
91       }
92       this.initFormsFrameScript(mm);
93     }
94   },
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);
111   },
113   receiveMessage: function keyboardReceiveMessage(msg) {
114     // If we get a 'Keyboard:XXX'/'System:XXX' message, check that the sender
115     // has the required permission.
116     let mm;
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) {
122         return;
123       }
125       try {
126         mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
127                        .frameLoader.messageManager;
128       } catch(e) {
129         mm = msg.target;
130       }
132       // That should never happen.
133       if (!mm) {
134         dump("!! No message manager found for " + msg.name);
135         return;
136       }
138       let testing = false;
139       try {
140         testing = Services.prefs.getBoolPref("dom.mozInputMethod.testing");
141       } catch (e) {
142       }
144       let perm = (msg.name.indexOf("Keyboard:") === 0) ? "input"
145                                                        : "input-manage";
146       if (!isKeyboardRegistration && !testing &&
147           !mm.assertPermission(perm)) {
148         dump("Keyboard message " + msg.name +
149         " from a content process with no '" + perm + "' privileges.");
150         return;
151       }
152     }
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
157     let kbID = null;
158     if ('kbID' in msg.data) {
159       kbID = msg.data.kbID;
160     }
162     if (0 === msg.name.indexOf('Keyboard:') &&
163         ('Keyboard:Register' !== msg.name && this._keyboardID !== kbID)
164        ) {
165       return;
166     }
168     switch (msg.name) {
169       case 'Forms:Input':
170         this.handleFocusChange(msg);
171         break;
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);
187         break;
189       case 'System:SetValue':
190         this.setValue(msg);
191         break;
192       case 'Keyboard:RemoveFocus':
193       case 'System:RemoveFocus':
194         this.removeFocus();
195         break;
196       case 'System:SetSelectedOption':
197         this.setSelectedOption(msg);
198         break;
199       case 'System:SetSelectedOptions':
200         this.setSelectedOption(msg);
201         break;
202       case 'Keyboard:SetSelectionRange':
203         this.setSelectionRange(msg);
204         break;
205       case 'Keyboard:ReplaceSurroundingText':
206         this.replaceSurroundingText(msg);
207         break;
208       case 'Keyboard:SwitchToNextInputMethod':
209         this.switchToNextInputMethod();
210         break;
211       case 'Keyboard:ShowInputMethodPicker':
212         this.showInputMethodPicker();
213         break;
214       case 'Keyboard:GetText':
215         this.getText(msg);
216         break;
217       case 'Keyboard:SendKey':
218         this.sendKey(msg);
219         break;
220       case 'Keyboard:GetContext':
221         this.getContext(msg);
222         break;
223       case 'Keyboard:SetComposition':
224         this.setComposition(msg);
225         break;
226       case 'Keyboard:EndComposition':
227         this.endComposition(msg);
228         break;
229       case 'Keyboard:Register':
230         this._keyboardMM = mm;
231         if (kbID !== null) {
232           // keyboard identifies itself, use its kbID
233           // this msg would be async, so no need to return
234           this._keyboardID = kbID;
235         }else{
236           // generate the id for the keyboard
237           this._keyboardID = this._nextKeyboardID;
238           this._nextKeyboardID++;
239           // this msg is sync,
240           // and we want to return the id back to inputmethod
241           return this._keyboardID;
242         }
243         break;
244       case 'Keyboard:Unregister':
245         this._keyboardMM = null;
246         this._keyboardID = -1;
247         break;
248     }
249   },
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) {
261         return false;
262       }
264       this.sendToKeyboard(newEventName, msg.data);
265       return true;
266     }
268     this.formMM = mm;
270     this.sendToKeyboard(newEventName, msg.data);
271     return true;
272   },
274   handleFocusChange: function keyboardHandleFocusChange(msg) {
275     let isSent = this.forwardEvent('Keyboard:FocusChange', msg);
277     if (!isSent) {
278       return;
279     }
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),
288       min: msg.data.min,
289       max: msg.data.max
290     });
291   },
293   setSelectedOption: function keyboardSetSelectedOption(msg) {
294     this.sendToForm('Forms:Select:Choice', msg.data);
295   },
297   setSelectedOptions: function keyboardSetSelectedOptions(msg) {
298     this.sendToForm('Forms:Select:Choice', msg.data);
299   },
301   setSelectionRange: function keyboardSetSelectionRange(msg) {
302     this.sendToForm('Forms:SetSelectionRange', msg.data);
303   },
305   setValue: function keyboardSetValue(msg) {
306     this.sendToForm('Forms:Input:Value', msg.data);
307   },
309   removeFocus: function keyboardRemoveFocus() {
310     this.sendToForm('Forms:Select:Blur', {});
311   },
313   replaceSurroundingText: function keyboardReplaceSurroundingText(msg) {
314     this.sendToForm('Forms:ReplaceSurroundingText', msg.data);
315   },
317   showInputMethodPicker: function keyboardShowInputMethodPicker() {
318     SystemAppProxy.dispatchEvent({
319       type: "inputmethod-showall"
320     });
321   },
323   switchToNextInputMethod: function keyboardSwitchToNextInputMethod() {
324     SystemAppProxy.dispatchEvent({
325       type: "inputmethod-next"
326     });
327   },
329   getText: function keyboardGetText(msg) {
330     this.sendToForm('Forms:GetText', msg.data);
331   },
333   sendKey: function keyboardSendKey(msg) {
334     this.sendToForm('Forms:Input:SendKey', msg.data);
335   },
337   getContext: function keyboardGetContext(msg) {
338     if (this._layouts) {
339       this.sendToKeyboard('Keyboard:LayoutsChange', this._layouts);
340     }
342     this.sendToForm('Forms:GetContext', msg.data);
343   },
345   setComposition: function keyboardSetComposition(msg) {
346     this.sendToForm('Forms:SetComposition', msg.data);
347   },
349   endComposition: function keyboardEndComposition(msg) {
350     this.sendToForm('Forms:EndComposition', msg.data);
351   },
353   /**
354    * Get the number of keyboard layouts active from keyboard_manager
355    */
356   _layouts: null,
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);
364   }
367 this.Keyboard.init();