Bug 1137557 - Part 3: Allow content to pass a dict representing the property of the keyboard event to send. r=masayuki, sr=smaug

- Overloading MozInputContext#sendKey() so it could take a dict.
- An optional trailing argument for setComposition() and endComposition() methods for these methods to take the dict.
- New keydown() and keyup() methods that takes dict as the only argument.
This commit is contained in:
Tim Chien 2015-08-23 21:19:00 -04:00
parent 3771bf346d
commit 0bd412eafa
5 changed files with 2003 additions and 79 deletions

View File

@ -573,7 +573,7 @@ MozInputContext.prototype = {
switch (msg.name) {
case "Keyboard:SendKey:Result:OK":
resolver.resolve();
resolver.resolve(true);
break;
case "Keyboard:SendKey:Result:Error":
resolver.reject(json.error);
@ -596,7 +596,7 @@ MozInputContext.prototype = {
break;
case "Keyboard:SetComposition:Result:OK": // Fall through.
case "Keyboard:EndComposition:Result:OK":
resolver.resolve();
resolver.resolve(true);
break;
default:
dump("Could not find a handler for " + msg.name);
@ -738,42 +738,79 @@ MozInputContext.prototype = {
return this.replaceSurroundingText(null, offset, length);
},
sendKey: function ic_sendKey(keyCode, charCode, modifiers, repeat) {
let self = this;
sendKey: function ic_sendKey(dictOrKeyCode, charCode, modifiers, repeat) {
if (typeof dictOrKeyCode === 'number') {
// XXX: modifiers are ignored in this API method.
// XXX: modifiers are ignored in this API method.
return this._sendPromise((resolverId) => {
cpmmSendAsyncMessageWithKbID(this, 'Keyboard:SendKey', {
contextId: this._contextId,
requestId: resolverId,
method: 'sendKey',
keyCode: dictOrKeyCode,
charCode: charCode,
repeat: repeat
});
});
} else if (typeof dictOrKeyCode === 'object') {
return this._sendPromise((resolverId) => {
cpmmSendAsyncMessageWithKbID(this, 'Keyboard:SendKey', {
contextId: this._contextId,
requestId: resolverId,
method: 'sendKey',
keyboardEventDict: this._getkeyboardEventDict(dictOrKeyCode)
});
});
} else {
// XXX: Should not reach here; implies WebIDL binding error.
throw new TypeError('Unknown argument passed.');
}
},
return this._sendPromise(function(resolverId) {
cpmmSendAsyncMessageWithKbID(self, 'Keyboard:SendKey', {
contextId: self._contextId,
keydown: function ic_keydown(dict) {
return this._sendPromise((resolverId) => {
cpmmSendAsyncMessageWithKbID(this, 'Keyboard:SendKey', {
contextId: this._contextId,
requestId: resolverId,
method: 'keydown',
keyboardEventDict: this._getkeyboardEventDict(dict)
});
});
},
keyup: function ic_keyup(dict) {
return this._sendPromise((resolverId) => {
cpmmSendAsyncMessageWithKbID(this, 'Keyboard:SendKey', {
contextId: this._contextId,
requestId: resolverId,
keyCode: keyCode,
charCode: charCode,
repeat: repeat
method: 'keyup',
keyboardEventDict: this._getkeyboardEventDict(dict)
});
});
},
setComposition: function ic_setComposition(text, cursor, clauses) {
setComposition: function ic_setComposition(text, cursor, clauses, dict) {
let self = this;
return this._sendPromise(function(resolverId) {
return this._sendPromise((resolverId) => {
cpmmSendAsyncMessageWithKbID(self, 'Keyboard:SetComposition', {
contextId: self._contextId,
requestId: resolverId,
text: text,
cursor: (typeof cursor !== 'undefined') ? cursor : text.length,
clauses: clauses || null
clauses: clauses || null,
keyboardEventDict: this._getkeyboardEventDict(dict)
});
});
},
endComposition: function ic_endComposition(text) {
endComposition: function ic_endComposition(text, dict) {
let self = this;
return this._sendPromise(function(resolverId) {
return this._sendPromise((resolverId) => {
cpmmSendAsyncMessageWithKbID(self, 'Keyboard:EndComposition', {
contextId: self._contextId,
requestId: resolverId,
text: text || ''
text: text || '',
keyboardEventDict: this._getkeyboardEventDict(dict)
});
});
},
@ -788,6 +825,43 @@ MozInputContext.prototype = {
}
callback(aResolverId);
});
},
// Take a MozInputMethodKeyboardEventDict dict, creates a keyboardEventDict
// object that can be sent to forms.js
_getkeyboardEventDict: function(dict) {
if (typeof dict !== 'object' || !dict.key) {
return;
}
var keyboardEventDict = {
key: dict.key,
code: dict.code,
repeat: dict.repeat,
flags: 0
};
if (dict.printable) {
keyboardEventDict.flags |=
Ci.nsITextInputProcessor.KEY_FORCE_PRINTABLE_KEY;
}
if (/^[a-zA-Z0-9]$/.test(dict.key)) {
// keyCode must follow the key value in this range;
// disregard the keyCode from content.
keyboardEventDict.keyCode = dict.key.toUpperCase().charCodeAt(0);
} else if (typeof dict.keyCode === 'number') {
// Allow keyCode to be specified for other key values.
keyboardEventDict.keyCode = dict.keyCode;
// Allow keyCode to be explicitly set to zero.
if (dict.keyCode === 0) {
keyboardEventDict.flags |=
Ci.nsITextInputProcessor.KEY_KEEP_KEYCODE_ZERO;
}
}
return keyboardEventDict;
}
};

View File

@ -11,6 +11,7 @@ dump("###################################### forms.js loaded\n");
let Ci = Components.interfaces;
let Cc = Components.classes;
let Cu = Components.utils;
let Cr = Components.results;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
@ -652,7 +653,7 @@ let FormAssistant = {
break;
case "keydown":
if (!this.focusedElement) {
if (!this.focusedElement || this._editing) {
break;
}
@ -660,7 +661,7 @@ let FormAssistant = {
break;
case "keyup":
if (!this.focusedElement) {
if (!this.focusedElement || this._editing) {
break;
}
@ -735,55 +736,100 @@ let FormAssistant = {
break;
}
// The naive way to figure out if the key to dispatch is printable.
let printable = !!json.charCode;
// If we receive a keyboardEventDict from json, that means the user
// is calling the method with the new arguments.
// Otherwise, we would have to construct our own keyboardEventDict
// based on legacy values we have received.
let keyboardEventDict = json.keyboardEventDict;
let flags = 0;
if (keyboardEventDict) {
if ('flags' in keyboardEventDict) {
flags = keyboardEventDict.flags;
}
} else {
// The naive way to figure out if the key to dispatch is printable.
let printable = !!json.charCode;
let keyboardEventDict = {
// For printable keys, the value should be the actual character.
// For non-printable keys, it should be a value in the D3E spec.
// Here we make some educated guess for it.
key: printable ?
String.fromCharCode(json.charCode) :
guessKeyNameFromKeyCode(win.KeyboardEvent, json.keyCode),
// We don't have any information to tell the virtual key the
// user have interacted with.
code: "",
// We violate the spec here and ask TextInputProcessor not to infer
// this value from value of key nor code so we could keep the original
let key = printable ?
String.fromCharCode(json.charCode) :
guessKeyNameFromKeyCode(win.KeyboardEvent, json.keyCode);
// keyCode from content is only respected when the key is not an
// an alphanumeric character. We also ask TextInputProcessor not to
// infer this value for non-printable keys to keep the original
// behavior.
keyCode: json.keyCode,
// We do not have the information to infer location of the virtual key
// either (and we would need TextInputProcessor not to compute it).
location: 0,
// This indicates the key is triggered for repeats.
repeat: json.repeat
};
let keyCode = (printable && /^[a-zA-Z0-9]$/.test(key)) ?
key.toUpperCase().charCodeAt(0) :
json.keyCode;
keyboardEventDict = {
key: key,
keyCode: keyCode,
// We don't have any information to tell the virtual key the
// user have interacted with.
code: "",
// We do not have the information to infer location of the virtual key
// either (and we would need TextInputProcessor not to compute it).
location: 0,
// This indicates the key is triggered for repeats.
repeat: json.repeat
};
flags = tip.KEY_KEEP_KEY_LOCATION_STANDARD;
if (!printable) {
flags |= tip.KEY_NON_PRINTABLE_KEY;
}
if (!keyboardEventDict.keyCode) {
flags |= tip.KEY_KEEP_KEYCODE_ZERO;
}
}
let keyboardEvent = new win.KeyboardEvent("", keyboardEventDict);
let flags = tip.KEY_KEEP_KEY_LOCATION_STANDARD;
if (!printable) {
flags |= tip.KEY_NON_PRINTABLE_KEY;
}
if (!json.keyCode) {
flags |= tip.KEY_KEEP_KEYCODE_ZERO;
}
let keydownDefaultPrevented;
let keydownDefaultPrevented = false;
try {
let consumedFlags = tip.keydown(keyboardEvent, flags);
keydownDefaultPrevented =
!!(tip.KEYDOWN_IS_CONSUMED & consumedFlags);
if (!json.repeat) {
tip.keyup(keyboardEvent, flags);
switch (json.method) {
case 'sendKey': {
let consumedFlags = tip.keydown(keyboardEvent, flags);
keydownDefaultPrevented =
!!(tip.KEYDOWN_IS_CONSUMED & consumedFlags);
if (!keyboardEventDict.repeat) {
tip.keyup(keyboardEvent, flags);
}
break;
}
case 'keydown': {
let consumedFlags = tip.keydown(keyboardEvent, flags);
keydownDefaultPrevented =
!!(tip.KEYDOWN_IS_CONSUMED & consumedFlags);
break;
}
case 'keyup': {
tip.keyup(keyboardEvent, flags);
break;
}
}
} catch (e) {
dump("forms.js:" + e.toString() + "\n");
} catch (err) {
dump("forms.js:" + err.toString() + "\n");
if (json.requestId) {
sendAsyncMessage("Forms:SendKey:Result:Error", {
requestId: json.requestId,
error: "Unable to type into destoryed input."
});
if (err instanceof Ci.nsIException &&
err.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
sendAsyncMessage("Forms:SendKey:Result:Error", {
requestId: json.requestId,
error: "The values specified are illegal."
});
} else {
sendAsyncMessage("Forms:SendKey:Result:Error", {
requestId: json.requestId,
error: "Unable to type into destroyed input."
});
}
}
break;
@ -915,7 +961,7 @@ let FormAssistant = {
case "Forms:SetComposition": {
CompositionManager.setComposition(target, json.text, json.cursor,
json.clauses);
json.clauses, json.keyboardEventDict);
sendAsyncMessage("Forms:SetComposition:Result:OK", {
requestId: json.requestId,
selectioninfo: this.getSelectionInfo()
@ -924,7 +970,7 @@ let FormAssistant = {
}
case "Forms:EndComposition": {
CompositionManager.endComposition(json.text);
CompositionManager.endComposition(json.text, json.keyboardEventDict);
sendAsyncMessage("Forms:EndComposition:Result:OK", {
requestId: json.requestId,
selectioninfo: this.getSelectionInfo()
@ -1444,6 +1490,7 @@ function replaceSurroundingText(element, text, offset, length) {
let CompositionManager = {
_isStarted: false,
_tip: null,
_KeyboardEventForWin: null,
_clauseAttrMap: {
'raw-input':
Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE,
@ -1455,7 +1502,7 @@ let CompositionManager = {
Ci.nsITextInputProcessor.ATTR_SELECTED_CLAUSE
},
setComposition: function cm_setComposition(element, text, cursor, clauses) {
setComposition: function cm_setComposition(element, text, cursor, clauses, dict) {
// Check parameters.
if (!element) {
return;
@ -1508,13 +1555,22 @@ let CompositionManager = {
if (cursor >= 0) {
tip.setCaretInPendingComposition(cursor);
}
this._isStarted = tip.flushPendingComposition();
if (!dict) {
this._isStarted = tip.flushPendingComposition();
} else {
let keyboardEvent = new win.KeyboardEvent("", dict);
let flags = dict.flags;
this._isStarted = tip.flushPendingComposition(keyboardEvent, flags);
}
if (this._isStarted) {
this._tip = tip;
this._KeyboardEventForWin = win.KeyboardEvent;
}
},
endComposition: function cm_endComposition(text) {
endComposition: function cm_endComposition(text, dict) {
if (!this._isStarted) {
return;
}
@ -1523,9 +1579,18 @@ let CompositionManager = {
return;
}
tip.commitCompositionWith(text ? text : "");
text = text || "";
if (!dict) {
tip.commitCompositionWith(text);
} else {
let keyboardEvent = new this._KeyboardEventForWin("", dict);
let flags = dict.flags;
tip.commitCompositionWith(text, keyboardEvent, flags);
}
this._isStarted = false;
this._tip = null;
this._KeyboardEventForWin = null;
},
// Composition ends due to external actions.
@ -1536,5 +1601,6 @@ let CompositionManager = {
this._isStarted = false;
this._tip = null;
this._KeyboardEventForWin = null;
}
};

View File

@ -26,3 +26,4 @@ support-files =
[test_two_inputs.html]
[test_two_selects.html]
[test_unload.html]
[test_bug1137557.html]

File diff suppressed because it is too large Load Diff

View File

@ -192,17 +192,54 @@ interface MozInputContext: EventTarget {
attribute EventHandler onsurroundingtextchange;
/*
* send a character with its key events.
* @param modifiers this paramater is no longer honored.
* @param repeat indicates whether a key would be sent repeatedly.
* @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.
* Note that, if you want to send a key n times repeatedly, make sure set
* parameter repeat to true and invoke sendKey n-1 times, and then set
* repeat to false in the last invoke.
*/
Promise<boolean> sendKey(long keyCode, long charCode, long modifiers, optional boolean repeat);
* Send a string/character with its key events. There are two ways of invocating
* the method for backward compability purpose.
*
* (1) The recommended way, allow specifying DOM level 3 properties like |code|.
* @param dictOrKeyCode See MozInputMethodKeyboardEventDict.
* @param charCode disregarded
* @param modifiers disregarded
* @param repeat disregarded
*
* (2) Deprecated, reserved for backward compability.
* @param dictOrKeyCode keyCode of the key to send, should be one of the DOM_VK_ value in KeyboardEvent.
* @param charCode charCode of the character, should be 0 for non-printable keys.
* @param modifiers this paramater is no longer honored.
* @param repeat indicates whether a key would be sent repeatedly.
*
* @return A promise. Resolve to true if succeeds.
* Rejects to a string indicating the error.
*
* Note that, if you want to send a key n times repeatedly, make sure set
* parameter repeat to true and invoke sendKey n times, and invoke keyup
* after the end of the input.
*/
Promise<boolean> sendKey((MozInputMethodRequiredKeyboardEventDict or long) dictOrKeyCode,
optional long charCode,
optional long modifiers,
optional boolean repeat);
/*
* Send a string/character with keydown, and keypress events.
* keyup should be called afterwards to ensure properly sequence.
*
* @param dict See MozInputMethodKeyboardEventDict.
*
* @return A promise. Resolve to true if succeeds.
* Rejects to a string indicating the error.
*/
Promise<boolean> keydown(MozInputMethodRequiredKeyboardEventDict dict);
/*
* Send a keyup event. keydown should be called first to ensure properly sequence.
*
* @param dict See MozInputMethodKeyboardEventDict.
*
* @return A promise. Resolve to true if succeeds.
* Rejects to a string indicating the error.
*
*/
Promise<boolean> keyup(MozInputMethodRequiredKeyboardEventDict dict);
/*
* Set current composing text. This method will start composition or update
@ -218,7 +255,11 @@ interface MozInputContext: EventTarget {
* cursor will be positioned after the composition text.
* @param clauses The array of composition clause information. If not set,
* only one clause is supported.
*
* @param dict The properties of the keyboard event that cause the composition
* to set. keydown or keyup event will be fired if it's necessary.
* For compatibility, we recommend that you should always set this argument
* if it's caused by a key operation.
*
* The composing text, which is shown with underlined style to distinguish
* from the existing text, is used to compose non-ASCII characters from
* keystrokes, e.g. Pinyin or Hiragana. The composing text is the
@ -231,9 +272,10 @@ interface MozInputContext: EventTarget {
* To finish composition and commit text to current input field, an IME
* should call |endComposition|.
*/
// XXXbz what is this promise resolved with?
Promise<any> setComposition(DOMString text, optional long cursor,
optional sequence<CompositionClauseParameters> clauses);
Promise<boolean> setComposition(DOMString text,
optional long cursor,
optional sequence<CompositionClauseParameters> clauses,
optional MozInputMethodKeyboardEventDict dict);
/*
* End composition, clear the composing text and commit given text to
@ -241,6 +283,10 @@ interface MozInputContext: EventTarget {
* position.
* @param text The text to commited before cursor position. If empty string
* is given, no text will be committed.
* @param dict The properties of the keyboard event that cause the composition
* to end. keydown or keyup event will be fired if it's necessary.
* For compatibility, we recommend that you should always set this argument
* if it's caused by a key operation.
*
* Note that composition always ends automatically with nothing to commit if
* the composition does not explicitly end by calling |endComposition|, but
@ -248,8 +294,8 @@ interface MozInputContext: EventTarget {
* |replaceSurroundingText|, |deleteSurroundingText|, user moving the
* cursor, changing the focus, etc.
*/
// XXXbz what is this promise resolved with?
Promise<any> endComposition(optional DOMString text);
Promise<boolean> endComposition(optional DOMString text,
optional MozInputMethodKeyboardEventDict dict);
};
enum CompositionClauseSelectionType {
@ -263,3 +309,66 @@ dictionary CompositionClauseParameters {
DOMString selectionType = "raw-input";
long length;
};
/*
* A MozInputMethodKeyboardEventDictBase contains the following properties,
* indicating the properties of the keyboard event caused.
*
* This is the base dictionary type for us to create two child types that could
* be used as argument type in two types of methods, as WebIDL parser required.
*
*/
dictionary MozInputMethodKeyboardEventDictBase {
/*
* String/character to output, or a registered name of non-printable key.
* (To be defined in the inheriting dictionary types.)
*/
// DOMString key;
/*
* String/char indicating the virtual hardware key pressed. Optional.
* Must be a value defined in
* http://www.w3.org/TR/DOM-Level-3-Events-code/#keyboard-chording-virtual
* If your keyboard app emulates physical keyboard layout, this value should
* not be empty string. Otherwise, it should be empty string.
*/
DOMString code = "";
/*
* keyCode of the keyboard event. Optional.
* To be disregarded if |key| is an alphanumeric character.
* If the key causes inputting a character and if your keyboard app emulates
* a physical keyboard layout, this value should be same as the value used
* by Firefox for desktop. If the key causes inputting an ASCII character
* but if your keyboard app doesn't emulate any physical keyboard layouts,
* the value should be proper value for the key value.
*/
long? keyCode;
/*
* Indicates whether a key would be sent repeatedly. Optional.
*/
boolean repeat = false;
/*
* Optional. True if |key| property is explicitly referring to a printable key.
* When this is set, key will be printable even if the |key| value matches any
* of the registered name of non-printable keys.
*/
boolean printable = false;
};
/*
* For methods like setComposition() and endComposition(), the optional
* dictionary type argument is really optional when all of it's property
* are optional.
* This dictionary type is used to denote that argument.
*/
dictionary MozInputMethodKeyboardEventDict : MozInputMethodKeyboardEventDictBase {
DOMString? key;
};
/*
* For methods like keydown() and keyup(), the dictionary type argument is
* considered required only if at least one of it's property is required.
* This dictionary type is used to denote that argument.
*/
dictionary MozInputMethodRequiredKeyboardEventDict : MozInputMethodKeyboardEventDictBase {
required DOMString key;
};