|
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/. */ |
|
4 |
|
5 'use strict'; |
|
6 |
|
7 this.EXPORTED_SYMBOLS = ['Keyboard']; |
|
8 |
|
9 const Cu = Components.utils; |
|
10 const Cc = Components.classes; |
|
11 const Ci = Components.interfaces; |
|
12 |
|
13 Cu.import('resource://gre/modules/Services.jsm'); |
|
14 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
15 |
|
16 XPCOMUtils.defineLazyServiceGetter(this, "ppmm", |
|
17 "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster"); |
|
18 |
|
19 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy", |
|
20 "resource://gre/modules/SystemAppProxy.jsm"); |
|
21 |
|
22 this.Keyboard = { |
|
23 _formMM: null, // The current web page message manager. |
|
24 _keyboardMM: null, // The keyboard app message manager. |
|
25 _systemMessageName: [ |
|
26 'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions' |
|
27 ], |
|
28 |
|
29 _messageNames: [ |
|
30 'SetSelectionRange', 'ReplaceSurroundingText', 'ShowInputMethodPicker', |
|
31 'SwitchToNextInputMethod', 'HideInputMethod', |
|
32 'GetText', 'SendKey', 'GetContext', |
|
33 'SetComposition', 'EndComposition', |
|
34 'Register', 'Unregister' |
|
35 ], |
|
36 |
|
37 get formMM() { |
|
38 if (this._formMM && !Cu.isDeadWrapper(this._formMM)) |
|
39 return this._formMM; |
|
40 |
|
41 return null; |
|
42 }, |
|
43 |
|
44 set formMM(mm) { |
|
45 this._formMM = mm; |
|
46 }, |
|
47 |
|
48 sendToForm: function(name, data) { |
|
49 try { |
|
50 this.formMM.sendAsyncMessage(name, data); |
|
51 } catch(e) { } |
|
52 }, |
|
53 |
|
54 sendToKeyboard: function(name, data) { |
|
55 try { |
|
56 this._keyboardMM.sendAsyncMessage(name, data); |
|
57 } catch(e) { } |
|
58 }, |
|
59 |
|
60 init: function keyboardInit() { |
|
61 Services.obs.addObserver(this, 'inprocess-browser-shown', false); |
|
62 Services.obs.addObserver(this, 'remote-browser-shown', false); |
|
63 Services.obs.addObserver(this, 'oop-frameloader-crashed', false); |
|
64 |
|
65 for (let name of this._messageNames) { |
|
66 ppmm.addMessageListener('Keyboard:' + name, this); |
|
67 } |
|
68 |
|
69 for (let name of this._systemMessageName) { |
|
70 ppmm.addMessageListener('System:' + name, this); |
|
71 } |
|
72 }, |
|
73 |
|
74 observe: function keyboardObserve(subject, topic, data) { |
|
75 let frameLoader = subject.QueryInterface(Ci.nsIFrameLoader); |
|
76 let mm = frameLoader.messageManager; |
|
77 |
|
78 if (topic == 'oop-frameloader-crashed') { |
|
79 if (this.formMM == mm) { |
|
80 // The application has been closed unexpectingly. Let's tell the |
|
81 // keyboard app that the focus has been lost. |
|
82 this.sendToKeyboard('Keyboard:FocusChange', { 'type': 'blur' }); |
|
83 } |
|
84 } else { |
|
85 // Ignore notifications that aren't from a BrowserOrApp |
|
86 if (!frameLoader.ownerIsBrowserOrAppFrame) { |
|
87 return; |
|
88 } |
|
89 this.initFormsFrameScript(mm); |
|
90 } |
|
91 }, |
|
92 |
|
93 initFormsFrameScript: function(mm) { |
|
94 mm.addMessageListener('Forms:Input', this); |
|
95 mm.addMessageListener('Forms:SelectionChange', this); |
|
96 mm.addMessageListener('Forms:GetText:Result:OK', this); |
|
97 mm.addMessageListener('Forms:GetText:Result:Error', this); |
|
98 mm.addMessageListener('Forms:SetSelectionRange:Result:OK', this); |
|
99 mm.addMessageListener('Forms:SetSelectionRange:Result:Error', this); |
|
100 mm.addMessageListener('Forms:ReplaceSurroundingText:Result:OK', this); |
|
101 mm.addMessageListener('Forms:ReplaceSurroundingText:Result:Error', this); |
|
102 mm.addMessageListener('Forms:SendKey:Result:OK', this); |
|
103 mm.addMessageListener('Forms:SendKey:Result:Error', this); |
|
104 mm.addMessageListener('Forms:SequenceError', this); |
|
105 mm.addMessageListener('Forms:GetContext:Result:OK', this); |
|
106 mm.addMessageListener('Forms:SetComposition:Result:OK', this); |
|
107 mm.addMessageListener('Forms:EndComposition:Result:OK', this); |
|
108 }, |
|
109 |
|
110 receiveMessage: function keyboardReceiveMessage(msg) { |
|
111 // If we get a 'Keyboard:XXX'/'System:XXX' message, check that the sender |
|
112 // has the required permission. |
|
113 let mm; |
|
114 let isKeyboardRegistration = msg.name == "Keyboard:Register" || |
|
115 msg.name == "Keyboard:Unregister"; |
|
116 if (msg.name.indexOf("Keyboard:") === 0 || |
|
117 msg.name.indexOf("System:") === 0) { |
|
118 if (!this.formMM && !isKeyboardRegistration) { |
|
119 return; |
|
120 } |
|
121 |
|
122 try { |
|
123 mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner) |
|
124 .frameLoader.messageManager; |
|
125 } catch(e) { |
|
126 mm = msg.target; |
|
127 } |
|
128 |
|
129 // That should never happen. |
|
130 if (!mm) { |
|
131 dump("!! No message manager found for " + msg.name); |
|
132 return; |
|
133 } |
|
134 |
|
135 let testing = false; |
|
136 try { |
|
137 testing = Services.prefs.getBoolPref("dom.mozInputMethod.testing"); |
|
138 } catch (e) { |
|
139 } |
|
140 |
|
141 let perm = (msg.name.indexOf("Keyboard:") === 0) ? "input" |
|
142 : "input-manage"; |
|
143 if (!isKeyboardRegistration && !testing && |
|
144 !mm.assertPermission(perm)) { |
|
145 dump("Keyboard message " + msg.name + |
|
146 " from a content process with no '" + perm + "' privileges."); |
|
147 return; |
|
148 } |
|
149 } |
|
150 |
|
151 switch (msg.name) { |
|
152 case 'Forms:Input': |
|
153 this.handleFocusChange(msg); |
|
154 break; |
|
155 case 'Forms:SelectionChange': |
|
156 case 'Forms:GetText:Result:OK': |
|
157 case 'Forms:GetText:Result:Error': |
|
158 case 'Forms:SetSelectionRange:Result:OK': |
|
159 case 'Forms:ReplaceSurroundingText:Result:OK': |
|
160 case 'Forms:SendKey:Result:OK': |
|
161 case 'Forms:SendKey:Result:Error': |
|
162 case 'Forms:SequenceError': |
|
163 case 'Forms:GetContext:Result:OK': |
|
164 case 'Forms:SetComposition:Result:OK': |
|
165 case 'Forms:EndComposition:Result:OK': |
|
166 case 'Forms:SetSelectionRange:Result:Error': |
|
167 case 'Forms:ReplaceSurroundingText:Result:Error': |
|
168 let name = msg.name.replace(/^Forms/, 'Keyboard'); |
|
169 this.forwardEvent(name, msg); |
|
170 break; |
|
171 |
|
172 case 'System:SetValue': |
|
173 this.setValue(msg); |
|
174 break; |
|
175 case 'Keyboard:RemoveFocus': |
|
176 case 'System:RemoveFocus': |
|
177 this.removeFocus(); |
|
178 break; |
|
179 case 'System:SetSelectedOption': |
|
180 this.setSelectedOption(msg); |
|
181 break; |
|
182 case 'System:SetSelectedOptions': |
|
183 this.setSelectedOption(msg); |
|
184 break; |
|
185 case 'Keyboard:SetSelectionRange': |
|
186 this.setSelectionRange(msg); |
|
187 break; |
|
188 case 'Keyboard:ReplaceSurroundingText': |
|
189 this.replaceSurroundingText(msg); |
|
190 break; |
|
191 case 'Keyboard:SwitchToNextInputMethod': |
|
192 this.switchToNextInputMethod(); |
|
193 break; |
|
194 case 'Keyboard:ShowInputMethodPicker': |
|
195 this.showInputMethodPicker(); |
|
196 break; |
|
197 case 'Keyboard:GetText': |
|
198 this.getText(msg); |
|
199 break; |
|
200 case 'Keyboard:SendKey': |
|
201 this.sendKey(msg); |
|
202 break; |
|
203 case 'Keyboard:GetContext': |
|
204 this.getContext(msg); |
|
205 break; |
|
206 case 'Keyboard:SetComposition': |
|
207 this.setComposition(msg); |
|
208 break; |
|
209 case 'Keyboard:EndComposition': |
|
210 this.endComposition(msg); |
|
211 break; |
|
212 case 'Keyboard:Register': |
|
213 this._keyboardMM = mm; |
|
214 break; |
|
215 case 'Keyboard:Unregister': |
|
216 this._keyboardMM = null; |
|
217 break; |
|
218 } |
|
219 }, |
|
220 |
|
221 forwardEvent: function keyboardForwardEvent(newEventName, msg) { |
|
222 this.formMM = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner) |
|
223 .frameLoader.messageManager; |
|
224 |
|
225 this.sendToKeyboard(newEventName, msg.data); |
|
226 }, |
|
227 |
|
228 handleFocusChange: function keyboardHandleFocusChange(msg) { |
|
229 this.forwardEvent('Keyboard:FocusChange', msg); |
|
230 |
|
231 // Chrome event, used also to render value selectors; that's why we need |
|
232 // the info about choices / min / max here as well... |
|
233 SystemAppProxy.dispatchEvent({ |
|
234 type: 'inputmethod-contextchange', |
|
235 inputType: msg.data.type, |
|
236 value: msg.data.value, |
|
237 choices: JSON.stringify(msg.data.choices), |
|
238 min: msg.data.min, |
|
239 max: msg.data.max |
|
240 }); |
|
241 }, |
|
242 |
|
243 setSelectedOption: function keyboardSetSelectedOption(msg) { |
|
244 this.sendToForm('Forms:Select:Choice', msg.data); |
|
245 }, |
|
246 |
|
247 setSelectedOptions: function keyboardSetSelectedOptions(msg) { |
|
248 this.sendToForm('Forms:Select:Choice', msg.data); |
|
249 }, |
|
250 |
|
251 setSelectionRange: function keyboardSetSelectionRange(msg) { |
|
252 this.sendToForm('Forms:SetSelectionRange', msg.data); |
|
253 }, |
|
254 |
|
255 setValue: function keyboardSetValue(msg) { |
|
256 this.sendToForm('Forms:Input:Value', msg.data); |
|
257 }, |
|
258 |
|
259 removeFocus: function keyboardRemoveFocus() { |
|
260 this.sendToForm('Forms:Select:Blur', {}); |
|
261 }, |
|
262 |
|
263 replaceSurroundingText: function keyboardReplaceSurroundingText(msg) { |
|
264 this.sendToForm('Forms:ReplaceSurroundingText', msg.data); |
|
265 }, |
|
266 |
|
267 showInputMethodPicker: function keyboardShowInputMethodPicker() { |
|
268 SystemAppProxy.dispatchEvent({ |
|
269 type: "inputmethod-showall" |
|
270 }); |
|
271 }, |
|
272 |
|
273 switchToNextInputMethod: function keyboardSwitchToNextInputMethod() { |
|
274 SystemAppProxy.dispatchEvent({ |
|
275 type: "inputmethod-next" |
|
276 }); |
|
277 }, |
|
278 |
|
279 getText: function keyboardGetText(msg) { |
|
280 this.sendToForm('Forms:GetText', msg.data); |
|
281 }, |
|
282 |
|
283 sendKey: function keyboardSendKey(msg) { |
|
284 this.sendToForm('Forms:Input:SendKey', msg.data); |
|
285 }, |
|
286 |
|
287 getContext: function keyboardGetContext(msg) { |
|
288 if (this._layouts) { |
|
289 this.sendToKeyboard('Keyboard:LayoutsChange', this._layouts); |
|
290 } |
|
291 |
|
292 this.sendToForm('Forms:GetContext', msg.data); |
|
293 }, |
|
294 |
|
295 setComposition: function keyboardSetComposition(msg) { |
|
296 this.sendToForm('Forms:SetComposition', msg.data); |
|
297 }, |
|
298 |
|
299 endComposition: function keyboardEndComposition(msg) { |
|
300 this.sendToForm('Forms:EndComposition', msg.data); |
|
301 }, |
|
302 |
|
303 /** |
|
304 * Get the number of keyboard layouts active from keyboard_manager |
|
305 */ |
|
306 _layouts: null, |
|
307 setLayouts: function keyboardSetLayoutCount(layouts) { |
|
308 // The input method plugins may not have loaded yet, |
|
309 // cache the layouts so on init we can respond immediately instead |
|
310 // of going back and forth between keyboard_manager |
|
311 this._layouts = layouts; |
|
312 |
|
313 this.sendToKeyboard('Keyboard:LayoutsChange', layouts); |
|
314 } |
|
315 }; |
|
316 |
|
317 this.Keyboard.init(); |