12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256 |
- /* 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";
- const Cc = Components.classes;
- const Ci = Components.interfaces;
- const Cu = Components.utils;
- const Cr = Components.results;
- Cu.import("resource://gre/modules/XPCOMUtils.jsm");
- Cu.import("resource://gre/modules/Services.jsm");
- Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
- XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
- "@mozilla.org/childprocessmessagemanager;1", "nsISyncMessageSender");
- XPCOMUtils.defineLazyServiceGetter(this, "tm",
- "@mozilla.org/thread-manager;1", "nsIThreadManager");
- /*
- * A WeakMap to map input method iframe window to
- * it's active status, kbID, and ipcHelper.
- */
- var WindowMap = {
- // WeakMap of <window, object> pairs.
- _map: null,
- /*
- * Set the object associated to the window and return it.
- */
- _getObjForWin: function(win) {
- if (!this._map) {
- this._map = new WeakMap();
- }
- if (this._map.has(win)) {
- return this._map.get(win);
- } else {
- let obj = {
- active: false,
- kbID: undefined,
- ipcHelper: null
- };
- this._map.set(win, obj);
- return obj;
- }
- },
- /*
- * Check if the given window is active.
- */
- isActive: function(win) {
- if (!this._map || !win) {
- return false;
- }
- return this._getObjForWin(win).active;
- },
- /*
- * Set the active status of the given window.
- */
- setActive: function(win, isActive) {
- if (!win) {
- return;
- }
- let obj = this._getObjForWin(win);
- obj.active = isActive;
- },
- /*
- * Get the keyboard ID (assigned by Keyboard.jsm) of the given window.
- */
- getKbID: function(win) {
- if (!this._map || !win) {
- return undefined;
- }
- let obj = this._getObjForWin(win);
- return obj.kbID;
- },
- /*
- * Set the keyboard ID (assigned by Keyboard.jsm) of the given window.
- */
- setKbID: function(win, kbID) {
- if (!win) {
- return;
- }
- let obj = this._getObjForWin(win);
- obj.kbID = kbID;
- },
- /*
- * Get InputContextDOMRequestIpcHelper instance attached to this window.
- */
- getInputContextIpcHelper: function(win) {
- if (!win) {
- return;
- }
- let obj = this._getObjForWin(win);
- if (!obj.ipcHelper) {
- obj.ipcHelper = new InputContextDOMRequestIpcHelper(win);
- }
- return obj.ipcHelper;
- },
- /*
- * Unset InputContextDOMRequestIpcHelper instance.
- */
- unsetInputContextIpcHelper: function(win) {
- if (!win) {
- return;
- }
- let obj = this._getObjForWin(win);
- if (!obj.ipcHelper) {
- return;
- }
- obj.ipcHelper = null;
- }
- };
- var cpmmSendAsyncMessageWithKbID = function (self, msg, data) {
- data.kbID = WindowMap.getKbID(self._window);
- cpmm.sendAsyncMessage(msg, data);
- };
- /**
- * ==============================================
- * InputMethodManager
- * ==============================================
- */
- function MozInputMethodManager(win) {
- this._window = win;
- }
- MozInputMethodManager.prototype = {
- supportsSwitchingForCurrentInputContext: false,
- _window: null,
- classID: Components.ID("{7e9d7280-ef86-11e2-b778-0800200c9a66}"),
- QueryInterface: XPCOMUtils.generateQI([]),
- set oninputcontextfocus(handler) {
- this.__DOM_IMPL__.setEventHandler("oninputcontextfocus", handler);
- },
- get oninputcontextfocus() {
- return this.__DOM_IMPL__.getEventHandler("oninputcontextfocus");
- },
- set oninputcontextblur(handler) {
- this.__DOM_IMPL__.setEventHandler("oninputcontextblur", handler);
- },
- get oninputcontextblur() {
- return this.__DOM_IMPL__.getEventHandler("oninputcontextblur");
- },
- set onshowallrequest(handler) {
- this.__DOM_IMPL__.setEventHandler("onshowallrequest", handler);
- },
- get onshowallrequest() {
- return this.__DOM_IMPL__.getEventHandler("onshowallrequest");
- },
- set onnextrequest(handler) {
- this.__DOM_IMPL__.setEventHandler("onnextrequest", handler);
- },
- get onnextrequest() {
- return this.__DOM_IMPL__.getEventHandler("onnextrequest");
- },
- set onaddinputrequest(handler) {
- this.__DOM_IMPL__.setEventHandler("onaddinputrequest", handler);
- },
- get onaddinputrequest() {
- return this.__DOM_IMPL__.getEventHandler("onaddinputrequest");
- },
- set onremoveinputrequest(handler) {
- this.__DOM_IMPL__.setEventHandler("onremoveinputrequest", handler);
- },
- get onremoveinputrequest() {
- return this.__DOM_IMPL__.getEventHandler("onremoveinputrequest");
- },
- showAll: function() {
- if (!WindowMap.isActive(this._window)) {
- return;
- }
- cpmmSendAsyncMessageWithKbID(this, 'Keyboard:ShowInputMethodPicker', {});
- },
- next: function() {
- if (!WindowMap.isActive(this._window)) {
- return;
- }
- cpmmSendAsyncMessageWithKbID(this, 'Keyboard:SwitchToNextInputMethod', {});
- },
- supportsSwitching: function() {
- if (!WindowMap.isActive(this._window)) {
- return false;
- }
- return this.supportsSwitchingForCurrentInputContext;
- },
- hide: function() {
- if (!WindowMap.isActive(this._window)) {
- return;
- }
- cpmmSendAsyncMessageWithKbID(this, 'Keyboard:RemoveFocus', {});
- },
- setSupportsSwitchingTypes: function(types) {
- cpmm.sendAsyncMessage('System:SetSupportsSwitchingTypes', {
- types: types
- });
- },
- handleFocus: function(data) {
- let detail = new MozInputContextFocusEventDetail(this._window, data);
- let wrappedDetail =
- this._window.MozInputContextFocusEventDetail._create(this._window, detail);
- let event = new this._window.CustomEvent('inputcontextfocus',
- { cancelable: true, detail: wrappedDetail });
- let handled = !this.__DOM_IMPL__.dispatchEvent(event);
- // A gentle warning if the event is not preventDefault() by the content.
- if (!handled) {
- dump('MozKeyboard.js: A frame with input-manage permission did not' +
- ' handle the inputcontextfocus event dispatched.\n');
- }
- },
- handleBlur: function(data) {
- let event =
- new this._window.Event('inputcontextblur', { cancelable: true });
- let handled = !this.__DOM_IMPL__.dispatchEvent(event);
- // A gentle warning if the event is not preventDefault() by the content.
- if (!handled) {
- dump('MozKeyboard.js: A frame with input-manage permission did not' +
- ' handle the inputcontextblur event dispatched.\n');
- }
- },
- dispatchShowAllRequestEvent: function() {
- this._fireSimpleEvent('showallrequest');
- },
- dispatchNextRequestEvent: function() {
- this._fireSimpleEvent('nextrequest');
- },
- _fireSimpleEvent: function(eventType) {
- let event = new this._window.Event(eventType);
- let handled = !this.__DOM_IMPL__.dispatchEvent(event, { cancelable: true });
- // A gentle warning if the event is not preventDefault() by the content.
- if (!handled) {
- dump('MozKeyboard.js: A frame with input-manage permission did not' +
- ' handle the ' + eventType + ' event dispatched.\n');
- }
- },
- handleAddInput: function(data) {
- let p = this._fireInputRegistryEvent('addinputrequest', data);
- if (!p) {
- return;
- }
- p.then(() => {
- cpmm.sendAsyncMessage('System:InputRegistry:Add:Done', {
- id: data.id
- });
- }, (error) => {
- cpmm.sendAsyncMessage('System:InputRegistry:Add:Done', {
- id: data.id,
- error: error || 'Unknown Error'
- });
- });
- },
- handleRemoveInput: function(data) {
- let p = this._fireInputRegistryEvent('removeinputrequest', data);
- if (!p) {
- return;
- }
- p.then(() => {
- cpmm.sendAsyncMessage('System:InputRegistry:Remove:Done', {
- id: data.id
- });
- }, (error) => {
- cpmm.sendAsyncMessage('System:InputRegistry:Remove:Done', {
- id: data.id,
- error: error || 'Unknown Error'
- });
- });
- },
- _fireInputRegistryEvent: function(eventType, data) {
- let detail = new MozInputRegistryEventDetail(this._window, data);
- let wrappedDetail =
- this._window.MozInputRegistryEventDetail._create(this._window, detail);
- let event = new this._window.CustomEvent(eventType,
- { cancelable: true, detail: wrappedDetail });
- let handled = !this.__DOM_IMPL__.dispatchEvent(event);
- // A gentle warning if the event is not preventDefault() by the content.
- if (!handled) {
- dump('MozKeyboard.js: A frame with input-manage permission did not' +
- ' handle the ' + eventType + ' event dispatched.\n');
- return null;
- }
- return detail.takeChainedPromise();
- }
- };
- function MozInputContextFocusEventDetail(win, data) {
- this.type = data.type;
- this.inputType = data.inputType;
- this.value = data.value;
- // Exposed as MozInputContextChoicesInfo dictionary defined in WebIDL
- this.choices = data.choices;
- this.min = data.min;
- this.max = data.max;
- }
- MozInputContextFocusEventDetail.prototype = {
- classID: Components.ID("{e0794208-ac50-40e8-b22e-6ee0b4c4e6e8}"),
- QueryInterface: XPCOMUtils.generateQI([]),
- type: undefined,
- inputType: undefined,
- value: '',
- choices: null,
- min: undefined,
- max: undefined
- };
- function MozInputRegistryEventDetail(win, data) {
- this._window = win;
- this.manifestURL = data.manifestURL;
- this.inputId = data.inputId;
- // Exposed as MozInputMethodInputManifest dictionary defined in WebIDL
- this.inputManifest = data.inputManifest;
- this._chainedPromise = Promise.resolve();
- }
- MozInputRegistryEventDetail.prototype = {
- classID: Components.ID("{02130070-9b3e-4f38-bbd9-f0013aa36717}"),
- QueryInterface: XPCOMUtils.generateQI([]),
- _window: null,
- manifestURL: undefined,
- inputId: undefined,
- inputManifest: null,
- waitUntil: function(p) {
- // Need an extra protection here since waitUntil will be an no-op
- // when chainedPromise is already returned.
- if (!this._chainedPromise) {
- throw new this._window.DOMException(
- 'Must call waitUntil() within the event handling loop.',
- 'InvalidStateError');
- }
- this._chainedPromise = this._chainedPromise
- .then(function() { return p; });
- },
- takeChainedPromise: function() {
- var p = this._chainedPromise;
- this._chainedPromise = null;
- return p;
- }
- };
- /**
- * ==============================================
- * InputMethod
- * ==============================================
- */
- function MozInputMethod() { }
- MozInputMethod.prototype = {
- __proto__: DOMRequestIpcHelper.prototype,
- _window: null,
- _inputcontext: null,
- _wrappedInputContext: null,
- _mgmt: null,
- _wrappedMgmt: null,
- _supportsSwitchingTypes: [],
- _inputManageId: undefined,
- classID: Components.ID("{4607330d-e7d2-40a4-9eb8-43967eae0142}"),
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsIDOMGlobalPropertyInitializer,
- Ci.nsIObserver,
- Ci.nsISupportsWeakReference
- ]),
- init: function mozInputMethodInit(win) {
- this._window = win;
- this._mgmt = new MozInputMethodManager(win);
- this._wrappedMgmt = win.MozInputMethodManager._create(win, this._mgmt);
- this.innerWindowID = win.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils)
- .currentInnerWindowID;
- Services.obs.addObserver(this, "inner-window-destroyed", false);
- cpmm.addWeakMessageListener('Keyboard:Focus', this);
- cpmm.addWeakMessageListener('Keyboard:Blur', this);
- cpmm.addWeakMessageListener('Keyboard:SelectionChange', this);
- cpmm.addWeakMessageListener('Keyboard:GetContext:Result:OK', this);
- cpmm.addWeakMessageListener('Keyboard:SupportsSwitchingTypesChange', this);
- cpmm.addWeakMessageListener('Keyboard:ReceiveHardwareKeyEvent', this);
- cpmm.addWeakMessageListener('InputRegistry:Result:OK', this);
- cpmm.addWeakMessageListener('InputRegistry:Result:Error', this);
- if (this._hasInputManagePerm(win)) {
- this._inputManageId = cpmm.sendSyncMessage('System:RegisterSync', {})[0];
- cpmm.addWeakMessageListener('System:Focus', this);
- cpmm.addWeakMessageListener('System:Blur', this);
- cpmm.addWeakMessageListener('System:ShowAll', this);
- cpmm.addWeakMessageListener('System:Next', this);
- cpmm.addWeakMessageListener('System:InputRegistry:Add', this);
- cpmm.addWeakMessageListener('System:InputRegistry:Remove', this);
- }
- },
- uninit: function mozInputMethodUninit() {
- this._window = null;
- this._mgmt = null;
- this._wrappedMgmt = null;
- cpmm.removeWeakMessageListener('Keyboard:Focus', this);
- cpmm.removeWeakMessageListener('Keyboard:Blur', this);
- cpmm.removeWeakMessageListener('Keyboard:SelectionChange', this);
- cpmm.removeWeakMessageListener('Keyboard:GetContext:Result:OK', this);
- cpmm.removeWeakMessageListener('Keyboard:SupportsSwitchingTypesChange', this);
- cpmm.removeWeakMessageListener('Keyboard:ReceiveHardwareKeyEvent', this);
- cpmm.removeWeakMessageListener('InputRegistry:Result:OK', this);
- cpmm.removeWeakMessageListener('InputRegistry:Result:Error', this);
- this.setActive(false);
- if (typeof this._inputManageId === 'number') {
- cpmm.sendAsyncMessage('System:Unregister', {
- 'id': this._inputManageId
- });
- cpmm.removeWeakMessageListener('System:Focus', this);
- cpmm.removeWeakMessageListener('System:Blur', this);
- cpmm.removeWeakMessageListener('System:ShowAll', this);
- cpmm.removeWeakMessageListener('System:Next', this);
- cpmm.removeWeakMessageListener('System:InputRegistry:Add', this);
- cpmm.removeWeakMessageListener('System:InputRegistry:Remove', this);
- }
- },
- receiveMessage: function mozInputMethodReceiveMsg(msg) {
- if (msg.name.startsWith('Keyboard') &&
- !WindowMap.isActive(this._window)) {
- return;
- }
- let data = msg.data;
- if (msg.name.startsWith('System') &&
- this._inputManageId !== data.inputManageId) {
- return;
- }
- delete data.inputManageId;
- let resolver = ('requestId' in data) ?
- this.takePromiseResolver(data.requestId) : null;
- switch(msg.name) {
- case 'Keyboard:Focus':
- // XXX Bug 904339 could receive 'text' event twice
- this.setInputContext(data);
- break;
- case 'Keyboard:Blur':
- this.setInputContext(null);
- break;
- case 'Keyboard:SelectionChange':
- if (this.inputcontext) {
- this._inputcontext.updateSelectionContext(data, false);
- }
- break;
- case 'Keyboard:GetContext:Result:OK':
- this.setInputContext(data);
- break;
- case 'Keyboard:SupportsSwitchingTypesChange':
- this._supportsSwitchingTypes = data.types;
- break;
- case 'Keyboard:ReceiveHardwareKeyEvent':
- if (!Ci.nsIHardwareKeyHandler) {
- break;
- }
- let defaultPrevented = Ci.nsIHardwareKeyHandler.NO_DEFAULT_PREVENTED;
- // |event.preventDefault()| is allowed to be called only when
- // |event.cancelable| is true
- if (this._inputcontext && data.keyDict.cancelable) {
- defaultPrevented |= this._inputcontext.forwardHardwareKeyEvent(data);
- }
- cpmmSendAsyncMessageWithKbID(this, 'Keyboard:ReplyHardwareKeyEvent', {
- type: data.type,
- defaultPrevented: defaultPrevented
- });
- break;
- case 'InputRegistry:Result:OK':
- resolver.resolve();
- break;
- case 'InputRegistry:Result:Error':
- resolver.reject(data.error);
- break;
- case 'System:Focus':
- this._mgmt.handleFocus(data);
- break;
- case 'System:Blur':
- this._mgmt.handleBlur(data);
- break;
- case 'System:ShowAll':
- this._mgmt.dispatchShowAllRequestEvent();
- break;
- case 'System:Next':
- this._mgmt.dispatchNextRequestEvent();
- break;
- case 'System:InputRegistry:Add':
- this._mgmt.handleAddInput(data);
- break;
- case 'System:InputRegistry:Remove':
- this._mgmt.handleRemoveInput(data);
- break;
- }
- },
- observe: function mozInputMethodObserve(subject, topic, data) {
- let wId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
- if (wId == this.innerWindowID)
- this.uninit();
- },
- get mgmt() {
- return this._wrappedMgmt;
- },
- get inputcontext() {
- if (!WindowMap.isActive(this._window)) {
- return null;
- }
- return this._wrappedInputContext;
- },
- set oninputcontextchange(handler) {
- this.__DOM_IMPL__.setEventHandler("oninputcontextchange", handler);
- },
- get oninputcontextchange() {
- return this.__DOM_IMPL__.getEventHandler("oninputcontextchange");
- },
- setInputContext: function mozKeyboardContextChange(data) {
- if (this._inputcontext) {
- this._inputcontext.destroy();
- this._inputcontext = null;
- this._wrappedInputContext = null;
- this._mgmt.supportsSwitchingForCurrentInputContext = false;
- }
- if (data) {
- this._mgmt.supportsSwitchingForCurrentInputContext =
- (this._supportsSwitchingTypes.indexOf(data.inputType) !== -1);
- this._inputcontext = new MozInputContext(data);
- this._inputcontext.init(this._window);
- // inputcontext will be exposed as a WebIDL object. Create its
- // content-side object explicitly to avoid Bug 1001325.
- this._wrappedInputContext =
- this._window.MozInputContext._create(this._window, this._inputcontext);
- }
- let event = new this._window.Event("inputcontextchange");
- this.__DOM_IMPL__.dispatchEvent(event);
- },
- setActive: function mozInputMethodSetActive(isActive) {
- if (WindowMap.isActive(this._window) === isActive) {
- return;
- }
- WindowMap.setActive(this._window, isActive);
- if (isActive) {
- // Activate current input method.
- // If there is already an active context, then this will trigger
- // a GetContext:Result:OK event, and we can initialize ourselves.
- // Otherwise silently ignored.
- // get keyboard ID from Keyboard.jsm,
- // or if we already have it, get it from our map
- // Note: if we need to get it from Keyboard.jsm,
- // we have to use a synchronous message
- var kbID = WindowMap.getKbID(this._window);
- if (kbID) {
- cpmmSendAsyncMessageWithKbID(this, 'Keyboard:RegisterSync', {});
- } else {
- let res = cpmm.sendSyncMessage('Keyboard:RegisterSync', {});
- WindowMap.setKbID(this._window, res[0]);
- }
- cpmmSendAsyncMessageWithKbID(this, 'Keyboard:GetContext', {});
- } else {
- // Deactive current input method.
- cpmmSendAsyncMessageWithKbID(this, 'Keyboard:Unregister', {});
- if (this._inputcontext) {
- this.setInputContext(null);
- }
- }
- },
- addInput: function(inputId, inputManifest) {
- return this.createPromiseWithId(function(resolverId) {
- let appId = this._window.document.nodePrincipal.appId;
- cpmm.sendAsyncMessage('InputRegistry:Add', {
- requestId: resolverId,
- inputId: inputId,
- inputManifest: inputManifest,
- appId: appId
- });
- }.bind(this));
- },
- removeInput: function(inputId) {
- return this.createPromiseWithId(function(resolverId) {
- let appId = this._window.document.nodePrincipal.appId;
- cpmm.sendAsyncMessage('InputRegistry:Remove', {
- requestId: resolverId,
- inputId: inputId,
- appId: appId
- });
- }.bind(this));
- },
- setValue: function(value) {
- cpmm.sendAsyncMessage('System:SetValue', {
- 'value': value
- });
- },
- setSelectedOption: function(index) {
- cpmm.sendAsyncMessage('System:SetSelectedOption', {
- 'index': index
- });
- },
- setSelectedOptions: function(indexes) {
- cpmm.sendAsyncMessage('System:SetSelectedOptions', {
- 'indexes': indexes
- });
- },
- removeFocus: function() {
- cpmm.sendAsyncMessage('System:RemoveFocus', {});
- },
- // Only the system app needs that, so instead of testing a permission which
- // is allowed for all chrome:// url, we explicitly test that this is the
- // system app's start URL.
- _hasInputManagePerm: function(win) {
- let url = win.location.href;
- let systemAppIndex;
- try {
- systemAppIndex = Services.prefs.getCharPref('b2g.system_startup_url');
- } catch(e) {
- dump('MozKeyboard.jsm: no system app startup url set (pref is b2g.system_startup_url)');
- }
- dump(`MozKeyboard.jsm expecting ${systemAppIndex}\n`);
- return url == systemAppIndex;
- }
- };
- /**
- * ==============================================
- * InputContextDOMRequestIpcHelper
- * ==============================================
- */
- function InputContextDOMRequestIpcHelper(win) {
- this.initDOMRequestHelper(win,
- ["Keyboard:GetText:Result:OK",
- "Keyboard:GetText:Result:Error",
- "Keyboard:SetSelectionRange:Result:OK",
- "Keyboard:ReplaceSurroundingText:Result:OK",
- "Keyboard:SendKey:Result:OK",
- "Keyboard:SendKey:Result:Error",
- "Keyboard:SetComposition:Result:OK",
- "Keyboard:EndComposition:Result:OK",
- "Keyboard:SequenceError"]);
- }
- InputContextDOMRequestIpcHelper.prototype = {
- __proto__: DOMRequestIpcHelper.prototype,
- _inputContext: null,
- attachInputContext: function(inputCtx) {
- if (this._inputContext) {
- throw new Error("InputContextDOMRequestIpcHelper: detach the context first.");
- }
- this._inputContext = inputCtx;
- },
- // Unset ourselves when the window is destroyed.
- uninit: function() {
- WindowMap.unsetInputContextIpcHelper(this._window);
- },
- detachInputContext: function() {
- // All requests that are still pending need to be invalidated
- // because the context is no longer valid.
- this.forEachPromiseResolver(k => {
- this.takePromiseResolver(k).reject("InputContext got destroyed");
- });
- this._inputContext = null;
- },
- receiveMessage: function(msg) {
- if (!this._inputContext) {
- dump('InputContextDOMRequestIpcHelper received message without context attached.\n');
- return;
- }
- this._inputContext.receiveMessage(msg);
- }
- };
- function MozInputContextSelectionChangeEventDetail(ctx, ownAction) {
- this._ctx = ctx;
- this.ownAction = ownAction;
- }
- MozInputContextSelectionChangeEventDetail.prototype = {
- classID: Components.ID("ef35443e-a400-4ae3-9170-c2f4e05f7aed"),
- QueryInterface: XPCOMUtils.generateQI([]),
- ownAction: false,
- get selectionStart() {
- return this._ctx.selectionStart;
- },
- get selectionEnd() {
- return this._ctx.selectionEnd;
- }
- };
- function MozInputContextSurroundingTextChangeEventDetail(ctx, ownAction) {
- this._ctx = ctx;
- this.ownAction = ownAction;
- }
- MozInputContextSurroundingTextChangeEventDetail.prototype = {
- classID: Components.ID("1c50fdaf-74af-4b2e-814f-792caf65a168"),
- QueryInterface: XPCOMUtils.generateQI([]),
- ownAction: false,
- get text() {
- return this._ctx.text;
- },
- get textBeforeCursor() {
- return this._ctx.textBeforeCursor;
- },
- get textAfterCursor() {
- return this._ctx.textAfterCursor;
- }
- };
- /**
- * ==============================================
- * HardwareInput
- * ==============================================
- */
- function MozHardwareInput() {
- }
- MozHardwareInput.prototype = {
- classID: Components.ID("{1e38633d-d08b-4867-9944-afa5c648adb6}"),
- QueryInterface: XPCOMUtils.generateQI([]),
- };
- /**
- * ==============================================
- * InputContext
- * ==============================================
- */
- function MozInputContext(data) {
- this._context = {
- type: data.type,
- inputType: data.inputType,
- inputMode: data.inputMode,
- lang: data.lang,
- selectionStart: data.selectionStart,
- selectionEnd: data.selectionEnd,
- text: data.value
- };
- this._contextId = data.contextId;
- }
- MozInputContext.prototype = {
- _window: null,
- _context: null,
- _contextId: -1,
- _ipcHelper: null,
- _hardwareinput: null,
- _wrappedhardwareinput: null,
- classID: Components.ID("{1e38633d-d08b-4867-9944-afa5c648adb6}"),
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsIObserver,
- Ci.nsISupportsWeakReference
- ]),
- init: function ic_init(win) {
- this._window = win;
- this._ipcHelper = WindowMap.getInputContextIpcHelper(win);
- this._ipcHelper.attachInputContext(this);
- this._hardwareinput = new MozHardwareInput();
- this._wrappedhardwareinput =
- this._window.MozHardwareInput._create(this._window, this._hardwareinput);
- },
- destroy: function ic_destroy() {
- // A consuming application might still hold a cached version of
- // this object. After destroying all methods will throw because we
- // cannot create new promises anymore, but we still hold
- // (outdated) information in the context. So let's clear that out.
- for (var k in this._context) {
- if (this._context.hasOwnProperty(k)) {
- this._context[k] = null;
- }
- }
- this._ipcHelper.detachInputContext();
- this._ipcHelper = null;
- this._window = null;
- this._hardwareinput = null;
- this._wrappedhardwareinput = null;
- },
- receiveMessage: function ic_receiveMessage(msg) {
- if (!msg || !msg.json) {
- dump('InputContext received message without data\n');
- return;
- }
- let json = msg.json;
- let resolver = this._ipcHelper.takePromiseResolver(json.requestId);
- if (!resolver) {
- dump('InputContext received invalid requestId.\n');
- return;
- }
- // Update context first before resolving promise to avoid race condition
- if (json.selectioninfo) {
- this.updateSelectionContext(json.selectioninfo, true);
- }
- switch (msg.name) {
- case "Keyboard:SendKey:Result:OK":
- resolver.resolve(true);
- break;
- case "Keyboard:SendKey:Result:Error":
- resolver.reject(json.error);
- break;
- case "Keyboard:GetText:Result:OK":
- resolver.resolve(json.text);
- break;
- case "Keyboard:GetText:Result:Error":
- resolver.reject(json.error);
- break;
- case "Keyboard:SetSelectionRange:Result:OK":
- case "Keyboard:ReplaceSurroundingText:Result:OK":
- resolver.resolve(
- Cu.cloneInto(json.selectioninfo, this._window));
- break;
- case "Keyboard:SequenceError":
- // Occurs when a new element got focus, but the inputContext was
- // not invalidated yet...
- resolver.reject("InputContext has expired");
- break;
- case "Keyboard:SetComposition:Result:OK": // Fall through.
- case "Keyboard:EndComposition:Result:OK":
- resolver.resolve(true);
- break;
- default:
- dump("Could not find a handler for " + msg.name);
- resolver.reject();
- break;
- }
- },
- updateSelectionContext: function ic_updateSelectionContext(data, ownAction) {
- if (!this._context) {
- return;
- }
- let selectionDirty =
- this._context.selectionStart !== data.selectionStart ||
- this._context.selectionEnd !== data.selectionEnd;
- let surroundDirty = selectionDirty || data.text !== this._contextId.text;
- this._context.text = data.text;
- this._context.selectionStart = data.selectionStart;
- this._context.selectionEnd = data.selectionEnd;
- if (selectionDirty) {
- let selectionChangeDetail =
- new MozInputContextSelectionChangeEventDetail(this, ownAction);
- let wrappedSelectionChangeDetail =
- this._window.MozInputContextSelectionChangeEventDetail
- ._create(this._window, selectionChangeDetail);
- let selectionChangeEvent = new this._window.CustomEvent("selectionchange",
- { cancelable: false, detail: wrappedSelectionChangeDetail });
- this.__DOM_IMPL__.dispatchEvent(selectionChangeEvent);
- }
- if (surroundDirty) {
- let surroundingTextChangeDetail =
- new MozInputContextSurroundingTextChangeEventDetail(this, ownAction);
- let wrappedSurroundingTextChangeDetail =
- this._window.MozInputContextSurroundingTextChangeEventDetail
- ._create(this._window, surroundingTextChangeDetail);
- let selectionChangeEvent = new this._window.CustomEvent("surroundingtextchange",
- { cancelable: false, detail: wrappedSurroundingTextChangeDetail });
- this.__DOM_IMPL__.dispatchEvent(selectionChangeEvent);
- }
- },
- // tag name of the input field
- get type() {
- return this._context.type;
- },
- // type of the input field
- get inputType() {
- return this._context.inputType;
- },
- get inputMode() {
- return this._context.inputMode;
- },
- get lang() {
- return this._context.lang;
- },
- getText: function ic_getText(offset, length) {
- let text;
- if (offset && length) {
- text = this._context.text.substr(offset, length);
- } else if (offset) {
- text = this._context.text.substr(offset);
- } else {
- text = this._context.text;
- }
- return this._window.Promise.resolve(text);
- },
- get selectionStart() {
- return this._context.selectionStart;
- },
- get selectionEnd() {
- return this._context.selectionEnd;
- },
- get text() {
- return this._context.text;
- },
- get textBeforeCursor() {
- let text = this._context.text;
- let start = this._context.selectionStart;
- return (start < 100) ?
- text.substr(0, start) :
- text.substr(start - 100, 100);
- },
- get textAfterCursor() {
- let text = this._context.text;
- let start = this._context.selectionStart;
- let end = this._context.selectionEnd;
- return text.substr(start, end - start + 100);
- },
- get hardwareinput() {
- return this._wrappedhardwareinput;
- },
- setSelectionRange: function ic_setSelectionRange(start, length) {
- let self = this;
- return this._sendPromise(function(resolverId) {
- cpmmSendAsyncMessageWithKbID(self, 'Keyboard:SetSelectionRange', {
- contextId: self._contextId,
- requestId: resolverId,
- selectionStart: start,
- selectionEnd: start + length
- });
- });
- },
- get onsurroundingtextchange() {
- return this.__DOM_IMPL__.getEventHandler("onsurroundingtextchange");
- },
- set onsurroundingtextchange(handler) {
- this.__DOM_IMPL__.setEventHandler("onsurroundingtextchange", handler);
- },
- get onselectionchange() {
- return this.__DOM_IMPL__.getEventHandler("onselectionchange");
- },
- set onselectionchange(handler) {
- this.__DOM_IMPL__.setEventHandler("onselectionchange", handler);
- },
- replaceSurroundingText: function ic_replaceSurrText(text, offset, length) {
- let self = this;
- return this._sendPromise(function(resolverId) {
- cpmmSendAsyncMessageWithKbID(self, 'Keyboard:ReplaceSurroundingText', {
- contextId: self._contextId,
- requestId: resolverId,
- text: text,
- offset: offset || 0,
- length: length || 0
- });
- });
- },
- deleteSurroundingText: function ic_deleteSurrText(offset, length) {
- return this.replaceSurroundingText(null, offset, length);
- },
- sendKey: function ic_sendKey(dictOrKeyCode, charCode, modifiers, repeat) {
- if (typeof dictOrKeyCode === 'number') {
- // XXX: modifiers are ignored in this API method.
- return this._sendPromise((resolverId) => {
- cpmmSendAsyncMessageWithKbID(this, 'Keyboard:SendKey', {
- contextId: this._contextId,
- requestId: resolverId,
- method: 'sendKey',
- keyCode: dictOrKeyCode,
- charCode: charCode,
- repeat: repeat
- });
- });
- } else if (typeof dictOrKeyCode === 'object') {
- return this._sendPromise((resolverId) => {
- cpmmSendAsyncMessageWithKbID(this, 'Keyboard:SendKey', {
- contextId: this._contextId,
- requestId: resolverId,
- method: 'sendKey',
- keyboardEventDict: this._getkeyboardEventDict(dictOrKeyCode)
- });
- });
- } else {
- // XXX: Should not reach here; implies WebIDL binding error.
- throw new TypeError('Unknown argument passed.');
- }
- },
- keydown: function ic_keydown(dict) {
- return this._sendPromise((resolverId) => {
- cpmmSendAsyncMessageWithKbID(this, 'Keyboard:SendKey', {
- contextId: this._contextId,
- requestId: resolverId,
- method: 'keydown',
- keyboardEventDict: this._getkeyboardEventDict(dict)
- });
- });
- },
- keyup: function ic_keyup(dict) {
- return this._sendPromise((resolverId) => {
- cpmmSendAsyncMessageWithKbID(this, 'Keyboard:SendKey', {
- contextId: this._contextId,
- requestId: resolverId,
- method: 'keyup',
- keyboardEventDict: this._getkeyboardEventDict(dict)
- });
- });
- },
- setComposition: function ic_setComposition(text, cursor, clauses, dict) {
- let self = this;
- return this._sendPromise((resolverId) => {
- cpmmSendAsyncMessageWithKbID(self, 'Keyboard:SetComposition', {
- contextId: self._contextId,
- requestId: resolverId,
- text: text,
- cursor: (typeof cursor !== 'undefined') ? cursor : text.length,
- clauses: clauses || null,
- keyboardEventDict: this._getkeyboardEventDict(dict)
- });
- });
- },
- endComposition: function ic_endComposition(text, dict) {
- let self = this;
- return this._sendPromise((resolverId) => {
- cpmmSendAsyncMessageWithKbID(self, 'Keyboard:EndComposition', {
- contextId: self._contextId,
- requestId: resolverId,
- text: text || '',
- keyboardEventDict: this._getkeyboardEventDict(dict)
- });
- });
- },
- // Generate a new keyboard event by the received keyboard dictionary
- // and return defaultPrevented's result of the event after dispatching.
- forwardHardwareKeyEvent: function ic_forwardHardwareKeyEvent(data) {
- if (!Ci.nsIHardwareKeyHandler) {
- return;
- }
- if (!this._context) {
- return Ci.nsIHardwareKeyHandler.NO_DEFAULT_PREVENTED;
- }
- let evt = new this._window.KeyboardEvent(data.type,
- Cu.cloneInto(data.keyDict,
- this._window));
- this._hardwareinput.__DOM_IMPL__.dispatchEvent(evt);
- return this._getDefaultPreventedValue(evt);
- },
- _getDefaultPreventedValue: function(evt) {
- if (!Ci.nsIHardwareKeyHandler) {
- return;
- }
- let flags = Ci.nsIHardwareKeyHandler.NO_DEFAULT_PREVENTED;
- if (evt.defaultPrevented) {
- flags |= Ci.nsIHardwareKeyHandler.DEFAULT_PREVENTED;
- }
- if (evt.defaultPreventedByChrome) {
- flags |= Ci.nsIHardwareKeyHandler.DEFAULT_PREVENTED_BY_CHROME;
- }
- if (evt.defaultPreventedByContent) {
- flags |= Ci.nsIHardwareKeyHandler.DEFAULT_PREVENTED_BY_CONTENT;
- }
- return flags;
- },
- _sendPromise: function(callback) {
- let self = this;
- return this._ipcHelper.createPromiseWithId(function(aResolverId) {
- if (!WindowMap.isActive(self._window)) {
- self._ipcHelper.removePromiseResolver(aResolverId);
- reject('Input method is not active.');
- return;
- }
- callback(aResolverId);
- });
- },
- // Take a MozInputMethodKeyboardEventDict dict, creates a keyboardEventDict
- // object that can be sent to forms.js
- _getkeyboardEventDict: function(dict) {
- if (typeof dict !== 'object' || !dict.key) {
- return;
- }
- var keyboardEventDict = {
- key: dict.key,
- code: dict.code,
- repeat: dict.repeat,
- flags: 0
- };
- if (dict.printable) {
- keyboardEventDict.flags |=
- Ci.nsITextInputProcessor.KEY_FORCE_PRINTABLE_KEY;
- }
- if (/^[a-zA-Z0-9]$/.test(dict.key)) {
- // keyCode must follow the key value in this range;
- // disregard the keyCode from content.
- keyboardEventDict.keyCode = dict.key.toUpperCase().charCodeAt(0);
- } else if (typeof dict.keyCode === 'number') {
- // Allow keyCode to be specified for other key values.
- keyboardEventDict.keyCode = dict.keyCode;
- // Allow keyCode to be explicitly set to zero.
- if (dict.keyCode === 0) {
- keyboardEventDict.flags |=
- Ci.nsITextInputProcessor.KEY_KEEP_KEYCODE_ZERO;
- }
- }
- return keyboardEventDict;
- }
- };
- this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MozInputMethod]);
|