From e284a1b7b014a2118010c754b283b7549703378b Mon Sep 17 00:00:00 2001 From: Tim Chien Date: Wed, 16 Sep 2015 22:11:00 +0200 Subject: [PATCH] Bug 1201407 - Add input-manage-only events for InputMethod API. r=janjongboom, sr=smaug --- dom/inputmethod/Keyboard.jsm | 93 +++++- dom/inputmethod/MozKeyboard.js | 290 +++++++++++++++++- dom/inputmethod/forms.js | 2 + ...putmethod_1043828.html => file_blank.html} | 0 dom/inputmethod/mochitest/mochitest.ini | 5 +- .../mochitest/test_bug1043828.html | 2 +- .../test_focus_blur_manage_events.html | 230 ++++++++++++++ .../mochitest/test_input_registry_events.html | 259 ++++++++++++++++ .../mochitest/test_simple_manage_events.html | 164 ++++++++++ dom/webidl/InputMethod.webidl | 167 +++++++++- 10 files changed, 1185 insertions(+), 27 deletions(-) rename dom/inputmethod/mochitest/{file_inputmethod_1043828.html => file_blank.html} (100%) create mode 100644 dom/inputmethod/mochitest/test_focus_blur_manage_events.html create mode 100644 dom/inputmethod/mochitest/test_input_registry_events.html create mode 100644 dom/inputmethod/mochitest/test_simple_manage_events.html diff --git a/dom/inputmethod/Keyboard.jsm b/dom/inputmethod/Keyboard.jsm index 177c49200b6e..84d8db1c920a 100644 --- a/dom/inputmethod/Keyboard.jsm +++ b/dom/inputmethod/Keyboard.jsm @@ -45,10 +45,12 @@ this.Keyboard = { _keyboardMM: null, // The keyboard app message manager. _keyboardID: -1, // The keyboard app's ID number. -1 = invalid _nextKeyboardID: 0, // The ID number counter. + _systemMMs: [], // The message managers registered to handle system async + // messages. _supportsSwitchingTypes: [], _systemMessageNames: [ 'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions', - 'SetSupportsSwitchingTypes' + 'SetSupportsSwitchingTypes', 'RegisterSync', 'Unregister' ], _messageNames: [ @@ -57,7 +59,7 @@ this.Keyboard = { 'SwitchToNextInputMethod', 'HideInputMethod', 'GetText', 'SendKey', 'GetContext', 'SetComposition', 'EndComposition', - 'Register', 'Unregister' + 'RegisterSync', 'Unregister' ], get formMM() { @@ -89,6 +91,20 @@ this.Keyboard = { } catch(e) { } }, + sendToSystem: function(name, data) { + if (!this._systemMMs.length) { + dump("Keyboard.jsm: Attempt to send message " + name + + " to system but no message manager registered.\n"); + + return; + } + + this._systemMMs.forEach((mm, i) => { + data.inputManageId = i; + mm.sendAsyncMessage(name, data); + }); + }, + init: function keyboardInit() { Services.obs.addObserver(this, 'inprocess-browser-shown', false); Services.obs.addObserver(this, 'remote-browser-shown', false); @@ -124,10 +140,14 @@ this.Keyboard = { // keyboard app that the focus has been lost. this.sendToKeyboard('Keyboard:Blur', {}); // Notify system app to hide keyboard. + this.sendToSystem('System:Blur', {}); + // XXX: To be removed when content migrate away from mozChromeEvents. SystemAppProxy.dispatchEvent({ type: 'inputmethod-contextchange', inputType: 'blur' }); + + this.formMM = null; } } else { // Ignore notifications that aren't from a BrowserOrApp @@ -193,7 +213,7 @@ this.Keyboard = { } if (0 === msg.name.indexOf('Keyboard:') && - ('Keyboard:Register' !== msg.name && this._keyboardID !== kbID) + ('Keyboard:RegisterSync' !== msg.name && this._keyboardID !== kbID) ) { return; } @@ -228,6 +248,24 @@ this.Keyboard = { case 'Keyboard:RemoveFocus': case 'System:RemoveFocus': this.removeFocus(); + break; + case 'System:RegisterSync': { + if (this._systemMMs.length !== 0) { + dump('Keyboard.jsm Warning: There are more than one content page ' + + 'with input-manage permission. There will be undeterministic ' + + 'responses to addInput()/removeInput() if both content pages are ' + + 'trying to respond to the same request event.\n'); + } + + let id = this._systemMMs.length; + this._systemMMs.push(mm); + + return id; + } + + case 'System:Unregister': + this._systemMMs.splice(msg.data.id, 1); + break; case 'System:SetSelectedOption': this.setSelectedOption(msg); @@ -265,7 +303,7 @@ this.Keyboard = { case 'Keyboard:EndComposition': this.endComposition(msg); break; - case 'Keyboard:Register': + case 'Keyboard:RegisterSync': this._keyboardMM = mm; if (kbID) { // keyboard identifies itself, use its kbID @@ -293,10 +331,14 @@ this.Keyboard = { .frameLoader.messageManager; this.formMM = mm; + // Notify the current active input app to gain focus. this.forwardEvent('Keyboard:Focus', msg); - // Chrome event, used also to render value selectors; that's why we need - // the info about choices / min / max here as well... + // Notify System app, used also to render value selectors for now; + // that's why we need the info about choices / min / max here as well... + this.sendToSystem('System:Focus', msg.data); + + // XXX: To be removed when content migrate away from mozChromeEvents. SystemAppProxy.dispatchEvent({ type: 'inputmethod-contextchange', inputType: msg.data.inputType, @@ -322,7 +364,9 @@ this.Keyboard = { this.formMM = null; this.forwardEvent('Keyboard:Blur', msg); + this.sendToSystem('System:Blur', {}); + // XXX: To be removed when content migrate away from mozChromeEvents. SystemAppProxy.dispatchEvent({ type: 'inputmethod-contextchange', inputType: 'blur' @@ -362,12 +406,18 @@ this.Keyboard = { }, showInputMethodPicker: function keyboardShowInputMethodPicker() { + this.sendToSystem('System:ShowAll', {}); + + // XXX: To be removed with mozContentEvent support from shell.js SystemAppProxy.dispatchEvent({ type: "inputmethod-showall" }); }, switchToNextInputMethod: function keyboardSwitchToNextInputMethod() { + this.sendToSystem('System:Next', {}); + + // XXX: To be removed with mozContentEvent support from shell.js SystemAppProxy.dispatchEvent({ type: "inputmethod-next" }); @@ -432,14 +482,17 @@ function InputRegistryGlue() { ppmm.addMessageListener('InputRegistry:Add', this); ppmm.addMessageListener('InputRegistry:Remove', this); + ppmm.addMessageListener('System:InputRegistry:Add:Done', this); + ppmm.addMessageListener('System:InputRegistry:Remove:Done', this); }; InputRegistryGlue.prototype.receiveMessage = function(msg) { let mm = Utils.getMMFromMessage(msg); - if (!Utils.checkPermissionForMM(mm, 'input')) { + let permName = msg.name.startsWith("System:") ? "input-mgmt" : "input"; + if (!Utils.checkPermissionForMM(mm, permName)) { dump("InputRegistryGlue message " + msg.name + - " from a content process with no 'input' privileges."); + " from a content process with no " + permName + " privileges."); return; } @@ -452,6 +505,12 @@ InputRegistryGlue.prototype.receiveMessage = function(msg) { case 'InputRegistry:Remove': this.removeInput(msg, mm); + break; + + case 'System:InputRegistry:Add:Done': + case 'System:InputRegistry:Remove:Done': + this.returnMessage(msg.data); + break; } }; @@ -465,6 +524,14 @@ InputRegistryGlue.prototype.addInput = function(msg, mm) { let manifestURL = appsService.getManifestURLByLocalId(msg.data.appId); + Keyboard.sendToSystem('System:InputRegistry:Add', { + id: msgId, + manifestURL: manifestURL, + inputId: msg.data.inputId, + inputManifest: msg.data.inputManifest + }); + + // XXX: To be removed when content migrate away from mozChromeEvents. SystemAppProxy.dispatchEvent({ type: 'inputregistry-add', id: msgId, @@ -483,6 +550,13 @@ InputRegistryGlue.prototype.removeInput = function(msg, mm) { let manifestURL = appsService.getManifestURLByLocalId(msg.data.appId); + Keyboard.sendToSystem('System:InputRegistry:Remove', { + id: msgId, + manifestURL: manifestURL, + inputId: msg.data.inputId + }); + + // XXX: To be removed when content migrate away from mozChromeEvents. SystemAppProxy.dispatchEvent({ type: 'inputregistry-remove', id: msgId, @@ -493,6 +567,8 @@ InputRegistryGlue.prototype.removeInput = function(msg, mm) { InputRegistryGlue.prototype.returnMessage = function(detail) { if (!this._msgMap.has(detail.id)) { + dump('InputRegistryGlue: Ignoring already handled message response. ' + + 'id=' + detail.id + '\n'); return; } @@ -500,6 +576,7 @@ InputRegistryGlue.prototype.returnMessage = function(detail) { this._msgMap.delete(detail.id); if (Cu.isDeadWrapper(mm)) { + dump('InputRegistryGlue: Message manager has already died.\n'); return; } diff --git a/dom/inputmethod/MozKeyboard.js b/dom/inputmethod/MozKeyboard.js index 7f6c7e7a5d23..1a740b930a7c 100644 --- a/dom/inputmethod/MozKeyboard.js +++ b/dom/inputmethod/MozKeyboard.js @@ -7,6 +7,7 @@ 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"); @@ -143,6 +144,54 @@ MozInputMethodManager.prototype = { 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; @@ -175,6 +224,169 @@ MozInputMethodManager.prototype = { 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; } }; @@ -188,10 +400,13 @@ function MozInputMethod() { } MozInputMethod.prototype = { __proto__: DOMRequestIpcHelper.prototype, + _window: null, _inputcontext: null, _wrappedInputContext: null, + _mgmt: null, + _wrappedMgmt: null, _supportsSwitchingTypes: [], - _window: null, + _inputManageId: undefined, classID: Components.ID("{4607330d-e7d2-40a4-9eb8-43967eae0142}"), @@ -204,6 +419,7 @@ MozInputMethod.prototype = { 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; @@ -217,11 +433,22 @@ MozInputMethod.prototype = { cpmm.addWeakMessageListener('Keyboard:SupportsSwitchingTypesChange', 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); @@ -231,15 +458,34 @@ MozInputMethod.prototype = { 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('InputRegistry') && + 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; @@ -272,6 +518,30 @@ MozInputMethod.prototype = { 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; } }, @@ -282,7 +552,7 @@ MozInputMethod.prototype = { }, get mgmt() { - return this._mgmt; + return this._wrappedMgmt; }, get inputcontext() { @@ -320,8 +590,7 @@ MozInputMethod.prototype = { this._window.MozInputContext._create(this._window, this._inputcontext); } - let event = new this._window.Event("inputcontextchange", - Cu.cloneInto({}, this._window)); + let event = new this._window.Event("inputcontextchange"); this.__DOM_IMPL__.dispatchEvent(event); }, @@ -344,9 +613,9 @@ MozInputMethod.prototype = { // we have to use a synchronous message var kbID = WindowMap.getKbID(this._window); if (kbID) { - cpmmSendAsyncMessageWithKbID(this, 'Keyboard:Register', {}); + cpmmSendAsyncMessageWithKbID(this, 'Keyboard:RegisterSync', {}); } else { - let res = cpmm.sendSyncMessage('Keyboard:Register', {}); + let res = cpmm.sendSyncMessage('Keyboard:RegisterSync', {}); WindowMap.setKbID(this._window, res[0]); } @@ -405,6 +674,13 @@ MozInputMethod.prototype = { removeFocus: function() { cpmm.sendAsyncMessage('System:RemoveFocus', {}); + }, + + _hasInputManagePerm: function(win) { + let principal = win.document.nodePrincipal; + let perm = Services.perms.testExactPermissionFromPrincipal(principal, + "input-manage"); + return (perm === Ci.nsIPermissionManager.ALLOW_ACTION); } }; diff --git a/dom/inputmethod/forms.js b/dom/inputmethod/forms.js index 0a7d151c0052..0510b08dce63 100644 --- a/dom/inputmethod/forms.js +++ b/dom/inputmethod/forms.js @@ -1135,6 +1135,8 @@ function getJSON(element, focusCounter) { switch (inputTypeLowerCase) { case "datetime": case "datetime-local": + case "month": + case "week": case "range": inputType = inputTypeLowerCase; break; diff --git a/dom/inputmethod/mochitest/file_inputmethod_1043828.html b/dom/inputmethod/mochitest/file_blank.html similarity index 100% rename from dom/inputmethod/mochitest/file_inputmethod_1043828.html rename to dom/inputmethod/mochitest/file_blank.html diff --git a/dom/inputmethod/mochitest/mochitest.ini b/dom/inputmethod/mochitest/mochitest.ini index 3c2614e67fbd..2b7652e602d4 100644 --- a/dom/inputmethod/mochitest/mochitest.ini +++ b/dom/inputmethod/mochitest/mochitest.ini @@ -4,7 +4,7 @@ skip-if = (toolkit == 'android' || toolkit == 'gonk') || e10s support-files = inputmethod_common.js file_inputmethod.html - file_inputmethod_1043828.html + file_blank.html file_test_app.html file_test_sendkey_cancel.html file_test_sms_app.html @@ -22,8 +22,11 @@ support-files = [test_bug1066515.html] [test_bug1175399.html] [test_bug1137557.html] +[test_focus_blur_manage_events.html] +[test_input_registry_events.html] [test_sendkey_cancel.html] [test_setSupportsSwitching.html] +[test_simple_manage_events.html] [test_sync_edit.html] [test_two_inputs.html] [test_two_selects.html] diff --git a/dom/inputmethod/mochitest/test_bug1043828.html b/dom/inputmethod/mochitest/test_bug1043828.html index 5325ab722abd..0dae3e6a9d4e 100644 --- a/dom/inputmethod/mochitest/test_bug1043828.html +++ b/dom/inputmethod/mochitest/test_bug1043828.html @@ -84,7 +84,7 @@ function runTest() { document.body.appendChild(keyboardB); // simulate two different keyboard apps - let imeUrl = basePath + '/file_inputmethod_1043828.html'; + let imeUrl = basePath + '/file_blank.html'; SpecialPowers.pushPermissions([{ type: 'input', diff --git a/dom/inputmethod/mochitest/test_focus_blur_manage_events.html b/dom/inputmethod/mochitest/test_focus_blur_manage_events.html new file mode 100644 index 000000000000..9bea186fbd47 --- /dev/null +++ b/dom/inputmethod/mochitest/test_focus_blur_manage_events.html @@ -0,0 +1,230 @@ + + + + + Test inputcontextfocus and inputcontextblur event + + + + + +Mozilla Bug 1201407 +

+
+
+
+ + + diff --git a/dom/inputmethod/mochitest/test_input_registry_events.html b/dom/inputmethod/mochitest/test_input_registry_events.html new file mode 100644 index 000000000000..bd93ae8b37a8 --- /dev/null +++ b/dom/inputmethod/mochitest/test_input_registry_events.html @@ -0,0 +1,259 @@ + + + + + Test addinputrequest and removeinputrequest event + + + + + +Mozilla Bug 1201407 +

+
+
+
+ + diff --git a/dom/inputmethod/mochitest/test_simple_manage_events.html b/dom/inputmethod/mochitest/test_simple_manage_events.html new file mode 100644 index 000000000000..88bd344e8d81 --- /dev/null +++ b/dom/inputmethod/mochitest/test_simple_manage_events.html @@ -0,0 +1,164 @@ + + + + + Test simple manage notification events on MozInputMethodManager + + + + + +Mozilla Bug 1201407 +

+
+
+
+ + + diff --git a/dom/webidl/InputMethod.webidl b/dom/webidl/InputMethod.webidl index 46e5963eb7a0..7214f27ff0a0 100644 --- a/dom/webidl/InputMethod.webidl +++ b/dom/webidl/InputMethod.webidl @@ -120,7 +120,7 @@ interface MozInputMethod : EventTarget { [JSImplementation="@mozilla.org/b2g-imm;1", Pref="dom.mozInputMethod.enabled", CheckAnyPermissions="input input-manage"] -interface MozInputMethodManager { +interface MozInputMethodManager : EventTarget { /** * Ask the OS to show a list of available inputs for users to switch from. * OS should sliently ignore this request if the app is currently not the @@ -165,6 +165,149 @@ interface MozInputMethodManager { */ [CheckAllPermissions="input-manage"] void setSupportsSwitchingTypes(sequence types); + + /** + * CustomEvent dispatches to System when there is an input to handle. + * If the API consumer failed to handle and call preventDefault(), + * there will be a message printed on the console. + * + * evt.detail is defined by MozInputContextFocusEventDetail. + */ + [CheckAnyPermissions="input-manage"] + attribute EventHandler oninputcontextfocus; + + /** + * Event dispatches to System when there is no longer an input to handle. + * If the API consumer failed to handle and call preventDefault(), + * there will be a message printed on the console. + */ + [CheckAnyPermissions="input-manage"] + attribute EventHandler oninputcontextblur; + + /** + * Event dispatches to System when there is a showAll() call. + * If the API consumer failed to handle and call preventDefault(), + * there will be a message printed on the console. + */ + [CheckAnyPermissions="input-manage"] + attribute EventHandler onshowallrequest; + + /** + * Event dispatches to System when there is a next() call. + * If the API consumer failed to handle and call preventDefault(), + * there will be a message printed on the console. + */ + [CheckAnyPermissions="input-manage"] + attribute EventHandler onnextrequest; + + /** + * Event dispatches to System when there is a addInput() call. + * The API consumer must call preventDefault() to indicate the event is + * consumed, otherwise the request is not considered handled even if + * waitUntil() was called. + * + * evt.detail is defined by MozInputRegistryEventDetail. + */ + [CheckAnyPermissions="input-manage"] + attribute EventHandler onaddinputrequest; + + /** + * Event dispatches to System when there is a removeInput() call. + * The API consumer must call preventDefault() to indicate the event is + * consumed, otherwise the request is not considered handled even if + * waitUntil() was called. + * + * evt.detail is defined by MozInputRegistryEventDetail. + */ + [CheckAnyPermissions="input-manage"] + attribute EventHandler onremoveinputrequest; +}; + +/** + * Detail of the inputcontextfocus event. + */ +[JSImplementation="@mozilla.org/b2g-imm-focus;1", + Pref="dom.mozInputMethod.enabled", + CheckAnyPermissions="input-manage"] +interface MozInputContextFocusEventDetail { + /** + * The type of the focused input. + */ + readonly attribute MozInputMethodInputContextTypes type; + /** + * The input type of the focused input. + */ + readonly attribute MozInputMethodInputContextInputTypes inputType; + + /** + * The following is only needed for rendering and handling "option" input types, + * in System app. + */ + + /** + * Current value of the input/select element. + */ + readonly attribute DOMString? value; + /** + * An object representing all the and