mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 899999 - Implement new InputContext API. r=yxl
This commit is contained in:
parent
26b36f402d
commit
db365c9dce
@ -194,6 +194,9 @@ let FormAssistant = {
|
||||
addMessageListener("Forms:Select:Blur", this);
|
||||
addMessageListener("Forms:SetSelectionRange", this);
|
||||
addMessageListener("Forms:ReplaceSurroundingText", this);
|
||||
addMessageListener("Forms:GetText", this);
|
||||
addMessageListener("Forms:Input:SendKey", this);
|
||||
addMessageListener("Forms:GetContext", this);
|
||||
},
|
||||
|
||||
ignoredInputTypes: new Set([
|
||||
@ -203,8 +206,11 @@ let FormAssistant = {
|
||||
isKeyboardOpened: false,
|
||||
selectionStart: -1,
|
||||
selectionEnd: -1,
|
||||
textBeforeCursor: "",
|
||||
textAfterCursor: "",
|
||||
scrollIntoViewTimeout: null,
|
||||
_focusedElement: null,
|
||||
_focusCounter: 0, // up one for every time we focus a new element
|
||||
_documentEncoder: null,
|
||||
_editor: null,
|
||||
_editing: false,
|
||||
@ -218,6 +224,7 @@ let FormAssistant = {
|
||||
},
|
||||
|
||||
set focusedElement(val) {
|
||||
this._focusCounter++;
|
||||
this._focusedElement = val;
|
||||
},
|
||||
|
||||
@ -390,12 +397,34 @@ let FormAssistant = {
|
||||
|
||||
receiveMessage: function fa_receiveMessage(msg) {
|
||||
let target = this.focusedElement;
|
||||
let json = msg.json;
|
||||
|
||||
// To not break mozKeyboard contextId is optional
|
||||
if ('contextId' in json &&
|
||||
json.contextId !== this._focusCounter &&
|
||||
json.requestId) {
|
||||
// Ignore messages that are meant for a previously focused element
|
||||
sendAsyncMessage("Forms:SequenceError", {
|
||||
requestId: json.requestId,
|
||||
error: "Expected contextId " + this._focusCounter +
|
||||
" but was " + json.contextId
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
switch (msg.name) {
|
||||
case "Forms:GetText":
|
||||
sendAsyncMessage("Forms:GetText:Result:Error", {
|
||||
requestId: json.requestId,
|
||||
error: "No focused element"
|
||||
});
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this._editing = true;
|
||||
let json = msg.json;
|
||||
switch (msg.name) {
|
||||
case "Forms:Input:Value": {
|
||||
target.value = json.value;
|
||||
@ -406,6 +435,19 @@ let FormAssistant = {
|
||||
break;
|
||||
}
|
||||
|
||||
case "Forms:Input:SendKey":
|
||||
["keydown", "keypress", "keyup"].forEach(function(type) {
|
||||
domWindowUtils.sendKeyEvent(type, json.keyCode, json.charCode,
|
||||
json.modifiers);
|
||||
});
|
||||
|
||||
if (json.requestId) {
|
||||
sendAsyncMessage("Forms:SendKey:Result:OK", {
|
||||
requestId: json.requestId
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "Forms:Select:Choice":
|
||||
let options = target.options;
|
||||
let valueChanged = false;
|
||||
@ -442,6 +484,13 @@ let FormAssistant = {
|
||||
let end = json.selectionEnd;
|
||||
setSelectionRange(target, start, end);
|
||||
this.updateSelection();
|
||||
|
||||
if (json.requestId) {
|
||||
sendAsyncMessage("Forms:SetSelectionRange:Result:OK", {
|
||||
requestId: json.requestId,
|
||||
selectioninfo: this.getSelectionInfo()
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -449,8 +498,44 @@ let FormAssistant = {
|
||||
let text = json.text;
|
||||
let beforeLength = json.beforeLength;
|
||||
let afterLength = json.afterLength;
|
||||
replaceSurroundingText(target, text, this.selectionStart, beforeLength,
|
||||
let selectionRange = getSelectionRange(target);
|
||||
|
||||
replaceSurroundingText(target, text, selectionRange[0], beforeLength,
|
||||
afterLength);
|
||||
|
||||
if (json.requestId) {
|
||||
sendAsyncMessage("Forms:ReplaceSurroundingText:Result:OK", {
|
||||
requestId: json.requestId,
|
||||
selectioninfo: this.getSelectionInfo()
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Forms:GetText": {
|
||||
let isPlainTextField = target instanceof HTMLInputElement ||
|
||||
target instanceof HTMLTextAreaElement;
|
||||
let value = isPlainTextField ?
|
||||
target.value :
|
||||
getContentEditableText(target);
|
||||
|
||||
if (json.offset && json.length) {
|
||||
value = value.substr(json.offset, json.length);
|
||||
}
|
||||
else if (json.offset) {
|
||||
value = value.substr(json.offset);
|
||||
}
|
||||
|
||||
sendAsyncMessage("Forms:GetText:Result:OK", {
|
||||
requestId: json.requestId,
|
||||
text: value
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "Forms:GetContext": {
|
||||
let obj = getJSON(target, this._focusCounter);
|
||||
sendAsyncMessage("Forms:GetContext:Result:OK", obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -529,20 +614,47 @@ let FormAssistant = {
|
||||
return false;
|
||||
}
|
||||
|
||||
sendAsyncMessage("Forms:Input", getJSON(element));
|
||||
sendAsyncMessage("Forms:Input", getJSON(element, this._focusCounter));
|
||||
return true;
|
||||
},
|
||||
|
||||
getSelectionInfo: function fa_getSelectionInfo() {
|
||||
let element = this.focusedElement;
|
||||
let range = getSelectionRange(element);
|
||||
|
||||
let isPlainTextField = element instanceof HTMLInputElement ||
|
||||
element instanceof HTMLTextAreaElement;
|
||||
|
||||
let text = isPlainTextField ?
|
||||
element.value :
|
||||
getContentEditableText(element);
|
||||
|
||||
let textAround = getTextAroundCursor(text, range);
|
||||
|
||||
let changed = this.selectionStart !== range[0] ||
|
||||
this.selectionEnd !== range[1] ||
|
||||
this.textBeforeCursor !== textAround.before ||
|
||||
this.textAfterCursor !== textAround.after;
|
||||
|
||||
this.selectionStart = range[0];
|
||||
this.selectionEnd = range[1];
|
||||
this.textBeforeCursor = textAround.before;
|
||||
this.textAfterCursor = textAround.after;
|
||||
|
||||
return {
|
||||
selectionStart: range[0],
|
||||
selectionEnd: range[1],
|
||||
textBeforeCursor: textAround.before,
|
||||
textAfterCursor: textAround.after,
|
||||
changed: changed
|
||||
};
|
||||
},
|
||||
|
||||
// Notify when the selection range changes
|
||||
updateSelection: function fa_updateSelection() {
|
||||
let range = getSelectionRange(this.focusedElement);
|
||||
if (range[0] != this.selectionStart || range[1] != this.selectionEnd) {
|
||||
this.selectionStart = range[0];
|
||||
this.selectionEnd = range[1];
|
||||
sendAsyncMessage("Forms:SelectionChange", {
|
||||
selectionStart: range[0],
|
||||
selectionEnd: range[1]
|
||||
});
|
||||
let selectionInfo = this.getSelectionInfo();
|
||||
if (selectionInfo.changed) {
|
||||
sendAsyncMessage("Forms:SelectionChange", this.getSelectionInfo());
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -568,7 +680,7 @@ function isContentEditable(element) {
|
||||
return element.ownerDocument && element.ownerDocument.designMode == "on";
|
||||
}
|
||||
|
||||
function getJSON(element) {
|
||||
function getJSON(element, focusCounter) {
|
||||
let type = element.type || "";
|
||||
let value = element.value || "";
|
||||
let max = element.max || "";
|
||||
@ -609,8 +721,11 @@ function getJSON(element) {
|
||||
}
|
||||
|
||||
let range = getSelectionRange(element);
|
||||
let textAround = getTextAroundCursor(value, range);
|
||||
|
||||
return {
|
||||
"contextId": focusCounter,
|
||||
|
||||
"type": type.toLowerCase(),
|
||||
"choices": getListForElement(element),
|
||||
"value": value,
|
||||
@ -618,7 +733,25 @@ function getJSON(element) {
|
||||
"selectionStart": range[0],
|
||||
"selectionEnd": range[1],
|
||||
"max": max,
|
||||
"min": min
|
||||
"min": min,
|
||||
"lang": element.lang || "",
|
||||
"textBeforeCursor": textAround.before,
|
||||
"textAfterCursor": textAround.after
|
||||
};
|
||||
}
|
||||
|
||||
function getTextAroundCursor(value, range) {
|
||||
let textBeforeCursor = range[0] < 100 ?
|
||||
value.substr(0, range[0]) :
|
||||
value.substr(range[0] - 100, 100);
|
||||
|
||||
let textAfterCursor = range[1] + 100 > value.length ?
|
||||
value.substr(range[0], value.length) :
|
||||
value.substr(range[0], range[1] - range[0] + 100);
|
||||
|
||||
return {
|
||||
before: textBeforeCursor,
|
||||
after: textAfterCursor
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,8 @@ component {397a7fdf-2254-47be-b74e-76625a1a66d5} MozKeyboard.js
|
||||
contract @mozilla.org/b2g-keyboard;1 {397a7fdf-2254-47be-b74e-76625a1a66d5}
|
||||
category JavaScript-navigator-property mozKeyboard @mozilla.org/b2g-keyboard;1
|
||||
|
||||
component {5c7f4ce1-a946-4adc-89e6-c908226341a0} MozKeyboard.js
|
||||
contract @mozilla.org/b2g-inputmethod;1 {5c7f4ce1-a946-4adc-89e6-c908226341a0}
|
||||
component {4607330d-e7d2-40a4-9eb8-43967eae0142} MozKeyboard.js
|
||||
contract @mozilla.org/b2g-inputmethod;1 {4607330d-e7d2-40a4-9eb8-43967eae0142}
|
||||
category JavaScript-navigator-property mozInputMethod @mozilla.org/b2g-inputmethod;1
|
||||
|
||||
# DirectoryProvider.js
|
||||
|
@ -22,7 +22,8 @@ let Keyboard = {
|
||||
_messageNames: [
|
||||
'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions',
|
||||
'SetSelectionRange', 'ReplaceSurroundingText', 'ShowInputMethodPicker',
|
||||
'SwitchToNextInputMethod', 'HideInputMethod'
|
||||
'SwitchToNextInputMethod', 'HideInputMethod',
|
||||
'GetText', 'SendKey', 'GetContext'
|
||||
],
|
||||
|
||||
get messageManager() {
|
||||
@ -58,6 +59,13 @@ let Keyboard = {
|
||||
} else {
|
||||
mm.addMessageListener('Forms:Input', this);
|
||||
mm.addMessageListener('Forms:SelectionChange', this);
|
||||
mm.addMessageListener('Forms:GetText:Result:OK', this);
|
||||
mm.addMessageListener('Forms:GetText:Result:Error', this);
|
||||
mm.addMessageListener('Forms:SetSelectionRange:Result:OK', this);
|
||||
mm.addMessageListener('Forms:ReplaceSurroundingText:Result:OK', this);
|
||||
mm.addMessageListener('Forms:SendKey:Result:OK', this);
|
||||
mm.addMessageListener('Forms:SequenceError', this);
|
||||
mm.addMessageListener('Forms:GetContext:Result:OK', this);
|
||||
|
||||
// When not running apps OOP, we need to load forms.js here since this
|
||||
// won't happen from dom/ipc/preload.js
|
||||
@ -98,11 +106,20 @@ let Keyboard = {
|
||||
|
||||
switch (msg.name) {
|
||||
case 'Forms:Input':
|
||||
this.handleFormsInput(msg);
|
||||
this.forwardEvent('Keyboard:FocusChange', msg);
|
||||
break;
|
||||
case 'Forms:SelectionChange':
|
||||
this.handleFormsSelectionChange(msg);
|
||||
case 'Forms:GetText:Result:OK':
|
||||
case 'Forms:GetText:Result:Error':
|
||||
case 'Forms:SetSelectionRange:Result:OK':
|
||||
case 'Forms:ReplaceSurroundingText:Result:OK':
|
||||
case 'Forms:SendKey:Result:OK':
|
||||
case 'Forms:SequenceError':
|
||||
case 'Forms:GetContext:Result:OK':
|
||||
let name = msg.name.replace(/^Forms/, 'Keyboard');
|
||||
this.forwardEvent(name, msg);
|
||||
break;
|
||||
|
||||
case 'Keyboard:SetValue':
|
||||
this.setValue(msg);
|
||||
break;
|
||||
@ -127,21 +144,23 @@ let Keyboard = {
|
||||
case 'Keyboard:ShowInputMethodPicker':
|
||||
this.showInputMethodPicker();
|
||||
break;
|
||||
case 'Keyboard:GetText':
|
||||
this.getText(msg);
|
||||
break;
|
||||
case 'Keyboard:SendKey':
|
||||
this.sendKey(msg);
|
||||
break;
|
||||
case 'Keyboard:GetContext':
|
||||
this.getContext(msg);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
handleFormsInput: function keyboardHandleFormsInput(msg) {
|
||||
forwardEvent: function keyboardForwardEvent(newEventName, msg) {
|
||||
this.messageManager = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
|
||||
.frameLoader.messageManager;
|
||||
|
||||
ppmm.broadcastAsyncMessage('Keyboard:FocusChange', msg.data);
|
||||
},
|
||||
|
||||
handleFormsSelectionChange: function keyboardHandleFormsSelectionChange(msg) {
|
||||
this.messageManager = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
|
||||
.frameLoader.messageManager;
|
||||
|
||||
ppmm.broadcastAsyncMessage('Keyboard:SelectionChange', msg.data);
|
||||
ppmm.broadcastAsyncMessage(newEventName, msg.data);
|
||||
},
|
||||
|
||||
setSelectedOption: function keyboardSetSelectedOption(msg) {
|
||||
@ -181,6 +200,18 @@ let Keyboard = {
|
||||
browser.shell.sendChromeEvent({
|
||||
type: "input-method-switch-to-next"
|
||||
});
|
||||
},
|
||||
|
||||
getText: function keyboardGetText(msg) {
|
||||
this.messageManager.sendAsyncMessage('Forms:GetText', msg.data);
|
||||
},
|
||||
|
||||
sendKey: function keyboardSendKey(msg) {
|
||||
this.messageManager.sendAsyncMessage('Forms:Input:SendKey', msg.data);
|
||||
},
|
||||
|
||||
getContext: function keyboardGetContext(msg) {
|
||||
this.messageManager.sendAsyncMessage('Forms:GetContext', msg.data);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -10,6 +10,7 @@ const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/ObjectWrapper.jsm");
|
||||
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
|
||||
"@mozilla.org/childprocessmessagemanager;1", "nsIMessageSender");
|
||||
@ -244,15 +245,18 @@ MozInputMethodManager.prototype = {
|
||||
function MozInputMethod() { }
|
||||
|
||||
MozInputMethod.prototype = {
|
||||
classID: Components.ID("{5c7f4ce1-a946-4adc-89e6-c908226341a0}"),
|
||||
_inputcontext: null,
|
||||
|
||||
classID: Components.ID("{4607330d-e7d2-40a4-9eb8-43967eae0142}"),
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIInputMethod,
|
||||
Ci.nsIDOMGlobalPropertyInitializer
|
||||
Ci.nsIDOMGlobalPropertyInitializer,
|
||||
Ci.nsIObserver
|
||||
]),
|
||||
|
||||
classInfo: XPCOMUtils.generateCI({
|
||||
"classID": Components.ID("{5c7f4ce1-a946-4adc-89e6-c908226341a0}"),
|
||||
"classID": Components.ID("{4607330d-e7d2-40a4-9eb8-43967eae0142}"),
|
||||
"contractID": "@mozilla.org/b2g-inputmethod;1",
|
||||
"interfaces": [Ci.nsIInputMethod],
|
||||
"flags": Ci.nsIClassInfo.DOM_OBJECT,
|
||||
@ -269,11 +273,374 @@ MozInputMethod.prototype = {
|
||||
return null;
|
||||
}
|
||||
|
||||
this._window = win;
|
||||
this._mgmt = new MozInputMethodManager();
|
||||
this.innerWindowID = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils)
|
||||
.currentInnerWindowID;
|
||||
|
||||
Services.obs.addObserver(this, "inner-window-destroyed", false);
|
||||
cpmm.addMessageListener('Keyboard:FocusChange', this);
|
||||
cpmm.addMessageListener('Keyboard:SelectionChange', this);
|
||||
cpmm.addMessageListener('Keyboard:GetContext:Result:OK', this);
|
||||
|
||||
// If there already is an active context, then this will trigger
|
||||
// a GetContext:Result:OK event, and we can initialize ourselves.
|
||||
// Otherwise silently ignored.
|
||||
cpmm.sendAsyncMessage("Keyboard:GetContext", {});
|
||||
},
|
||||
|
||||
uninit: function mozInputMethodUninit() {
|
||||
Services.obs.removeObserver(this, "inner-window-destroyed");
|
||||
cpmm.removeMessageListener('Keyboard:FocusChange', this);
|
||||
cpmm.removeMessageListener('Keyboard:SelectionChange', this);
|
||||
cpmm.removeMessageListener('Keyboard:GetContext:Result:OK', this);
|
||||
|
||||
this._window = null;
|
||||
this._inputcontextHandler = null;
|
||||
this._mgmt = null;
|
||||
},
|
||||
|
||||
receiveMessage: function mozInputMethodReceiveMsg(msg) {
|
||||
let json = msg.json;
|
||||
|
||||
switch(msg.name) {
|
||||
case 'Keyboard:FocusChange':
|
||||
if (json.type !== 'blur') {
|
||||
this.setInputContext(json);
|
||||
}
|
||||
else {
|
||||
this.setInputContext(null);
|
||||
}
|
||||
break;
|
||||
case 'Keyboard:SelectionChange':
|
||||
this._inputcontext.updateSelectionContext(json);
|
||||
break;
|
||||
case 'Keyboard:GetContext:Result:OK':
|
||||
this.setInputContext(json);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
observe: function mozInputMethodObserve(subject, topic, data) {
|
||||
let wId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
||||
if (wId == this.innerWindowID)
|
||||
this.uninit();
|
||||
},
|
||||
|
||||
get mgmt() {
|
||||
return this._mgmt;
|
||||
},
|
||||
|
||||
get inputcontext() {
|
||||
return this._inputcontext;
|
||||
},
|
||||
|
||||
set oninputcontextchange(val) {
|
||||
this._inputcontextHandler = val;
|
||||
},
|
||||
|
||||
get oninputcontextchange() {
|
||||
return this._inputcontextHandler;
|
||||
},
|
||||
|
||||
setInputContext: function mozKeyboardContextChange(data) {
|
||||
if (this._inputcontext) {
|
||||
this._inputcontext.destroy();
|
||||
this._inputcontext = null;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
this._inputcontext = new MozInputContext(data);
|
||||
this._inputcontext.init(this._window);
|
||||
}
|
||||
|
||||
let handler = this._inputcontextHandler;
|
||||
if (!handler || !(handler instanceof Ci.nsIDOMEventListener))
|
||||
return;
|
||||
|
||||
let evt = new this._window.CustomEvent("inputcontextchange",
|
||||
ObjectWrapper.wrap({}, this._window));
|
||||
handler.handleEvent(evt);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ==============================================
|
||||
* InputContext
|
||||
* ==============================================
|
||||
*/
|
||||
function MozInputContext(ctx) {
|
||||
this._context = {
|
||||
inputtype: ctx.type,
|
||||
inputmode: ctx.inputmode,
|
||||
lang: ctx.lang,
|
||||
type: ["textarea", "contenteditable"].indexOf(ctx.type) > -1 ?
|
||||
ctx.type :
|
||||
"text",
|
||||
selectionStart: ctx.selectionStart,
|
||||
selectionEnd: ctx.selectionEnd,
|
||||
textBeforeCursor: ctx.textBeforeCursor,
|
||||
textAfterCursor: ctx.textAfterCursor
|
||||
};
|
||||
|
||||
this._contextId = ctx.contextId;
|
||||
}
|
||||
|
||||
MozInputContext.prototype = {
|
||||
__proto__: DOMRequestIpcHelper.prototype,
|
||||
|
||||
_context: null,
|
||||
_contextId: -1,
|
||||
|
||||
classID: Components.ID("{1e38633d-d08b-4867-9944-afa5c648adb6}"),
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIB2GInputContext,
|
||||
Ci.nsIObserver
|
||||
]),
|
||||
|
||||
classInfo: XPCOMUtils.generateCI({
|
||||
"classID": Components.ID("{1e38633d-d08b-4867-9944-afa5c648adb6}"),
|
||||
"contractID": "@mozilla.org/b2g-inputcontext;1",
|
||||
"interfaces": [Ci.nsIB2GInputContext],
|
||||
"flags": Ci.nsIClassInfo.DOM_OBJECT,
|
||||
"classDescription": "B2G Input Context"
|
||||
}),
|
||||
|
||||
init: function ic_init(win) {
|
||||
this._window = win;
|
||||
this._utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
|
||||
this.initDOMRequestHelper(win,
|
||||
["Keyboard:GetText:Result:OK",
|
||||
"Keyboard:GetText:Result:Error",
|
||||
"Keyboard:SetSelectionRange:Result:OK",
|
||||
"Keyboard:ReplaceSurroundingText:Result:OK",
|
||||
"Keyboard:SendKey:Result:OK",
|
||||
"Keyboard:SequenceError"]);
|
||||
},
|
||||
|
||||
destroy: function ic_destroy() {
|
||||
let self = this;
|
||||
|
||||
// All requests that are still pending need to be invalidated
|
||||
// because the context is no longer valid.
|
||||
Object.keys(self._requests).forEach(function(k) {
|
||||
// takeRequest also does a delete from context
|
||||
let req = self.takeRequest(k);
|
||||
Services.DOMRequest.fireError(req, "InputContext got destroyed");
|
||||
});
|
||||
|
||||
this.destroyDOMRequestHelper();
|
||||
|
||||
// A consuming application might still hold a cached version of this
|
||||
// object. After destroying the DOMRequestHelper all methods will throw
|
||||
// because we cannot create new requests 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;
|
||||
},
|
||||
|
||||
receiveMessage: function ic_receiveMessage(msg) {
|
||||
if (!msg || !msg.json) {
|
||||
dump('InputContext received message without data\n');
|
||||
return;
|
||||
}
|
||||
|
||||
let json = msg.json;
|
||||
let request = json.requestId ? this.takeRequest(json.requestId) : null;
|
||||
|
||||
if (!request) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.name) {
|
||||
case "Keyboard:SendKey:Result:OK":
|
||||
Services.DOMRequest.fireSuccess(request, null);
|
||||
break;
|
||||
case "Keyboard:GetText:Result:OK":
|
||||
Services.DOMRequest.fireSuccess(request, json.text);
|
||||
break;
|
||||
case "Keyboard:GetText:Result:Error":
|
||||
Services.DOMRequest.fireError(request, json.error);
|
||||
break;
|
||||
case "Keyboard:SetSelectionRange:Result:OK":
|
||||
case "Keyboard:ReplaceSurroundingText:Result:OK":
|
||||
Services.DOMRequest.fireSuccess(request,
|
||||
ObjectWrapper.wrap(json.selectioninfo, this._window));
|
||||
break;
|
||||
case "Keyboard:SequenceError":
|
||||
// Occurs when a new element got focus, but the inputContext was
|
||||
// not invalidated yet...
|
||||
Services.DOMRequest.fireError(request, "InputContext has expired");
|
||||
break;
|
||||
default:
|
||||
Services.DOMRequest.fireError(request, "Could not find a handler for " +
|
||||
msg.name);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
updateSelectionContext: function ic_updateSelectionContext(ctx) {
|
||||
if (!this._context) {
|
||||
return;
|
||||
}
|
||||
|
||||
let selectionDirty = this._context.selectionStart !== ctx.selectionStart ||
|
||||
this._context.selectionEnd !== ctx.selectionEnd;
|
||||
let surroundDirty = this._context.textBeforeCursor !== ctx.textBeforeCursor ||
|
||||
this._context.textAfterCursor !== ctx.textAfterCursor;
|
||||
|
||||
this._context.selectionStart = ctx.selectionStart;
|
||||
this._context.selectionEnd = ctx.selectionEnd;
|
||||
this._context.textBeforeCursor = ctx.textBeforeCursor;
|
||||
this._context.textAfterCursor = ctx.textAfterCursor;
|
||||
|
||||
if (selectionDirty) {
|
||||
this._fireEvent(this._onselectionchange, "selectionchange", {
|
||||
selectionStart: ctx.selectionStart,
|
||||
selectionEnd: ctx.selectionEnd
|
||||
});
|
||||
}
|
||||
|
||||
if (surroundDirty) {
|
||||
this._fireEvent(this._onsurroundingtextchange, "surroundingtextchange", {
|
||||
beforeString: ctx.textBeforeCursor,
|
||||
afterString: ctx.textAfterCursor
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_fireEvent: function ic_fireEvent(handler, eventName, aDetail) {
|
||||
if (!handler || !(handler instanceof Ci.nsIDOMEventListener))
|
||||
return;
|
||||
|
||||
let detail = {
|
||||
detail: aDetail
|
||||
};
|
||||
|
||||
let evt = new this._window.CustomEvent(eventName,
|
||||
ObjectWrapper.wrap(aDetail, this._window));
|
||||
handler.handleEvent(evt);
|
||||
},
|
||||
|
||||
// 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 request = this.createRequest();
|
||||
|
||||
cpmm.sendAsyncMessage('Keyboard:GetText', {
|
||||
contextId: this._contextId,
|
||||
requestId: this.getRequestId(request),
|
||||
offset: offset,
|
||||
length: length
|
||||
});
|
||||
|
||||
return request;
|
||||
},
|
||||
|
||||
get selectionStart() {
|
||||
return this._context.selectionStart;
|
||||
},
|
||||
|
||||
get selectionEnd() {
|
||||
return this._context.selectionEnd;
|
||||
},
|
||||
|
||||
get textBeforeCursor() {
|
||||
return this._context.textBeforeCursor;
|
||||
},
|
||||
|
||||
get textAfterCursor() {
|
||||
return this._context.textAfterCursor;
|
||||
},
|
||||
|
||||
setSelectionRange: function ic_setSelectionRange(start, length) {
|
||||
let request = this.createRequest();
|
||||
|
||||
cpmm.sendAsyncMessage("Keyboard:SetSelectionRange", {
|
||||
contextId: this._contextId,
|
||||
requestId: this.getRequestId(request),
|
||||
selectionStart: start,
|
||||
selectionEnd: start + length
|
||||
});
|
||||
|
||||
return request;
|
||||
},
|
||||
|
||||
get onsurroundingtextchange() {
|
||||
return this._onsurroundingtextchange;
|
||||
},
|
||||
|
||||
set onsurroundingtextchange(handler) {
|
||||
this._onsurroundingtextchange = handler;
|
||||
},
|
||||
|
||||
get onselectionchange() {
|
||||
return this._onselectionchange;
|
||||
},
|
||||
|
||||
set onselectionchange(handler) {
|
||||
this._onselectionchange = handler;
|
||||
},
|
||||
|
||||
replaceSurroundingText: function ic_replaceSurrText(text, offset, length) {
|
||||
let request = this.createRequest();
|
||||
|
||||
cpmm.sendAsyncMessage('Keyboard:ReplaceSurroundingText', {
|
||||
contextId: this._contextId,
|
||||
requestId: this.getRequestId(request),
|
||||
text: text,
|
||||
beforeLength: offset || 0,
|
||||
afterLength: length || 0
|
||||
});
|
||||
|
||||
return request;
|
||||
},
|
||||
|
||||
deleteSurroundingText: function ic_deleteSurrText(offset, length) {
|
||||
return this.replaceSurroundingText(null, offset, length);
|
||||
},
|
||||
|
||||
sendKey: function ic_sendKey(keyCode, charCode, modifiers) {
|
||||
let request = this.createRequest();
|
||||
|
||||
cpmm.sendAsyncMessage('Keyboard:SendKey', {
|
||||
contextId: this._contextId,
|
||||
requestId: this.getRequestId(request),
|
||||
keyCode: keyCode,
|
||||
charCode: charCode,
|
||||
modifiers: modifiers
|
||||
});
|
||||
|
||||
return request;
|
||||
},
|
||||
|
||||
setComposition: function ic_setComposition(text, cursor) {
|
||||
throw "Not implemented";
|
||||
},
|
||||
|
||||
endComposition: function ic_endComposition(text) {
|
||||
throw "Not implemented";
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
#include "domstubs.idl"
|
||||
|
||||
interface nsIDOMDOMRequest;
|
||||
|
||||
[scriptable, uuid(3615a616-571d-4194-bf54-ccf546067b14)]
|
||||
interface nsIB2GCameraContent : nsISupports
|
||||
{
|
||||
@ -11,6 +13,119 @@ interface nsIB2GCameraContent : nsISupports
|
||||
DOMString getCameraURI([optional] in jsval options);
|
||||
};
|
||||
|
||||
[scriptable, uuid(1e38633d-d08b-4867-9944-afa5c648adb6)]
|
||||
interface nsIB2GInputContext : nsISupports
|
||||
{
|
||||
// The tag name of input field, which is enum of "input", "textarea", or "contenteditable"
|
||||
readonly attribute DOMString type;
|
||||
|
||||
// The type of the input field, which is enum of text, number, password, url, search, email, and so on.
|
||||
// See http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#states-of-the-type-attribute
|
||||
readonly attribute DOMString inputType;
|
||||
|
||||
/*
|
||||
* The inputmode string, representing the input mode.
|
||||
* See http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#input-modalities:-the-inputmode-attribute
|
||||
*/
|
||||
readonly attribute DOMString inputMode;
|
||||
|
||||
/*
|
||||
* The primary language for the input field.
|
||||
* It is the value of HTMLElement.lang.
|
||||
* See http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#htmlelement
|
||||
*/
|
||||
readonly attribute DOMString lang;
|
||||
|
||||
/*
|
||||
* Get the whole text content of the input field.
|
||||
*/
|
||||
nsIDOMDOMRequest getText([optional] in long offset, [optional] in long length);
|
||||
|
||||
// The start and stop position of the selection.
|
||||
readonly attribute long selectionStart;
|
||||
readonly attribute long selectionEnd;
|
||||
|
||||
// The start and stop position of the selection.
|
||||
readonly attribute DOMString textBeforeCursor;
|
||||
readonly attribute DOMString textAfterCursor;
|
||||
|
||||
/*
|
||||
* Set the selection range of the the editable text.
|
||||
* Note: This method cannot be used to move the cursor during composition. Calling this
|
||||
* method will cancel composition.
|
||||
* @param start The beginning of the selected text.
|
||||
* @param length The length of the selected text.
|
||||
*
|
||||
* Note that the start position should be less or equal to the end position.
|
||||
* To move the cursor, set the start and end position to the same value.
|
||||
*/
|
||||
nsIDOMDOMRequest setSelectionRange(in long start, in long length);
|
||||
|
||||
/*
|
||||
* Commit text to current input field and replace text around cursor position. It will clear the current composition.
|
||||
*
|
||||
* @param text The string to be replaced with.
|
||||
* @param offset The offset from the cursor position where replacing starts. Defaults to 0.
|
||||
* @param length The length of text to replace. Defaults to 0.
|
||||
*/
|
||||
nsIDOMDOMRequest replaceSurroundingText(in DOMString text, [optional] in long offset, [optional] in long length);
|
||||
|
||||
/*
|
||||
*
|
||||
* Delete text around the cursor.
|
||||
* @param offset The offset from the cursor position where deletion starts.
|
||||
* @param length The length of text to delete.
|
||||
* TODO: maybe updateSurroundingText(DOMString beforeText, DOMString afterText); ?
|
||||
*/
|
||||
nsIDOMDOMRequest deleteSurroundingText(in long offset, in long length);
|
||||
|
||||
/*
|
||||
* Notifies when the text around the cursor is changed, due to either text
|
||||
* editing or cursor movement. If the cursor has been moved, but the text around has not
|
||||
* changed, the IME won't get notification.
|
||||
*
|
||||
* The event handler function is specified as:
|
||||
* @param beforeString Text before and including cursor position.
|
||||
* @param afterString Text after and excluing cursor position.
|
||||
* function(DOMString beforeText, DOMString afterText) {
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
attribute nsIDOMEventListener onsurroundingtextchange;
|
||||
|
||||
attribute nsIDOMEventListener onselectionchange;
|
||||
|
||||
/*
|
||||
* send a character with its key events.
|
||||
* @param modifiers see http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/base/nsIDOMWindowUtils.idl#206
|
||||
* @return true if succeeds. Otherwise false if the input context becomes void.
|
||||
* Alternative: sendKey(KeyboardEvent event), but we will likely waste memory for creating the KeyboardEvent object.
|
||||
*/
|
||||
nsIDOMDOMRequest sendKey(in long keyCode, in long charCode, in long modifiers);
|
||||
|
||||
/*
|
||||
* Set current composition. It will start or update composition.
|
||||
* @param cursor Position in the text of the cursor.
|
||||
*
|
||||
* The API implementation should automatically ends the composition
|
||||
* session (with event and confirm the current composition) if
|
||||
* endComposition is never called. Same apply when the inputContext is lost
|
||||
* during a unfinished composition session.
|
||||
*/
|
||||
nsIDOMDOMRequest setComposition(in DOMString text, in long cursor);
|
||||
|
||||
/*
|
||||
* End composition and actually commit the text. (was |commitText(text, offset, length)|)
|
||||
* Ending the composition with an empty string will not send any text.
|
||||
* Note that if composition always ends automatically (with the current composition committed) if the composition
|
||||
* did not explicitly with |endComposition()| but was interrupted with |sendKey()|, |setSelectionRange()|,
|
||||
* user moving the cursor, or remove the focus, etc.
|
||||
*
|
||||
* @param text The text
|
||||
*/
|
||||
nsIDOMDOMRequest endComposition(in DOMString text);
|
||||
};
|
||||
|
||||
[scriptable, uuid(40ad96b2-9efa-41fb-84c7-fbcec9b153f0)]
|
||||
interface nsIB2GKeyboard : nsISupports
|
||||
{
|
||||
@ -103,9 +218,19 @@ interface nsIInputMethodManager : nsISupports
|
||||
void hide();
|
||||
};
|
||||
|
||||
[scriptable, uuid(5c7f4ce1-a946-4adc-89e6-c908226341a0)]
|
||||
[scriptable, uuid(4607330d-e7d2-40a4-9eb8-43967eae0142)]
|
||||
interface nsIInputMethod : nsISupports
|
||||
{
|
||||
// Input Method Manager contain a few global methods expose to apps
|
||||
readonly attribute nsIInputMethodManager mgmt;
|
||||
|
||||
// Fired when the input context changes, include changes from and to null.
|
||||
// The new InputContext instance will be available in the event object under |inputcontext| property.
|
||||
// When it changes to null it means the app (the user of this API) no longer has the control of the original focused input field.
|
||||
// Note that if the app saves the original context, it might get void; implementation decides when to void the input context.
|
||||
readonly attribute nsIB2GInputContext inputcontext;
|
||||
|
||||
// An "input context" is mapped to a text field that the app is allow to mutate.
|
||||
// this attribute should be null when there is no text field currently focused.
|
||||
attribute nsIDOMEventListener oninputcontextchange;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user