Bug 1093153: introduce 'BrowserTestUtils#synthesizeKey', 'BrowserTestUtils#synthesizeComposition' and 'BrowserTestUtils#synthesizeCompositionChange' to allow mochitests to remotely invoke EventUtils' text composition utilities. Changes to EventUtils include 1) removed dependency on the 'navigator' object when 'nsIXULRuntime' is available and 2) make '_getKeyboardEvent' more robust when used in frame scripts. r=Enn

This commit is contained in:
Mike de Boer 2016-03-14 14:35:46 +01:00
parent 011ff7762d
commit 5378afd0c0
3 changed files with 148 additions and 17 deletions

View File

@ -38,6 +38,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
Cu.permitCPOWsInScope(this);
var gSendCharCount = 0;
var gSynthesizeKeyCount = 0;
var gSynthesizeCompositionCount = 0;
var gSynthesizeCompositionChangeCount = 0;
this.BrowserTestUtils = {
/**
@ -827,7 +830,7 @@ this.BrowserTestUtils = {
/**
* Version of EventUtils' `sendChar` function; it will synthesize a keypress
* event in a child process and returns a Promise that will result when the
* event in a child process and returns a Promise that will resolve when the
* event was fired. Instead of a Window, a Browser object is required to be
* passed to this function.
*
@ -849,7 +852,7 @@ this.BrowserTestUtils = {
return;
mm.removeMessageListener("Test:SendCharDone", charMsg);
resolve(message.data.sendCharResult);
resolve(message.data.result);
});
mm.sendAsyncMessage("Test:SendChar", {
@ -859,6 +862,99 @@ this.BrowserTestUtils = {
});
},
/**
* Version of EventUtils' `synthesizeKey` function; it will synthesize a key
* event in a child process and returns a Promise that will resolve when the
* event was fired. Instead of a Window, a Browser object is required to be
* passed to this function.
*
* @param {String} key
* See the documentation available for EventUtils#synthesizeKey.
* @param {Object} event
* See the documentation available for EventUtils#synthesizeKey.
* @param {Browser} browser
* Browser element, must not be null.
*
* @returns {Promise}
*/
synthesizeKey(key, event, browser) {
return new Promise(resolve => {
let seq = ++gSynthesizeKeyCount;
let mm = browser.messageManager;
mm.addMessageListener("Test:SynthesizeKeyDone", function keyMsg(message) {
if (message.data.seq != seq)
return;
mm.removeMessageListener("Test:SynthesizeKeyDone", keyMsg);
resolve();
});
mm.sendAsyncMessage("Test:SynthesizeKey", { key, event, seq });
});
},
/**
* Version of EventUtils' `synthesizeComposition` function; it will synthesize
* a composition event in a child process and returns a Promise that will
* resolve when the event was fired. Instead of a Window, a Browser object is
* required to be passed to this function.
*
* @param {Object} event
* See the documentation available for EventUtils#synthesizeComposition.
* @param {Browser} browser
* Browser element, must not be null.
*
* @returns {Promise}
* @resolves False if the composition event could not be synthesized.
*/
synthesizeComposition(event, browser) {
return new Promise(resolve => {
let seq = ++gSynthesizeCompositionCount;
let mm = browser.messageManager;
mm.addMessageListener("Test:SynthesizeCompositionDone", function compMsg(message) {
if (message.data.seq != seq)
return;
mm.removeMessageListener("Test:SynthesizeCompositionDone", compMsg);
resolve(message.data.result);
});
mm.sendAsyncMessage("Test:SynthesizeComposition", { event, seq });
});
},
/**
* Version of EventUtils' `synthesizeCompositionChange` function; it will
* synthesize a compositionchange event in a child process and returns a
* Promise that will resolve when the event was fired. Instead of a Window, a
* Browser object is required to be passed to this function.
*
* @param {Object} event
* See the documentation available for EventUtils#synthesizeCompositionChange.
* @param {Browser} browser
* Browser element, must not be null.
*
* @returns {Promise}
*/
synthesizeCompositionChange(event, browser) {
return new Promise(resolve => {
let seq = ++gSynthesizeCompositionChangeCount;
let mm = browser.messageManager;
mm.addMessageListener("Test:SynthesizeCompositionChangeDone", function compMsg(message) {
if (message.data.seq != seq)
return;
mm.removeMessageListener("Test:SynthesizeCompositionChangeDone", compMsg);
resolve();
});
mm.sendAsyncMessage("Test:SynthesizeCompositionChange", { event, seq });
});
},
/**
* Will poll a condition function until it returns true.
*

View File

@ -58,8 +58,20 @@ addMessageListener("Test:SynthesizeMouse", (message) => {
addMessageListener("Test:SendChar", message => {
let result = EventUtils.sendChar(message.data.char, content);
sendAsyncMessage("Test:SendCharDone", {
sendCharResult: result,
seq: message.data.seq
});
sendAsyncMessage("Test:SendCharDone", { result, seq: message.data.seq });
});
addMessageListener("Test:SynthesizeKey", message => {
EventUtils.synthesizeKey(message.data.key, message.data.event || {}, content);
sendAsyncMessage("Test:SynthesizeKeyDone", { seq: message.data.seq });
});
addMessageListener("Test:SynthesizeComposition", message => {
let result = EventUtils.synthesizeComposition(message.data.event, content);
sendAsyncMessage("Test:SynthesizeCompositionDone", { result, seq: message.data.seq });
});
addMessageListener("Test:SynthesizeCompositionChange", message => {
EventUtils.synthesizeCompositionChange(message.data.event, content);
sendAsyncMessage("Test:SynthesizeCompositionChangeDone", { seq: message.data.seq });
});

View File

@ -43,6 +43,24 @@ window.__defineGetter__('_EU_Cu', function() {
return c.value && !c.writable ? Components.utils : SpecialPowers.Cu;
});
window.__defineGetter__("_EU_OS", function() {
delete this._EU_OS;
try {
this._EU_OS = Cu.import("resource://gre/modules/AppConstants.jsm", {}).platform;
} catch (ex) {
this._EU_OS = null;
}
return this._EU_OS;
});
function _EU_isMac(aWindow = window) {
return window._EU_OS ? window._EU_OS == "macosx" : aWindow.navigator.platform.indexOf("Mac") > -1;
}
function _EU_isWin(aWindow = window) {
return window._EU_OS ? window._EU_OS == "win" : aWindow.navigator.platform.indexOf("Win") > -1;
}
/**
* Send a mouse event to the node aTarget (aTarget can be an id, or an
* actual node) . The "event" passed in to aEvent is just a JavaScript
@ -237,7 +255,7 @@ function _parseModifiers(aEvent, aWindow = window)
mval |= nsIDOMWindowUtils.MODIFIER_META;
}
if (aEvent.accelKey) {
mval |= (navigator.platform.indexOf("Mac") >= 0) ?
mval |= _EU_isMac(aWindow) ?
nsIDOMWindowUtils.MODIFIER_META : nsIDOMWindowUtils.MODIFIER_CONTROL;
}
if (aEvent.altGrKey) {
@ -789,16 +807,13 @@ function _parseNativeModifiers(aModifiers, aWindow = window)
}
if (aModifiers.accelKey) {
modifiers |=
(navigator.platform.indexOf("Mac") == 0) ? 0x00004000 : 0x00000400;
modifiers |= _EU_isMac(aWindow) ? 0x00004000 : 0x00000400;
}
if (aModifiers.accelRightKey) {
modifiers |=
(navigator.platform.indexOf("Mac") == 0) ? 0x00008000 : 0x00000800;
modifiers |= _EU_isMac(aWindow) ? 0x00008000 : 0x00000800;
}
if (aModifiers.altGrKey) {
modifiers |=
(navigator.platform.indexOf("Win") == 0) ? 0x00002800 : 0x00001000;
modifiers |= _EU_isWin(aWindow) ? 0x00002800 : 0x00001000;
}
return modifiers;
}
@ -873,9 +888,9 @@ function synthesizeNativeKey(aKeyboardLayout, aNativeKeyCode, aModifiers,
}
var navigator = _getNavigator(aWindow);
var nativeKeyboardLayout = null;
if (navigator.platform.indexOf("Mac") == 0) {
if (_EU_isMac(aWindow)) {
nativeKeyboardLayout = aKeyboardLayout.Mac;
} else if (navigator.platform.indexOf("Win") == 0) {
} else if (_EU_isWin(aWindow)) {
nativeKeyboardLayout = aKeyboardLayout.Win;
}
if (nativeKeyboardLayout === null) {
@ -1063,7 +1078,15 @@ function _getTIP(aWindow, aCallback)
function _getKeyboardEvent(aWindow = window)
{
if (typeof KeyboardEvent != "undefined") {
return KeyboardEvent;
try {
// See if the object can be instantiated; sometimes this yields
// 'TypeError: can't access dead object' or 'KeyboardEvent is not a constructor'.
new KeyboardEvent("", {});
return KeyboardEvent;
} catch (ex) {}
}
if (typeof content != "undefined" && ("KeyboardEvent" in content)) {
return content.KeyboardEvent;
}
return aWindow.KeyboardEvent;
}
@ -1282,7 +1305,7 @@ function _emulateToActivateModifiers(aTIP, aKeyEvent, aWindow = window)
{ key: "OS", attr: "osKey" },
{ key: "Shift", attr: "shiftKey" },
{ key: "Symbol", attr: "symbolKey" },
{ key: (navigator.platform.indexOf("Mac") >= 0) ? "Meta" : "Control",
{ key: _EU_isMac(aWindow) ? "Meta" : "Control",
attr: "accelKey" },
],
lockable: [