mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 15:52:07 +00:00
Bug 1201407 - Add input-manage-only events for InputMethod API. r=janjongboom, sr=smaug
This commit is contained in:
parent
346978573f
commit
e284a1b7b0
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1135,6 +1135,8 @@ function getJSON(element, focusCounter) {
|
||||
switch (inputTypeLowerCase) {
|
||||
case "datetime":
|
||||
case "datetime-local":
|
||||
case "month":
|
||||
case "week":
|
||||
case "range":
|
||||
inputType = inputTypeLowerCase;
|
||||
break;
|
||||
|
@ -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]
|
||||
|
@ -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',
|
||||
|
230
dom/inputmethod/mochitest/test_focus_blur_manage_events.html
Normal file
230
dom/inputmethod/mochitest/test_focus_blur_manage_events.html
Normal file
@ -0,0 +1,230 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1201407
|
||||
-->
|
||||
<head>
|
||||
<title>Test inputcontextfocus and inputcontextblur event</title>
|
||||
<script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1201407">Mozilla Bug 1201407</a>
|
||||
<p id="display"></p>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="application/javascript;version=1.7">
|
||||
|
||||
let contentFrameMM;
|
||||
|
||||
function setupTestRunner() {
|
||||
info('setupTestRunner');
|
||||
let im = navigator.mozInputMethod;
|
||||
|
||||
let expectedEventDetails = [
|
||||
{ type: 'input', inputType: 'text' },
|
||||
{ type: 'input', inputType: 'search' },
|
||||
{ type: 'textarea', inputType: 'textarea' },
|
||||
{ type: 'contenteditable', inputType: 'textarea' },
|
||||
{ type: 'input', inputType: 'number' },
|
||||
{ type: 'input', inputType: 'tel' },
|
||||
{ type: 'input', inputType: 'url' },
|
||||
{ type: 'input', inputType: 'email' },
|
||||
{ type: 'input', inputType: 'password' },
|
||||
{ type: 'input', inputType: 'datetime' },
|
||||
{ type: 'input', inputType: 'date',
|
||||
value: '2015-08-03', min: '1990-01-01', max: '2020-01-01' },
|
||||
{ type: 'input', inputType: 'month' },
|
||||
{ type: 'input', inputType: 'week' },
|
||||
{ type: 'input', inputType: 'time' },
|
||||
{ type: 'input', inputType: 'datetime-local' },
|
||||
{ type: 'input', inputType: 'color' },
|
||||
{ type: 'select', inputType: 'select-one',
|
||||
choices: {
|
||||
multiple: false,
|
||||
choices: [
|
||||
{ group: false, inGroup: false, text: 'foo',
|
||||
disabled: false, selected: true, optionIndex: 0 },
|
||||
{ group: false, inGroup: false, text: 'bar',
|
||||
disabled: true, selected: false, optionIndex: 1 },
|
||||
{ group: true, text: 'group', disabled: false },
|
||||
{ group: false, inGroup: true, text: 'baz',
|
||||
disabled: false, selected: false, optionIndex: 2 } ] }
|
||||
},
|
||||
{ type: 'select', inputType: 'select-multiple',
|
||||
choices: {
|
||||
multiple: true,
|
||||
choices: [
|
||||
{ group: false, inGroup: false, text: 'foo',
|
||||
disabled: false, selected: true, optionIndex: 0 },
|
||||
{ group: false, inGroup: false, text: 'bar',
|
||||
disabled: true, selected: false, optionIndex: 1 },
|
||||
{ group: true, text: 'group', disabled: false },
|
||||
{ group: false, inGroup: true, text: 'baz',
|
||||
disabled: false, selected: false, optionIndex: 2 } ] }
|
||||
}
|
||||
];
|
||||
|
||||
let expectBlur = false;
|
||||
|
||||
function deepAssertObject(obj, expectedObj, desc) {
|
||||
for (let prop in expectedObj) {
|
||||
if (typeof expectedObj[prop] === 'object') {
|
||||
deepAssertObject(obj[prop], expectedObj[prop], desc + '.' + prop);
|
||||
} else {
|
||||
is(obj[prop], expectedObj[prop], desc + '.' + prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
im.mgmt.oninputcontextfocus =
|
||||
im.mgmt.oninputcontextblur = function(evt) {
|
||||
if (expectBlur) {
|
||||
is(evt.type, 'inputcontextblur', 'evt.type');
|
||||
evt.preventDefault();
|
||||
expectBlur = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let expectedEventDetail = expectedEventDetails.shift();
|
||||
|
||||
if (!expectedEventDetail) {
|
||||
ok(false, 'Receving extra events');
|
||||
inputmethod_cleanup();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
is(evt.type, 'inputcontextfocus', 'evt.type');
|
||||
evt.preventDefault();
|
||||
expectBlur = true;
|
||||
|
||||
let detail = evt.detail;
|
||||
deepAssertObject(detail, expectedEventDetail, 'detail');
|
||||
|
||||
if (expectedEventDetails.length) {
|
||||
contentFrameMM.sendAsyncMessage('test:next');
|
||||
} else {
|
||||
im.mgmt.oninputcontextfocus = im.mgmt.oninputcontextblur = null;
|
||||
inputmethod_cleanup();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function setupInputAppFrame() {
|
||||
info('setupInputAppFrame');
|
||||
return new Promise((resolve, reject) => {
|
||||
let appFrameScript = function appFrameScript() {
|
||||
let im = content.navigator.mozInputMethod;
|
||||
|
||||
im.mgmt.oninputcontextfocus =
|
||||
im.mgmt.oninputcontextblur = function(evt) {
|
||||
sendAsyncMessage('text:appEvent', { type: evt.type });
|
||||
};
|
||||
|
||||
content.document.body.textContent = 'I am a input app';
|
||||
};
|
||||
|
||||
let path = location.pathname;
|
||||
let basePath = location.protocol + '//' + location.host +
|
||||
path.substring(0, path.lastIndexOf('/'));
|
||||
let imeUrl = basePath + '/file_blank.html';
|
||||
|
||||
let inputAppFrame = document.createElement('iframe');
|
||||
inputAppFrame.setAttribute('mozbrowser', true);
|
||||
inputAppFrame.src = imeUrl;
|
||||
document.body.appendChild(inputAppFrame);
|
||||
|
||||
SpecialPowers.pushPermissions([{
|
||||
type: 'input',
|
||||
allow: true,
|
||||
context: {
|
||||
url: imeUrl,
|
||||
appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
|
||||
isInBrowserElement: true
|
||||
}
|
||||
}], function() {
|
||||
let mm = SpecialPowers.getBrowserFrameMessageManager(inputAppFrame);
|
||||
inputAppFrame.addEventListener('mozbrowserloadend', function() {
|
||||
mm.addMessageListener('text:appEvent', function(msg) {
|
||||
ok(false, 'Input app should not receive ' + msg.data.type + ' event.');
|
||||
});
|
||||
mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
|
||||
|
||||
// Set the input app frame to be active
|
||||
let req = inputAppFrame.setInputMethodActive(true);
|
||||
resolve(req);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupContentFrame() {
|
||||
info('setupContentFrame');
|
||||
return new Promise((resolve, reject) => {
|
||||
let contentFrameScript = function contentFrameScript() {
|
||||
let input = content.document.body.firstElementChild;
|
||||
|
||||
let i = 0;
|
||||
|
||||
input.focus();
|
||||
|
||||
addMessageListener('test:next', function() {
|
||||
content.document.body.children[++i].focus();
|
||||
});
|
||||
};
|
||||
|
||||
let iframe = document.createElement('iframe');
|
||||
iframe.src = 'data:text/html,<html><body>' +
|
||||
'<input type="text">' +
|
||||
'<input type="search">' +
|
||||
'<textarea></textarea>' +
|
||||
'<p contenteditable></p>' +
|
||||
'<input type="number">' +
|
||||
'<input type="tel">' +
|
||||
'<input type="url">' +
|
||||
'<input type="email">' +
|
||||
'<input type="password">' +
|
||||
'<input type="datetime">' +
|
||||
'<input type="date" value="2015-08-03" min="1990-01-01" max="2020-01-01">' +
|
||||
'<input type="month">' +
|
||||
'<input type="week">' +
|
||||
'<input type="time">' +
|
||||
'<input type="datetime-local">' +
|
||||
'<input type="color">' +
|
||||
'<select><option selected>foo</option><option disabled>bar</option>' +
|
||||
'<optgroup label="group"><option>baz</option></optgroup></select>' +
|
||||
'<select multiple><option selected>foo</option><option disabled>bar</option>' +
|
||||
'<optgroup label="group"><option>baz</option></optgroup></select>' +
|
||||
'</body></html>';
|
||||
iframe.setAttribute('mozbrowser', true);
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
let mm = contentFrameMM =
|
||||
SpecialPowers.getBrowserFrameMessageManager(iframe);
|
||||
|
||||
iframe.addEventListener('mozbrowserloadend', function() {
|
||||
mm.loadFrameScript('data:,(' + encodeURIComponent(contentFrameScript.toString()) + ')();', false);
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
inputmethod_setup(function() {
|
||||
Promise.resolve()
|
||||
.then(() => setupTestRunner())
|
||||
.then(() => setupContentFrame())
|
||||
.then(() => setupInputAppFrame())
|
||||
.catch((e) => {
|
||||
ok(false, 'Error' + e.toString());
|
||||
console.error(e);
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
259
dom/inputmethod/mochitest/test_input_registry_events.html
Normal file
259
dom/inputmethod/mochitest/test_input_registry_events.html
Normal file
@ -0,0 +1,259 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1201407
|
||||
-->
|
||||
<head>
|
||||
<title>Test addinputrequest and removeinputrequest event</title>
|
||||
<script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1201407">Mozilla Bug 1201407</a>
|
||||
<p id="display"></p>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="application/javascript;version=1.7">
|
||||
|
||||
let appFrameMM;
|
||||
let nextStep;
|
||||
|
||||
function setupInputAppFrame() {
|
||||
info('setupInputAppFrame');
|
||||
return new Promise((resolve, reject) => {
|
||||
let appFrameScript = function appFrameScript() {
|
||||
let im = content.navigator.mozInputMethod;
|
||||
|
||||
addMessageListener('test:callAddInput', function() {
|
||||
im.addInput('foo', {
|
||||
launch_path: 'bar.html',
|
||||
name: 'Foo',
|
||||
description: 'foobar',
|
||||
types: ['text', 'password']
|
||||
})
|
||||
.then((r) => {
|
||||
sendAsyncMessage('test:resolved', { resolved: true, result: r });
|
||||
}, (e) => {
|
||||
sendAsyncMessage('test:rejected', { rejected: true, error: e });
|
||||
});
|
||||
});
|
||||
|
||||
addMessageListener('test:callRemoveInput', function() {
|
||||
im.removeInput('foo')
|
||||
.then((r) => {
|
||||
sendAsyncMessage('test:resolved', { resolved: true, result: r });
|
||||
}, (e) => {
|
||||
sendAsyncMessage('test:rejected', { rejected: true, error: e });
|
||||
});
|
||||
});
|
||||
|
||||
im.mgmt.onaddinputrequest =
|
||||
im.mgmt.onremoveinputrequest = function(evt) {
|
||||
sendAsyncMessage('test:appEvent', { type: evt.type });
|
||||
};
|
||||
|
||||
content.document.body.textContent = 'I am a input app';
|
||||
};
|
||||
|
||||
let path = location.pathname;
|
||||
let basePath = location.protocol + '//' + location.host +
|
||||
path.substring(0, path.lastIndexOf('/'));
|
||||
let imeUrl = basePath + '/file_blank.html';
|
||||
|
||||
let inputAppFrame = document.createElement('iframe');
|
||||
inputAppFrame.setAttribute('mozbrowser', true);
|
||||
inputAppFrame.src = imeUrl;
|
||||
document.body.appendChild(inputAppFrame);
|
||||
|
||||
SpecialPowers.pushPermissions([{
|
||||
type: 'input',
|
||||
allow: true,
|
||||
context: {
|
||||
url: imeUrl,
|
||||
appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
|
||||
isInBrowserElement: true
|
||||
}
|
||||
}], function() {
|
||||
let mm = appFrameMM =
|
||||
SpecialPowers.getBrowserFrameMessageManager(inputAppFrame);
|
||||
|
||||
inputAppFrame.addEventListener('mozbrowserloadend', function() {
|
||||
mm.addMessageListener('test:appEvent', function(msg) {
|
||||
ok(false, 'Input app should not receive ' + msg.data.type + ' event.');
|
||||
});
|
||||
mm.addMessageListener('test:resolved', function(msg) {
|
||||
nextStep && nextStep(msg.data);
|
||||
});
|
||||
mm.addMessageListener('test:rejected', function(msg) {
|
||||
nextStep && nextStep(msg.data);
|
||||
});
|
||||
mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function Deferred() {
|
||||
this.promise = new Promise((res, rej) => {
|
||||
this.resolve = res;
|
||||
this.reject = rej;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
function deepAssertObject(obj, expectedObj, desc) {
|
||||
for (let prop in expectedObj) {
|
||||
if (typeof expectedObj[prop] === 'object') {
|
||||
deepAssertObject(obj[prop], expectedObj[prop], desc + '.' + prop);
|
||||
} else {
|
||||
is(obj[prop], expectedObj[prop], desc + '.' + prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupTestRunner() {
|
||||
let im = navigator.mozInputMethod;
|
||||
let d;
|
||||
|
||||
let i = -1;
|
||||
nextStep = function next(evt) {
|
||||
i++;
|
||||
info('Step ' + i);
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
appFrameMM.sendAsyncMessage('test:callAddInput');
|
||||
|
||||
break;
|
||||
|
||||
case 1:
|
||||
is(evt.type, 'addinputrequest', 'evt.type');
|
||||
deepAssertObject(evt.detail, {
|
||||
inputId: 'foo',
|
||||
manifestURL: null, // todo
|
||||
inputManifest: {
|
||||
launch_path: 'bar.html',
|
||||
name: 'Foo',
|
||||
description: 'foobar',
|
||||
types: ['text', 'password']
|
||||
}
|
||||
}, 'detail');
|
||||
|
||||
d = new Deferred();
|
||||
evt.detail.waitUntil(d.promise);
|
||||
evt.preventDefault();
|
||||
|
||||
Promise.resolve().then(next);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
d.resolve();
|
||||
d = null;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
ok(evt.resolved, 'resolved');
|
||||
appFrameMM.sendAsyncMessage('test:callAddInput');
|
||||
|
||||
break;
|
||||
|
||||
case 4:
|
||||
is(evt.type, 'addinputrequest', 'evt.type');
|
||||
|
||||
d = new Deferred();
|
||||
evt.detail.waitUntil(d.promise);
|
||||
evt.preventDefault();
|
||||
|
||||
Promise.resolve().then(next);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
d.reject('Foo Error');
|
||||
d = null;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
ok(evt.rejected, 'rejected');
|
||||
is(evt.error, 'Foo Error', 'rejected');
|
||||
|
||||
|
||||
appFrameMM.sendAsyncMessage('test:callRemoveInput');
|
||||
|
||||
break;
|
||||
|
||||
case 7:
|
||||
is(evt.type, 'removeinputrequest', 'evt.type');
|
||||
deepAssertObject(evt.detail, {
|
||||
inputId: 'foo',
|
||||
manifestURL: null // todo
|
||||
}, 'detail');
|
||||
|
||||
d = new Deferred();
|
||||
evt.detail.waitUntil(d.promise);
|
||||
evt.preventDefault();
|
||||
|
||||
Promise.resolve().then(next);
|
||||
break;
|
||||
|
||||
case 8:
|
||||
d.resolve();
|
||||
d = null;
|
||||
break;
|
||||
|
||||
case 9:
|
||||
ok(evt.resolved, 'resolved');
|
||||
appFrameMM.sendAsyncMessage('test:callRemoveInput');
|
||||
|
||||
break;
|
||||
|
||||
case 10:
|
||||
is(evt.type, 'removeinputrequest', 'evt.type');
|
||||
|
||||
d = new Deferred();
|
||||
evt.detail.waitUntil(d.promise);
|
||||
evt.preventDefault();
|
||||
|
||||
Promise.resolve().then(next);
|
||||
break;
|
||||
|
||||
case 11:
|
||||
d.reject('Foo Error');
|
||||
d = null;
|
||||
break;
|
||||
|
||||
case 12:
|
||||
ok(evt.rejected, 'rejected');
|
||||
is(evt.error, 'Foo Error', 'rejected');
|
||||
inputmethod_cleanup();
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
ok(false, 'received extra call.');
|
||||
inputmethod_cleanup();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
im.mgmt.onaddinputrequest =
|
||||
im.mgmt.onremoveinputrequest = nextStep;
|
||||
}
|
||||
|
||||
inputmethod_setup(function() {
|
||||
Promise.resolve()
|
||||
.then(() => setupTestRunner())
|
||||
.then(() => setupInputAppFrame())
|
||||
.then(() => nextStep())
|
||||
.catch((e) => {
|
||||
ok(false, 'Error' + e.toString());
|
||||
console.error(e);
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
164
dom/inputmethod/mochitest/test_simple_manage_events.html
Normal file
164
dom/inputmethod/mochitest/test_simple_manage_events.html
Normal file
@ -0,0 +1,164 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1201407
|
||||
-->
|
||||
<head>
|
||||
<title>Test simple manage notification events on MozInputMethodManager</title>
|
||||
<script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1201407">Mozilla Bug 1201407</a>
|
||||
<p id="display"></p>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="application/javascript;version=1.7">
|
||||
|
||||
let appFrameMM;
|
||||
let nextStep;
|
||||
|
||||
function setupTestRunner() {
|
||||
info('setupTestRunner');
|
||||
let im = navigator.mozInputMethod;
|
||||
|
||||
let i = 0;
|
||||
im.mgmt.onshowallrequest =
|
||||
im.mgmt.onnextrequest = nextStep = function(evt) {
|
||||
i++;
|
||||
switch (i) {
|
||||
case 1:
|
||||
is(evt.type, 'inputcontextchange', '1) inputcontextchange event');
|
||||
appFrameMM.sendAsyncMessage('test:callShowAll');
|
||||
|
||||
break;
|
||||
|
||||
case 2:
|
||||
is(evt.type, 'showallrequest', '2) showallrequest event');
|
||||
ok(evt.target, im.mgmt, '2) evt.target');
|
||||
evt.preventDefault();
|
||||
|
||||
appFrameMM.sendAsyncMessage('test:callNext');
|
||||
|
||||
break;
|
||||
|
||||
case 3:
|
||||
is(evt.type, 'nextrequest', '3) nextrequest event');
|
||||
ok(evt.target, im.mgmt, '3) evt.target');
|
||||
evt.preventDefault();
|
||||
|
||||
im.mgmt.onshowallrequest =
|
||||
im.mgmt.onnextrequest = nextStep = null;
|
||||
|
||||
inputmethod_cleanup();
|
||||
break;
|
||||
|
||||
default:
|
||||
ok(false, 'Receving extra events');
|
||||
inputmethod_cleanup();
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function setupInputAppFrame() {
|
||||
info('setupInputAppFrame');
|
||||
return new Promise((resolve, reject) => {
|
||||
let appFrameScript = function appFrameScript() {
|
||||
let im = content.navigator.mozInputMethod;
|
||||
|
||||
addMessageListener('test:callShowAll', function() {
|
||||
im.mgmt.showAll();
|
||||
});
|
||||
|
||||
addMessageListener('test:callNext', function() {
|
||||
im.mgmt.next();
|
||||
});
|
||||
|
||||
im.mgmt.onshowallrequest =
|
||||
im.mgmt.onnextrequest = function(evt) {
|
||||
sendAsyncMessage('test:appEvent', { type: evt.type });
|
||||
};
|
||||
|
||||
im.oninputcontextchange = function(evt) {
|
||||
sendAsyncMessage('test:inputcontextchange', {});
|
||||
};
|
||||
|
||||
content.document.body.textContent = 'I am a input app';
|
||||
};
|
||||
|
||||
let path = location.pathname;
|
||||
let basePath = location.protocol + '//' + location.host +
|
||||
path.substring(0, path.lastIndexOf('/'));
|
||||
let imeUrl = basePath + '/file_blank.html';
|
||||
|
||||
let inputAppFrame = document.createElement('iframe');
|
||||
inputAppFrame.setAttribute('mozbrowser', true);
|
||||
inputAppFrame.src = imeUrl;
|
||||
document.body.appendChild(inputAppFrame);
|
||||
|
||||
SpecialPowers.pushPermissions([{
|
||||
type: 'input',
|
||||
allow: true,
|
||||
context: {
|
||||
url: imeUrl,
|
||||
appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
|
||||
isInBrowserElement: true
|
||||
}
|
||||
}], function() {
|
||||
let mm = appFrameMM =
|
||||
SpecialPowers.getBrowserFrameMessageManager(inputAppFrame);
|
||||
|
||||
inputAppFrame.addEventListener('mozbrowserloadend', function() {
|
||||
mm.addMessageListener('test:appEvent', function(msg) {
|
||||
ok(false, 'Input app should not receive ' + msg.data.type + ' event.');
|
||||
});
|
||||
mm.addMessageListener('test:inputcontextchange', function(msg) {
|
||||
nextStep && nextStep({ type: 'inputcontextchange' });
|
||||
});
|
||||
mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
|
||||
|
||||
// Set the input app frame to be active
|
||||
let req = inputAppFrame.setInputMethodActive(true);
|
||||
resolve(req);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupContentFrame() {
|
||||
let contentFrameScript = function contentFrameScript() {
|
||||
let input = content.document.body.firstElementChild;
|
||||
|
||||
input.focus();
|
||||
};
|
||||
|
||||
let iframe = document.createElement('iframe');
|
||||
iframe.src = 'data:text/html,<html><body><input type="text"></body></html>';
|
||||
iframe.setAttribute('mozbrowser', true);
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
|
||||
|
||||
iframe.addEventListener('mozbrowserloadend', function() {
|
||||
mm.loadFrameScript('data:,(' + encodeURIComponent(contentFrameScript.toString()) + ')();', false);
|
||||
});
|
||||
}
|
||||
|
||||
inputmethod_setup(function() {
|
||||
Promise.resolve()
|
||||
.then(() => setupTestRunner())
|
||||
.then(() => setupContentFrame())
|
||||
.then(() => setupInputAppFrame())
|
||||
.catch((e) => {
|
||||
ok(false, 'Error' + e.toString());
|
||||
console.error(e);
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -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<MozInputMethodInputContextInputTypes> 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 <optgroup> and <option> elements
|
||||
* in the <select> element.
|
||||
*/
|
||||
[Pure, Cached, Frozen]
|
||||
readonly attribute MozInputContextChoicesInfo? choices;
|
||||
/**
|
||||
* Max/min value of <input>
|
||||
*/
|
||||
readonly attribute DOMString? min;
|
||||
readonly attribute DOMString? max;
|
||||
};
|
||||
|
||||
/**
|
||||
* Information about the options within the <select> element.
|
||||
*/
|
||||
dictionary MozInputContextChoicesInfo {
|
||||
boolean multiple;
|
||||
sequence<MozInputMethodChoiceDict> choices;
|
||||
};
|
||||
|
||||
/**
|
||||
* Content the header (<optgroup>) or an option (<option>).
|
||||
*/
|
||||
dictionary MozInputMethodChoiceDict {
|
||||
boolean group;
|
||||
DOMString text;
|
||||
boolean disabled;
|
||||
boolean? inGroup;
|
||||
boolean? selected;
|
||||
long? optionIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* detail of addinputrequest or removeinputrequest event.
|
||||
*/
|
||||
[JSImplementation="@mozilla.org/b2g-imm-input-registry;1",
|
||||
Pref="dom.mozInputMethod.enabled",
|
||||
CheckAnyPermissions="input-manage"]
|
||||
interface MozInputRegistryEventDetail {
|
||||
/**
|
||||
* Manifest URL of the requesting app.
|
||||
*/
|
||||
readonly attribute DOMString manifestURL;
|
||||
/**
|
||||
* ID of the input
|
||||
*/
|
||||
readonly attribute DOMString inputId;
|
||||
/**
|
||||
* Input manifest of the input to add.
|
||||
* Null for removeinputrequest event.
|
||||
*/
|
||||
[Pure, Cached, Frozen]
|
||||
readonly attribute MozInputMethodInputManifest? inputManifest;
|
||||
/**
|
||||
* Resolve or Reject the addInput() or removeInput() call when the passed
|
||||
* promises are resolved.
|
||||
*/
|
||||
[Throws]
|
||||
void waitUntil(Promise<any> p);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -397,19 +540,20 @@ dictionary CompositionClauseParameters {
|
||||
* *and* the special keyword "contenteditable" for contenteditable element.
|
||||
*/
|
||||
enum MozInputMethodInputContextTypes {
|
||||
"input", "textarea", "contenteditable"
|
||||
"input", "textarea", "contenteditable",
|
||||
/**
|
||||
* <select> is managed by the API but it's not exposed through InputContext
|
||||
* yet.
|
||||
* <select> is managed by the API but it is handled by the System app only,
|
||||
* so this value is only accessible by System app from inputcontextfocus event.
|
||||
*/
|
||||
// "select"
|
||||
"select"
|
||||
};
|
||||
|
||||
/**
|
||||
* InputTypes of the input that InputContext is representing. The value
|
||||
* is inferred from the type attribute of input element.
|
||||
* is inferred from the type attribute of element.
|
||||
*
|
||||
* See https://html.spec.whatwg.org/multipage/forms.html#states-of-the-type-attribute
|
||||
* for types of HTMLInputElement.
|
||||
*
|
||||
* They are divided into groups -- an layout/input capable of handling one type
|
||||
* in the group is considered as capable of handling all of the types in the
|
||||
@ -444,12 +588,15 @@ enum MozInputMethodInputContextInputTypes {
|
||||
* Group "password".
|
||||
* An non-Latin alphabet layout/input should not be able to handle this type.
|
||||
*/
|
||||
"password"
|
||||
"password",
|
||||
/**
|
||||
* Group "option". These types are handled by System app itself currently, and
|
||||
* not exposed and allowed to handled with input context.
|
||||
* Group "option". These types are handled by System app itself currently, so
|
||||
* no input app will be set to active for these input types.
|
||||
* System app access these types from inputcontextfocus event.
|
||||
* ("select-one" and "select-multiple" are valid HTMLSelectElement#type.)
|
||||
*/
|
||||
//"datetime", "date", "month", "week", "time", "datetime-local", "color",
|
||||
"datetime", "date", "month", "week", "time", "datetime-local", "color",
|
||||
"select-one", "select-multiple"
|
||||
/**
|
||||
* These types are ignored by the API even though they are valid
|
||||
* HTMLInputElement#type.
|
||||
|
Loading…
Reference in New Issue
Block a user