mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Backed out 18 changesets (bug 1245153) for multiple test failures
Backed out changeset 18d54b8d4ae8 (bug 1245153) Backed out changeset 98b6d0c053c0 (bug 1245153) Backed out changeset c29a348930a4 (bug 1245153) Backed out changeset f79252e92acc (bug 1245153) Backed out changeset 9f3f1c358e47 (bug 1245153) Backed out changeset 3b9e9a027fa7 (bug 1245153) Backed out changeset 6da8099573f3 (bug 1245153) Backed out changeset 63a56310a1b5 (bug 1245153) Backed out changeset 5fe42d498a2a (bug 1245153) Backed out changeset b3be2d2f3ac1 (bug 1245153) Backed out changeset ad5bf32d8fef (bug 1245153) Backed out changeset 68a6dda373d2 (bug 1245153) Backed out changeset 6ebd9fde50c0 (bug 1245153) Backed out changeset e41a5b41859a (bug 1245153) Backed out changeset 048d70070751 (bug 1245153) Backed out changeset eff85dc0eaa9 (bug 1245153) Backed out changeset dc6460e0f336 (bug 1245153) Backed out changeset 36526a2e8b00 (bug 1245153)
This commit is contained in:
parent
3b22c16bf1
commit
8b1607e1c3
@ -4,17 +4,8 @@ var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1'].
|
||||
getService(Ci.mozIJSSubScriptLoader);
|
||||
|
||||
// Set up a dummy environment so that EventUtils works. We need to be careful to
|
||||
// pass a window object into each EventUtils method we call rather than having
|
||||
// it rely on the |window| global.
|
||||
let EventUtils = {};
|
||||
EventUtils.window = content;
|
||||
EventUtils.parent = EventUtils.window;
|
||||
EventUtils._EU_Ci = Components.interfaces;
|
||||
EventUtils._EU_Cc = Components.classes;
|
||||
EventUtils.navigator = content.navigator;
|
||||
EventUtils.KeyboardEvent = content.KeyboardEvent;
|
||||
loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
|
||||
const EventUtils = {};
|
||||
loadSubScript("chrome://marionette/content/EventUtils.js", EventUtils);
|
||||
|
||||
dump("Frame script loaded.\n");
|
||||
|
||||
|
@ -3,11 +3,14 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
const {require, loader} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const promise = require("promise");
|
||||
loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm", "Task");
|
||||
|
||||
const subScriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader);
|
||||
var EventUtils = {};
|
||||
subScriptLoader.loadSubScript("chrome://marionette/content/EventUtils.js", EventUtils);
|
||||
loader.lazyGetter(this, "nsIProfilerModule", () => {
|
||||
return Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
});
|
||||
@ -177,6 +180,20 @@ addMessageListener("devtools:test:setAttribute", function(msg) {
|
||||
sendAsyncMessage("devtools:test:setAttribute");
|
||||
});
|
||||
|
||||
/**
|
||||
* Synthesize a key event for an element. This handler doesn't send a message
|
||||
* back. Consumers should listen to specific events on the inspector/highlighter
|
||||
* to know when the event got synthesized.
|
||||
* @param {Object} msg The msg.data part expects the following properties:
|
||||
* - {String} key
|
||||
* - {Object} options
|
||||
*/
|
||||
addMessageListener("Test:SynthesizeKey", function(msg) {
|
||||
let {key, options} = msg.data;
|
||||
|
||||
EventUtils.synthesizeKey(key, options, content);
|
||||
});
|
||||
|
||||
/**
|
||||
* Like document.querySelector but can go into iframes too.
|
||||
* ".container iframe || .sub-container div" will first try to find the node
|
||||
|
@ -13,16 +13,8 @@ const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
var DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||
var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader);
|
||||
|
||||
// Set up a dummy environment so that EventUtils works. We need to be careful to
|
||||
// pass a window object into each EventUtils method we call rather than having
|
||||
// it rely on the |window| global.
|
||||
let EventUtils = {};
|
||||
EventUtils.window = {};
|
||||
EventUtils.parent = {};
|
||||
EventUtils._EU_Ci = Components.interfaces;
|
||||
EventUtils._EU_Cc = Components.classes;
|
||||
loader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
|
||||
var EventUtils = {};
|
||||
loader.loadSubScript("chrome://marionette/content/EventUtils.js", EventUtils);
|
||||
|
||||
const protocol = require("devtools/server/protocol");
|
||||
const {Arg, Option, method, RetVal, types} = protocol;
|
||||
|
291
testing/marionette/ChromeUtils.js
Normal file
291
testing/marionette/ChromeUtils.js
Normal file
@ -0,0 +1,291 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* ChromeUtils.js is a set of mochitest utilities that are used to
|
||||
* synthesize events in the browser. These are only used by
|
||||
* mochitest-chrome and browser-chrome tests. Originally these functions were in
|
||||
* EventUtils.js, but when porting to specialPowers, we didn't want
|
||||
* to move unnecessary functions.
|
||||
*
|
||||
* ChromeUtils.js depends on EventUtils.js being loaded.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Synthesize a query text content event.
|
||||
*
|
||||
* @param aOffset The character offset. 0 means the first character in the
|
||||
* selection root.
|
||||
* @param aLength The length of getting text. If the length is too long,
|
||||
* the extra length is ignored.
|
||||
* @param aWindow Optional (If null, current |window| will be used)
|
||||
* @return An nsIQueryContentEventResult object. If this failed,
|
||||
* the result might be null.
|
||||
*/
|
||||
function synthesizeQueryTextContent(aOffset, aLength, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (!utils) {
|
||||
return nullptr;
|
||||
}
|
||||
return utils.sendQueryContentEvent(utils.QUERY_TEXT_CONTENT,
|
||||
aOffset, aLength, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a query caret rect event.
|
||||
*
|
||||
* @param aOffset The caret offset. 0 means left side of the first character
|
||||
* in the selection root.
|
||||
* @param aWindow Optional (If null, current |window| will be used)
|
||||
* @return An nsIQueryContentEventResult object. If this failed,
|
||||
* the result might be null.
|
||||
*/
|
||||
function synthesizeQueryCaretRect(aOffset, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (!utils) {
|
||||
return nullptr;
|
||||
}
|
||||
return utils.sendQueryContentEvent(utils.QUERY_CARET_RECT,
|
||||
aOffset, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a query text rect event.
|
||||
*
|
||||
* @param aOffset The character offset. 0 means the first character in the
|
||||
* selection root.
|
||||
* @param aLength The length of the text. If the length is too long,
|
||||
* the extra length is ignored.
|
||||
* @param aWindow Optional (If null, current |window| will be used)
|
||||
* @return An nsIQueryContentEventResult object. If this failed,
|
||||
* the result might be null.
|
||||
*/
|
||||
function synthesizeQueryTextRect(aOffset, aLength, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (!utils) {
|
||||
return nullptr;
|
||||
}
|
||||
return utils.sendQueryContentEvent(utils.QUERY_TEXT_RECT,
|
||||
aOffset, aLength, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a query editor rect event.
|
||||
*
|
||||
* @param aWindow Optional (If null, current |window| will be used)
|
||||
* @return An nsIQueryContentEventResult object. If this failed,
|
||||
* the result might be null.
|
||||
*/
|
||||
function synthesizeQueryEditorRect(aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (!utils) {
|
||||
return nullptr;
|
||||
}
|
||||
return utils.sendQueryContentEvent(utils.QUERY_EDITOR_RECT, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a character at point event.
|
||||
*
|
||||
* @param aX, aY The offset in the client area of the DOM window.
|
||||
* @param aWindow Optional (If null, current |window| will be used)
|
||||
* @return An nsIQueryContentEventResult object. If this failed,
|
||||
* the result might be null.
|
||||
*/
|
||||
function synthesizeCharAtPoint(aX, aY, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (!utils) {
|
||||
return nullptr;
|
||||
}
|
||||
return utils.sendQueryContentEvent(utils.QUERY_CHARACTER_AT_POINT,
|
||||
0, 0, aX, aY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulate a dragstart event.
|
||||
* element - element to fire the dragstart event on
|
||||
* expectedDragData - the data you expect the data transfer to contain afterwards
|
||||
* This data is in the format:
|
||||
* [ [ {type: value, data: value, test: function}, ... ], ... ]
|
||||
* can be null
|
||||
* aWindow - optional; defaults to the current window object.
|
||||
* x - optional; initial x coordinate
|
||||
* y - optional; initial y coordinate
|
||||
* Returns null if data matches.
|
||||
* Returns the event.dataTransfer if data does not match
|
||||
*
|
||||
* eqTest is an optional function if comparison can't be done with x == y;
|
||||
* function (actualData, expectedData) {return boolean}
|
||||
* @param actualData from dataTransfer
|
||||
* @param expectedData from expectedDragData
|
||||
* see bug 462172 for example of use
|
||||
*
|
||||
*/
|
||||
function synthesizeDragStart(element, expectedDragData, aWindow, x, y)
|
||||
{
|
||||
if (!aWindow)
|
||||
aWindow = window;
|
||||
x = x || 2;
|
||||
y = y || 2;
|
||||
const step = 9;
|
||||
|
||||
var result = "trapDrag was not called";
|
||||
var trapDrag = function(event) {
|
||||
try {
|
||||
var dataTransfer = event.dataTransfer;
|
||||
result = null;
|
||||
if (!dataTransfer)
|
||||
throw "no dataTransfer";
|
||||
if (expectedDragData == null ||
|
||||
dataTransfer.mozItemCount != expectedDragData.length)
|
||||
throw dataTransfer;
|
||||
for (var i = 0; i < dataTransfer.mozItemCount; i++) {
|
||||
var dtTypes = dataTransfer.mozTypesAt(i);
|
||||
if (dtTypes.length != expectedDragData[i].length)
|
||||
throw dataTransfer;
|
||||
for (var j = 0; j < dtTypes.length; j++) {
|
||||
if (dtTypes[j] != expectedDragData[i][j].type)
|
||||
throw dataTransfer;
|
||||
var dtData = dataTransfer.mozGetDataAt(dtTypes[j],i);
|
||||
if (expectedDragData[i][j].eqTest) {
|
||||
if (!expectedDragData[i][j].eqTest(dtData, expectedDragData[i][j].data))
|
||||
throw dataTransfer;
|
||||
}
|
||||
else if (expectedDragData[i][j].data != dtData)
|
||||
throw dataTransfer;
|
||||
}
|
||||
}
|
||||
} catch(ex) {
|
||||
result = ex;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
aWindow.addEventListener("dragstart", trapDrag, false);
|
||||
synthesizeMouse(element, x, y, { type: "mousedown" }, aWindow);
|
||||
x += step; y += step;
|
||||
synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow);
|
||||
x += step; y += step;
|
||||
synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow);
|
||||
aWindow.removeEventListener("dragstart", trapDrag, false);
|
||||
synthesizeMouse(element, x, y, { type: "mouseup" }, aWindow);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulate a drop by emulating a dragstart and firing events dragenter, dragover, and drop.
|
||||
* srcElement - the element to use to start the drag, usually the same as destElement
|
||||
* but if destElement isn't suitable to start a drag on pass a suitable
|
||||
* element for srcElement
|
||||
* destElement - the element to fire the dragover, dragleave and drop events
|
||||
* dragData - the data to supply for the data transfer
|
||||
* This data is in the format:
|
||||
* [ [ {type: value, data: value}, ...], ... ]
|
||||
* dropEffect - the drop effect to set during the dragstart event, or 'move' if null
|
||||
* aWindow - optional; defaults to the current window object.
|
||||
* eventUtils - optional; allows you to pass in a reference to EventUtils.js.
|
||||
* If the eventUtils parameter is not passed in, we assume EventUtils.js is
|
||||
* in the scope. Used by browser-chrome tests.
|
||||
*
|
||||
* Returns the drop effect that was desired.
|
||||
*/
|
||||
function synthesizeDrop(srcElement, destElement, dragData, dropEffect, aWindow, eventUtils)
|
||||
{
|
||||
if (!aWindow)
|
||||
aWindow = window;
|
||||
|
||||
var synthesizeMouseAtCenter = (eventUtils || window).synthesizeMouseAtCenter;
|
||||
var synthesizeMouse = (eventUtils || window).synthesizeMouse;
|
||||
|
||||
var gWindowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
|
||||
getInterface(Components.interfaces.nsIDOMWindowUtils);
|
||||
var ds = Components.classes["@mozilla.org/widget/dragservice;1"].
|
||||
getService(Components.interfaces.nsIDragService);
|
||||
|
||||
var dataTransfer;
|
||||
var trapDrag = function(event) {
|
||||
dataTransfer = event.dataTransfer;
|
||||
for (var i = 0; i < dragData.length; i++) {
|
||||
var item = dragData[i];
|
||||
for (var j = 0; j < item.length; j++) {
|
||||
dataTransfer.mozSetDataAt(item[j].type, item[j].data, i);
|
||||
}
|
||||
}
|
||||
dataTransfer.dropEffect = dropEffect || "move";
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
ds.startDragSession();
|
||||
|
||||
try {
|
||||
// need to use real mouse action
|
||||
aWindow.addEventListener("dragstart", trapDrag, true);
|
||||
synthesizeMouseAtCenter(srcElement, { type: "mousedown" }, aWindow);
|
||||
|
||||
var rect = srcElement.getBoundingClientRect();
|
||||
var x = rect.width / 2;
|
||||
var y = rect.height / 2;
|
||||
synthesizeMouse(srcElement, x, y, { type: "mousemove" }, aWindow);
|
||||
synthesizeMouse(srcElement, x+10, y+10, { type: "mousemove" }, aWindow);
|
||||
aWindow.removeEventListener("dragstart", trapDrag, true);
|
||||
|
||||
event = aWindow.document.createEvent("DragEvents");
|
||||
event.initDragEvent("dragenter", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
|
||||
gWindowUtils.dispatchDOMEventViaPresShell(destElement, event, true);
|
||||
|
||||
var event = aWindow.document.createEvent("DragEvents");
|
||||
event.initDragEvent("dragover", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
|
||||
if (gWindowUtils.dispatchDOMEventViaPresShell(destElement, event, true)) {
|
||||
synthesizeMouseAtCenter(destElement, { type: "mouseup" }, aWindow);
|
||||
return "none";
|
||||
}
|
||||
|
||||
if (dataTransfer.dropEffect != "none") {
|
||||
event = aWindow.document.createEvent("DragEvents");
|
||||
event.initDragEvent("drop", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
|
||||
gWindowUtils.dispatchDOMEventViaPresShell(destElement, event, true);
|
||||
}
|
||||
|
||||
synthesizeMouseAtCenter(destElement, { type: "mouseup" }, aWindow);
|
||||
|
||||
return dataTransfer.dropEffect;
|
||||
} finally {
|
||||
ds.endDragSession(true);
|
||||
}
|
||||
};
|
||||
|
||||
var PluginUtils =
|
||||
{
|
||||
withTestPlugin : function(callback)
|
||||
{
|
||||
if (typeof Components == "undefined")
|
||||
{
|
||||
todo(false, "Not a Mozilla-based browser");
|
||||
return false;
|
||||
}
|
||||
|
||||
var ph = Components.classes["@mozilla.org/plugin/host;1"]
|
||||
.getService(Components.interfaces.nsIPluginHost);
|
||||
var tags = ph.getPluginTags();
|
||||
|
||||
// Find the test plugin
|
||||
for (var i = 0; i < tags.length; i++)
|
||||
{
|
||||
if (tags[i].name == "Test Plug-in")
|
||||
{
|
||||
callback(tags[i]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
todo(false, "Need a test plugin on this platform");
|
||||
return false;
|
||||
}
|
||||
};
|
673
testing/marionette/EventUtils.js
Normal file
673
testing/marionette/EventUtils.js
Normal file
@ -0,0 +1,673 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* EventUtils provides some utility methods for creating and sending DOM events.
|
||||
* Current methods:
|
||||
* sendMouseEvent
|
||||
* sendChar
|
||||
* sendString
|
||||
* sendKey
|
||||
* synthesizeMouse
|
||||
* synthesizeMouseAtCenter
|
||||
* synthesizeMouseScroll
|
||||
* synthesizeKey
|
||||
* synthesizeMouseExpectEvent
|
||||
* synthesizeKeyExpectEvent
|
||||
*
|
||||
* When adding methods to this file, please add a performance test for it.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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
|
||||
* object with the properties set that the real mouse event object should
|
||||
* have. This includes the type of the mouse event.
|
||||
* E.g. to send an click event to the node with id 'node' you might do this:
|
||||
*
|
||||
* sendMouseEvent({type:'click'}, 'node');
|
||||
*/
|
||||
function getElement(id) {
|
||||
return ((typeof(id) == "string") ?
|
||||
document.getElementById(id) : id);
|
||||
};
|
||||
|
||||
this.$ = this.getElement;
|
||||
const KeyEvent = Components.interfaces.nsIDOMKeyEvent;
|
||||
|
||||
function sendMouseEvent(aEvent, aTarget, aWindow) {
|
||||
if (['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) {
|
||||
throw new Error("sendMouseEvent doesn't know about event type '" + aEvent.type + "'");
|
||||
}
|
||||
|
||||
if (!aWindow) {
|
||||
aWindow = window;
|
||||
}
|
||||
|
||||
if (!(aTarget instanceof Element)) {
|
||||
aTarget = aWindow.document.getElementById(aTarget);
|
||||
}
|
||||
|
||||
var event = aWindow.document.createEvent('MouseEvent');
|
||||
|
||||
var typeArg = aEvent.type;
|
||||
var canBubbleArg = true;
|
||||
var cancelableArg = true;
|
||||
var viewArg = aWindow;
|
||||
var detailArg = aEvent.detail || (aEvent.type == 'click' ||
|
||||
aEvent.type == 'mousedown' ||
|
||||
aEvent.type == 'mouseup' ? 1 :
|
||||
aEvent.type == 'dblclick'? 2 : 0);
|
||||
var screenXArg = aEvent.screenX || 0;
|
||||
var screenYArg = aEvent.screenY || 0;
|
||||
var clientXArg = aEvent.clientX || 0;
|
||||
var clientYArg = aEvent.clientY || 0;
|
||||
var ctrlKeyArg = aEvent.ctrlKey || false;
|
||||
var altKeyArg = aEvent.altKey || false;
|
||||
var shiftKeyArg = aEvent.shiftKey || false;
|
||||
var metaKeyArg = aEvent.metaKey || false;
|
||||
var buttonArg = aEvent.button || 0;
|
||||
var relatedTargetArg = aEvent.relatedTarget || null;
|
||||
|
||||
event.initMouseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg,
|
||||
screenXArg, screenYArg, clientXArg, clientYArg,
|
||||
ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
|
||||
buttonArg, relatedTargetArg);
|
||||
|
||||
//removed: SpecialPowers.dispatchEvent(aWindow, aTarget, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the char aChar to the focused element. This method handles casing of
|
||||
* chars (sends the right charcode, and sends a shift key for uppercase chars).
|
||||
* No other modifiers are handled at this point.
|
||||
*
|
||||
* For now this method only works for English letters (lower and upper case)
|
||||
* and the digits 0-9.
|
||||
*/
|
||||
function sendChar(aChar, aWindow) {
|
||||
// DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9.
|
||||
var hasShift = (aChar == aChar.toUpperCase());
|
||||
synthesizeKey(aChar, { shiftKey: hasShift }, aWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the string aStr to the focused element.
|
||||
*
|
||||
* For now this method only works for English letters (lower and upper case)
|
||||
* and the digits 0-9.
|
||||
*/
|
||||
function sendString(aStr, aWindow) {
|
||||
for (var i = 0; i < aStr.length; ++i) {
|
||||
sendChar(aStr.charAt(i), aWindow);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the non-character key aKey to the focused node.
|
||||
* The name of the key should be the part that comes after "DOM_VK_" in the
|
||||
* KeyEvent constant name for this key.
|
||||
* No modifiers are handled at this point.
|
||||
*/
|
||||
function sendKey(aKey, aWindow) {
|
||||
var keyName = "VK_" + aKey.toUpperCase();
|
||||
synthesizeKey(keyName, { shiftKey: false }, aWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the key modifier flags from aEvent. Used to share code between
|
||||
* synthesizeMouse and synthesizeKey.
|
||||
*/
|
||||
function _parseModifiers(aEvent)
|
||||
{
|
||||
const masks = Components.interfaces.nsIDOMNSEvent;
|
||||
var mval = 0;
|
||||
if (aEvent.shiftKey)
|
||||
mval |= masks.SHIFT_MASK;
|
||||
if (aEvent.ctrlKey)
|
||||
mval |= masks.CONTROL_MASK;
|
||||
if (aEvent.altKey)
|
||||
mval |= masks.ALT_MASK;
|
||||
if (aEvent.metaKey)
|
||||
mval |= masks.META_MASK;
|
||||
if (aEvent.accelKey)
|
||||
mval |= (navigator.platform.indexOf("Mac") >= 0) ? masks.META_MASK :
|
||||
masks.CONTROL_MASK;
|
||||
|
||||
return mval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a mouse event on a target. The actual client point is determined
|
||||
* by taking the aTarget's client box and offseting it by aOffsetX and
|
||||
* aOffsetY. This allows mouse clicks to be simulated by calling this method.
|
||||
*
|
||||
* aEvent is an object which may contain the properties:
|
||||
* shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
|
||||
*
|
||||
* If the type is specified, an mouse event of that type is fired. Otherwise,
|
||||
* a mousedown followed by a mouse up is performed.
|
||||
*
|
||||
* aWindow is optional, and defaults to the current window object.
|
||||
*/
|
||||
function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
|
||||
{
|
||||
var rect = aTarget.getBoundingClientRect();
|
||||
synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
|
||||
aEvent, aWindow);
|
||||
}
|
||||
|
||||
/*
|
||||
* Synthesize a mouse event at a particular point in aWindow.
|
||||
*
|
||||
* aEvent is an object which may contain the properties:
|
||||
* shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
|
||||
*
|
||||
* If the type is specified, an mouse event of that type is fired. Otherwise,
|
||||
* a mousedown followed by a mouse up is performed.
|
||||
*
|
||||
* aWindow is optional, and defaults to the current window object.
|
||||
*/
|
||||
function synthesizeMouseAtPoint(left, top, aEvent, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
|
||||
if (utils) {
|
||||
var button = aEvent.button || 0;
|
||||
var clickCount = aEvent.clickCount || 1;
|
||||
var modifiers = _parseModifiers(aEvent);
|
||||
|
||||
if (("type" in aEvent) && aEvent.type) {
|
||||
utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers);
|
||||
}
|
||||
else {
|
||||
utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers);
|
||||
utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call synthesizeMouse with coordinates at the center of aTarget.
|
||||
function synthesizeMouseAtCenter(aTarget, aEvent, aWindow)
|
||||
{
|
||||
var rect = aTarget.getBoundingClientRect();
|
||||
synthesizeMouse(aTarget, rect.width / 2, rect.height / 2, aEvent,
|
||||
aWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a mouse scroll event on a target. The actual client point is determined
|
||||
* by taking the aTarget's client box and offseting it by aOffsetX and
|
||||
* aOffsetY.
|
||||
*
|
||||
* aEvent is an object which may contain the properties:
|
||||
* shiftKey, ctrlKey, altKey, metaKey, accessKey, button, type, axis, delta, hasPixels
|
||||
*
|
||||
* If the type is specified, a mouse scroll event of that type is fired. Otherwise,
|
||||
* "DOMMouseScroll" is used.
|
||||
*
|
||||
* If the axis is specified, it must be one of "horizontal" or "vertical". If not specified,
|
||||
* "vertical" is used.
|
||||
*
|
||||
* 'delta' is the amount to scroll by (can be positive or negative). It must
|
||||
* be specified.
|
||||
*
|
||||
* 'hasPixels' specifies whether kHasPixels should be set in the scrollFlags.
|
||||
*
|
||||
* 'isMomentum' specifies whether kIsMomentum should be set in the scrollFlags.
|
||||
*
|
||||
* aWindow is optional, and defaults to the current window object.
|
||||
*/
|
||||
function synthesizeMouseScroll(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
|
||||
if (utils) {
|
||||
// See nsMouseScrollFlags in nsGUIEvent.h
|
||||
const kIsVertical = 0x02;
|
||||
const kIsHorizontal = 0x04;
|
||||
const kHasPixels = 0x08;
|
||||
const kIsMomentum = 0x40;
|
||||
|
||||
var button = aEvent.button || 0;
|
||||
var modifiers = _parseModifiers(aEvent);
|
||||
|
||||
var rect = aTarget.getBoundingClientRect();
|
||||
|
||||
var left = rect.left;
|
||||
var top = rect.top;
|
||||
|
||||
var type = (("type" in aEvent) && aEvent.type) || "DOMMouseScroll";
|
||||
var axis = aEvent.axis || "vertical";
|
||||
var scrollFlags = (axis == "horizontal") ? kIsHorizontal : kIsVertical;
|
||||
if (aEvent.hasPixels) {
|
||||
scrollFlags |= kHasPixels;
|
||||
}
|
||||
if (aEvent.isMomentum) {
|
||||
scrollFlags |= kIsMomentum;
|
||||
}
|
||||
utils.sendMouseScrollEvent(type, left + aOffsetX, top + aOffsetY, button,
|
||||
scrollFlags, aEvent.delta, modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
function _computeKeyCodeFromChar(aChar)
|
||||
{
|
||||
if (aChar.length != 1) {
|
||||
return 0;
|
||||
}
|
||||
const nsIDOMKeyEvent = Components.interfaces.nsIDOMKeyEvent;
|
||||
if (aChar >= 'a' && aChar <= 'z') {
|
||||
return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'a'.charCodeAt(0);
|
||||
}
|
||||
if (aChar >= 'A' && aChar <= 'Z') {
|
||||
return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'A'.charCodeAt(0);
|
||||
}
|
||||
if (aChar >= '0' && aChar <= '9') {
|
||||
return nsIDOMKeyEvent.DOM_VK_0 + aChar.charCodeAt(0) - '0'.charCodeAt(0);
|
||||
}
|
||||
// returns US keyboard layout's keycode
|
||||
switch (aChar) {
|
||||
case '~':
|
||||
case '`':
|
||||
return nsIDOMKeyEvent.DOM_VK_BACK_QUOTE;
|
||||
case '!':
|
||||
return nsIDOMKeyEvent.DOM_VK_1;
|
||||
case '@':
|
||||
return nsIDOMKeyEvent.DOM_VK_2;
|
||||
case '#':
|
||||
return nsIDOMKeyEvent.DOM_VK_3;
|
||||
case '$':
|
||||
return nsIDOMKeyEvent.DOM_VK_4;
|
||||
case '%':
|
||||
return nsIDOMKeyEvent.DOM_VK_5;
|
||||
case '^':
|
||||
return nsIDOMKeyEvent.DOM_VK_6;
|
||||
case '&':
|
||||
return nsIDOMKeyEvent.DOM_VK_7;
|
||||
case '*':
|
||||
return nsIDOMKeyEvent.DOM_VK_8;
|
||||
case '(':
|
||||
return nsIDOMKeyEvent.DOM_VK_9;
|
||||
case ')':
|
||||
return nsIDOMKeyEvent.DOM_VK_0;
|
||||
case '-':
|
||||
case '_':
|
||||
return nsIDOMKeyEvent.DOM_VK_SUBTRACT;
|
||||
case '+':
|
||||
case '=':
|
||||
return nsIDOMKeyEvent.DOM_VK_EQUALS;
|
||||
case '{':
|
||||
case '[':
|
||||
return nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET;
|
||||
case '}':
|
||||
case ']':
|
||||
return nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET;
|
||||
case '|':
|
||||
case '\\':
|
||||
return nsIDOMKeyEvent.DOM_VK_BACK_SLASH;
|
||||
case ':':
|
||||
case ';':
|
||||
return nsIDOMKeyEvent.DOM_VK_SEMICOLON;
|
||||
case '\'':
|
||||
case '"':
|
||||
return nsIDOMKeyEvent.DOM_VK_QUOTE;
|
||||
case '<':
|
||||
case ',':
|
||||
return nsIDOMKeyEvent.DOM_VK_COMMA;
|
||||
case '>':
|
||||
case '.':
|
||||
return nsIDOMKeyEvent.DOM_VK_PERIOD;
|
||||
case '?':
|
||||
case '/':
|
||||
return nsIDOMKeyEvent.DOM_VK_SLASH;
|
||||
case '\n':
|
||||
return nsIDOMKeyEvent.DOM_VK_RETURN;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* isKeypressFiredKey() returns TRUE if the given key should cause keypress
|
||||
* event when widget handles the native key event. Otherwise, FALSE.
|
||||
*
|
||||
* aDOMKeyCode should be one of consts of nsIDOMKeyEvent::DOM_VK_*, or a key
|
||||
* name begins with "VK_", or a character.
|
||||
*/
|
||||
function isKeypressFiredKey(aDOMKeyCode)
|
||||
{
|
||||
const KeyEvent = Components.interfaces.nsIDOMKeyEvent;
|
||||
if (typeof(aDOMKeyCode) == "string") {
|
||||
if (aDOMKeyCode.indexOf("VK_") == 0) {
|
||||
aDOMKeyCode = KeyEvent["DOM_" + aDOMKeyCode];
|
||||
if (!aDOMKeyCode) {
|
||||
throw "Unknown key: " + aDOMKeyCode;
|
||||
}
|
||||
} else {
|
||||
// If the key generates a character, it must cause a keypress event.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
switch (aDOMKeyCode) {
|
||||
case KeyEvent.DOM_VK_SHIFT:
|
||||
case KeyEvent.DOM_VK_CONTROL:
|
||||
case KeyEvent.DOM_VK_ALT:
|
||||
case KeyEvent.DOM_VK_CAPS_LOCK:
|
||||
case KeyEvent.DOM_VK_NUM_LOCK:
|
||||
case KeyEvent.DOM_VK_SCROLL_LOCK:
|
||||
case KeyEvent.DOM_VK_META:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a key event. It is targeted at whatever would be targeted by an
|
||||
* actual keypress by the user, typically the focused element.
|
||||
*
|
||||
* aKey should be either a character or a keycode starting with VK_ such as
|
||||
* VK_RETURN.
|
||||
*
|
||||
* aEvent is an object which may contain the properties:
|
||||
* shiftKey, ctrlKey, altKey, metaKey, accessKey, type
|
||||
*
|
||||
* If the type is specified, a key event of that type is fired. Otherwise,
|
||||
* a keydown, a keypress and then a keyup event are fired in sequence.
|
||||
*
|
||||
* aWindow is optional, and defaults to the current window object.
|
||||
*/
|
||||
function synthesizeKey(aKey, aEvent, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (utils) {
|
||||
var keyCode = 0, charCode = 0;
|
||||
if (aKey.indexOf("VK_") == 0) {
|
||||
keyCode = KeyEvent["DOM_" + aKey];
|
||||
if (!keyCode) {
|
||||
throw "Unknown key: " + aKey;
|
||||
}
|
||||
} else {
|
||||
charCode = aKey.charCodeAt(0);
|
||||
keyCode = _computeKeyCodeFromChar(aKey.charAt(0));
|
||||
}
|
||||
|
||||
var modifiers = _parseModifiers(aEvent);
|
||||
|
||||
if (!("type" in aEvent) || !aEvent.type) {
|
||||
// Send keydown + (optional) keypress + keyup events.
|
||||
var keyDownDefaultHappened =
|
||||
utils.sendKeyEvent("keydown", keyCode, 0, modifiers);
|
||||
if (isKeypressFiredKey(keyCode)) {
|
||||
utils.sendKeyEvent("keypress", charCode ? 0 : keyCode, charCode,
|
||||
modifiers, !keyDownDefaultHappened);
|
||||
}
|
||||
utils.sendKeyEvent("keyup", keyCode, 0, modifiers);
|
||||
} else if (aEvent.type == "keypress") {
|
||||
// Send standalone keypress event.
|
||||
utils.sendKeyEvent(aEvent.type, charCode ? 0 : keyCode,
|
||||
charCode, modifiers);
|
||||
} else {
|
||||
// Send other standalone event than keypress.
|
||||
utils.sendKeyEvent(aEvent.type, keyCode, 0, modifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _gSeenEvent = false;
|
||||
|
||||
/**
|
||||
* Indicate that an event with an original target of aExpectedTarget and
|
||||
* a type of aExpectedEvent is expected to be fired, or not expected to
|
||||
* be fired.
|
||||
*/
|
||||
function _expectEvent(aExpectedTarget, aExpectedEvent, aTestName)
|
||||
{
|
||||
if (!aExpectedTarget || !aExpectedEvent)
|
||||
return null;
|
||||
|
||||
_gSeenEvent = false;
|
||||
|
||||
var type = (aExpectedEvent.charAt(0) == "!") ?
|
||||
aExpectedEvent.substring(1) : aExpectedEvent;
|
||||
var eventHandler = function(event) {
|
||||
var epassed = (!_gSeenEvent && event.originalTarget == aExpectedTarget &&
|
||||
event.type == type);
|
||||
is(epassed, true, aTestName + " " + type + " event target " + (_gSeenEvent ? "twice" : ""));
|
||||
_gSeenEvent = true;
|
||||
};
|
||||
|
||||
aExpectedTarget.addEventListener(type, eventHandler, false);
|
||||
return eventHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the event was fired or not. The event handler aEventHandler
|
||||
* will be removed.
|
||||
*/
|
||||
function _checkExpectedEvent(aExpectedTarget, aExpectedEvent, aEventHandler, aTestName)
|
||||
{
|
||||
if (aEventHandler) {
|
||||
var expectEvent = (aExpectedEvent.charAt(0) != "!");
|
||||
var type = expectEvent ? aExpectedEvent : aExpectedEvent.substring(1);
|
||||
aExpectedTarget.removeEventListener(type, aEventHandler, false);
|
||||
var desc = type + " event";
|
||||
if (!expectEvent)
|
||||
desc += " not";
|
||||
is(_gSeenEvent, expectEvent, aTestName + " " + desc + " fired");
|
||||
}
|
||||
|
||||
_gSeenEvent = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to synthesizeMouse except that a test is performed to see if an
|
||||
* event is fired at the right target as a result.
|
||||
*
|
||||
* aExpectedTarget - the expected originalTarget of the event.
|
||||
* aExpectedEvent - the expected type of the event, such as 'select'.
|
||||
* aTestName - the test name when outputing results
|
||||
*
|
||||
* To test that an event is not fired, use an expected type preceded by an
|
||||
* exclamation mark, such as '!select'. This might be used to test that a
|
||||
* click on a disabled element doesn't fire certain events for instance.
|
||||
*
|
||||
* aWindow is optional, and defaults to the current window object.
|
||||
*/
|
||||
function synthesizeMouseExpectEvent(aTarget, aOffsetX, aOffsetY, aEvent,
|
||||
aExpectedTarget, aExpectedEvent, aTestName,
|
||||
aWindow)
|
||||
{
|
||||
var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
|
||||
synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow);
|
||||
_checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to synthesizeKey except that a test is performed to see if an
|
||||
* event is fired at the right target as a result.
|
||||
*
|
||||
* aExpectedTarget - the expected originalTarget of the event.
|
||||
* aExpectedEvent - the expected type of the event, such as 'select'.
|
||||
* aTestName - the test name when outputing results
|
||||
*
|
||||
* To test that an event is not fired, use an expected type preceded by an
|
||||
* exclamation mark, such as '!select'.
|
||||
*
|
||||
* aWindow is optional, and defaults to the current window object.
|
||||
*/
|
||||
function synthesizeKeyExpectEvent(key, aEvent, aExpectedTarget, aExpectedEvent,
|
||||
aTestName, aWindow)
|
||||
{
|
||||
var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
|
||||
synthesizeKey(key, aEvent, aWindow);
|
||||
_checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
|
||||
}
|
||||
|
||||
function disableNonTestMouseEvents(aDisable)
|
||||
{
|
||||
var domutils = _getDOMWindowUtils();
|
||||
domutils.disableNonTestMouseEvents(aDisable);
|
||||
}
|
||||
|
||||
function _getDOMWindowUtils(aWindow)
|
||||
{
|
||||
if (!aWindow) {
|
||||
aWindow = window;
|
||||
}
|
||||
|
||||
//TODO: this is assuming we are in chrome space
|
||||
return aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
|
||||
getInterface(Components.interfaces.nsIDOMWindowUtils);
|
||||
}
|
||||
|
||||
// Must be synchronized with nsIDOMWindowUtils.
|
||||
const COMPOSITION_ATTR_RAWINPUT = 0x02;
|
||||
const COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03;
|
||||
const COMPOSITION_ATTR_CONVERTEDTEXT = 0x04;
|
||||
const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;
|
||||
|
||||
/**
|
||||
* Synthesize a composition event.
|
||||
*
|
||||
* @param aEvent The composition event information. This must
|
||||
* have |type| member. The value must be
|
||||
* "compositionstart", "compositionend" or
|
||||
* "compositionupdate".
|
||||
* And also this may have |data| and |locale| which
|
||||
* would be used for the value of each property of
|
||||
* the composition event. Note that the data would
|
||||
* be ignored if the event type were
|
||||
* "compositionstart".
|
||||
* @param aWindow Optional (If null, current |window| will be used)
|
||||
*/
|
||||
function synthesizeComposition(aEvent, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (!utils) {
|
||||
return;
|
||||
}
|
||||
|
||||
utils.sendCompositionEvent(aEvent.type, aEvent.data ? aEvent.data : "",
|
||||
aEvent.locale ? aEvent.locale : "");
|
||||
}
|
||||
/**
|
||||
* Synthesize a text event.
|
||||
*
|
||||
* @param aEvent The text event's information, this has |composition|
|
||||
* and |caret| members. |composition| has |string| and
|
||||
* |clauses| members. |clauses| must be array object. Each
|
||||
* object has |length| and |attr|. And |caret| has |start| and
|
||||
* |length|. See the following tree image.
|
||||
*
|
||||
* aEvent
|
||||
* +-- composition
|
||||
* | +-- string
|
||||
* | +-- clauses[]
|
||||
* | +-- length
|
||||
* | +-- attr
|
||||
* +-- caret
|
||||
* +-- start
|
||||
* +-- length
|
||||
*
|
||||
* Set the composition string to |composition.string|. Set its
|
||||
* clauses information to the |clauses| array.
|
||||
*
|
||||
* When it's composing, set the each clauses' length to the
|
||||
* |composition.clauses[n].length|. The sum of the all length
|
||||
* values must be same as the length of |composition.string|.
|
||||
* Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the
|
||||
* |composition.clauses[n].attr|.
|
||||
*
|
||||
* When it's not composing, set 0 to the
|
||||
* |composition.clauses[0].length| and
|
||||
* |composition.clauses[0].attr|.
|
||||
*
|
||||
* Set caret position to the |caret.start|. It's offset from
|
||||
* the start of the composition string. Set caret length to
|
||||
* |caret.length|. If it's larger than 0, it should be wide
|
||||
* caret. However, current nsEditor doesn't support wide
|
||||
* caret, therefore, you should always set 0 now.
|
||||
*
|
||||
* @param aWindow Optional (If null, current |window| will be used)
|
||||
*/
|
||||
function synthesizeText(aEvent, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (!utils) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aEvent.composition || !aEvent.composition.clauses ||
|
||||
!aEvent.composition.clauses[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var firstClauseLength = aEvent.composition.clauses[0].length;
|
||||
var firstClauseAttr = aEvent.composition.clauses[0].attr;
|
||||
var secondClauseLength = 0;
|
||||
var secondClauseAttr = 0;
|
||||
var thirdClauseLength = 0;
|
||||
var thirdClauseAttr = 0;
|
||||
if (aEvent.composition.clauses[1]) {
|
||||
secondClauseLength = aEvent.composition.clauses[1].length;
|
||||
secondClauseAttr = aEvent.composition.clauses[1].attr;
|
||||
if (aEvent.composition.clauses[2]) {
|
||||
thirdClauseLength = aEvent.composition.clauses[2].length;
|
||||
thirdClauseAttr = aEvent.composition.clauses[2].attr;
|
||||
}
|
||||
}
|
||||
|
||||
var caretStart = -1;
|
||||
var caretLength = 0;
|
||||
if (aEvent.caret) {
|
||||
caretStart = aEvent.caret.start;
|
||||
caretLength = aEvent.caret.length;
|
||||
}
|
||||
|
||||
utils.sendTextEvent(aEvent.composition.string,
|
||||
firstClauseLength, firstClauseAttr,
|
||||
secondClauseLength, secondClauseAttr,
|
||||
thirdClauseLength, thirdClauseAttr,
|
||||
caretStart, caretLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a query selected text event.
|
||||
*
|
||||
* @param aWindow Optional (If null, current |window| will be used)
|
||||
* @return An nsIQueryContentEventResult object. If this failed,
|
||||
* the result might be null.
|
||||
*/
|
||||
function synthesizeQuerySelectedText(aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (!utils) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return utils.sendQueryContentEvent(utils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a selection set event.
|
||||
*
|
||||
* @param aOffset The character offset. 0 means the first character in the
|
||||
* selection root.
|
||||
* @param aLength The length of the text. If the length is too long,
|
||||
* the extra length is ignored.
|
||||
* @param aReverse If true, the selection is from |aOffset + aLength| to
|
||||
* |aOffset|. Otherwise, from |aOffset| to |aOffset + aLength|.
|
||||
* @param aWindow Optional (If null, current |window| will be used)
|
||||
* @return True, if succeeded. Otherwise false.
|
||||
*/
|
||||
function synthesizeSelectionSet(aOffset, aLength, aReverse, aWindow)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
if (!utils) {
|
||||
return false;
|
||||
}
|
||||
return utils.sendSelectionSetEvent(aOffset, aLength, aReverse);
|
||||
}
|
@ -7,21 +7,19 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
|
||||
Cu.import("chrome://marionette/content/event.js");
|
||||
|
||||
const CONTEXT_MENU_DELAY_PREF = "ui.click_hold_context_menus.delay";
|
||||
const DEFAULT_CONTEXT_MENU_DELAY = 750; // ms
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["action"];
|
||||
this.EXPORTED_SYMBOLS = ["actions"];
|
||||
|
||||
const logger = Log.repository.getLogger("Marionette");
|
||||
|
||||
this.action = {};
|
||||
this.actions = {};
|
||||
|
||||
/**
|
||||
* Functionality for (single finger) action chains.
|
||||
*/
|
||||
action.Chain = function(checkForInterrupted) {
|
||||
actions.Chain = function(utils, checkForInterrupted) {
|
||||
// for assigning unique ids to all touches
|
||||
this.nextTouchId = 1000;
|
||||
// keep track of active Touches
|
||||
@ -45,9 +43,12 @@ action.Chain = function(checkForInterrupted) {
|
||||
|
||||
// determines if we create touch events
|
||||
this.inputSource = null;
|
||||
|
||||
// test utilities providing some event synthesis code
|
||||
this.utils = utils;
|
||||
};
|
||||
|
||||
action.Chain.prototype.dispatchActions = function(
|
||||
actions.Chain.prototype.dispatchActions = function(
|
||||
args,
|
||||
touchId,
|
||||
container,
|
||||
@ -105,7 +106,7 @@ action.Chain.prototype.dispatchActions = function(
|
||||
* @param {Object} modifiers
|
||||
* An object of modifier keys present.
|
||||
*/
|
||||
action.Chain.prototype.emitMouseEvent = function(
|
||||
actions.Chain.prototype.emitMouseEvent = function(
|
||||
doc,
|
||||
type,
|
||||
elClientX,
|
||||
@ -126,7 +127,7 @@ action.Chain.prototype.emitMouseEvent = function(
|
||||
|
||||
let mods;
|
||||
if (typeof modifiers != "undefined") {
|
||||
mods = event.parseModifiers_(modifiers);
|
||||
mods = this.utils._parseModifiers(modifiers);
|
||||
} else {
|
||||
mods = 0;
|
||||
}
|
||||
@ -147,7 +148,7 @@ action.Chain.prototype.emitMouseEvent = function(
|
||||
/**
|
||||
* Reset any persisted values after a command completes.
|
||||
*/
|
||||
action.Chain.prototype.resetValues = function() {
|
||||
actions.Chain.prototype.resetValues = function() {
|
||||
this.onSuccess = null;
|
||||
this.onError = null;
|
||||
this.container = null;
|
||||
@ -163,7 +164,7 @@ action.Chain.prototype.resetValues = function() {
|
||||
* keyModifiers is an object keeping track keyDown/keyUp pairs through
|
||||
* an action chain.
|
||||
*/
|
||||
action.Chain.prototype.actions = function(chain, touchId, i, keyModifiers) {
|
||||
actions.Chain.prototype.actions = function(chain, touchId, i, keyModifiers) {
|
||||
if (i == chain.length) {
|
||||
this.onSuccess(touchId || null);
|
||||
this.resetValues();
|
||||
@ -186,12 +187,12 @@ action.Chain.prototype.actions = function(chain, touchId, i, keyModifiers) {
|
||||
|
||||
switch(command) {
|
||||
case "keyDown":
|
||||
event.sendKeyDown(pack[1], keyModifiers, this.container.frame);
|
||||
this.utils.sendKeyDown(pack[1], keyModifiers, this.container.frame);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
|
||||
case "keyUp":
|
||||
event.sendKeyUp(pack[1], keyModifiers, this.container.frame);
|
||||
this.utils.sendKeyUp(pack[1], keyModifiers, this.container.frame);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
|
||||
@ -323,7 +324,7 @@ action.Chain.prototype.actions = function(chain, touchId, i, keyModifiers) {
|
||||
* Y coordinate relative to target. If unspecified, the centre of
|
||||
* the target is used.
|
||||
*/
|
||||
action.Chain.prototype.coordinates = function(target, x, y) {
|
||||
actions.Chain.prototype.coordinates = function(target, x, y) {
|
||||
let box = target.getBoundingClientRect();
|
||||
if (x == null) {
|
||||
x = box.width / 2;
|
||||
@ -341,7 +342,7 @@ action.Chain.prototype.coordinates = function(target, x, y) {
|
||||
* Given an element and a pair of coordinates, returns an array of the
|
||||
* form [clientX, clientY, pageX, pageY, screenX, screenY].
|
||||
*/
|
||||
action.Chain.prototype.getCoordinateInfo = function(el, corx, cory) {
|
||||
actions.Chain.prototype.getCoordinateInfo = function(el, corx, cory) {
|
||||
let win = el.ownerDocument.defaultView;
|
||||
return [
|
||||
corx, // clientX
|
||||
@ -361,7 +362,7 @@ action.Chain.prototype.getCoordinateInfo = function(el, corx, cory) {
|
||||
* Y coordinate of the location to generate the event that is relative
|
||||
* to the viewport.
|
||||
*/
|
||||
action.Chain.prototype.generateEvents = function(
|
||||
actions.Chain.prototype.generateEvents = function(
|
||||
type, x, y, touchId, target, keyModifiers) {
|
||||
this.lastCoordinates = [x, y];
|
||||
let doc = this.container.frame.document;
|
||||
@ -494,7 +495,7 @@ action.Chain.prototype.generateEvents = function(
|
||||
this.checkForInterrupted();
|
||||
};
|
||||
|
||||
action.Chain.prototype.mouseTap = function(doc, x, y, button, count, mod) {
|
||||
actions.Chain.prototype.mouseTap = function(doc, x, y, button, count, mod) {
|
||||
this.emitMouseEvent(doc, "mousemove", x, y, button, count, mod);
|
||||
this.emitMouseEvent(doc, "mousedown", x, y, button, count, mod);
|
||||
this.emitMouseEvent(doc, "mouseup", x, y, button, count, mod);
|
29
testing/marionette/atoms/HOWTO
Normal file
29
testing/marionette/atoms/HOWTO
Normal file
@ -0,0 +1,29 @@
|
||||
These atoms are generated from the selenium trunk. They are minified versions of what's in the trunk,
|
||||
optimized to run on Firefox. To generate them, clone the repo:
|
||||
|
||||
svn checkout http://selenium.googlecode.com/svn/trunk/ selenium-read-only
|
||||
|
||||
then run the Google closure compiler and specify which atom you'd like to get.
|
||||
For example, this will generate the "get_text" atom:
|
||||
|
||||
cd selenium-read-only
|
||||
./go //javascript/webdriver/atoms:get_text:firefox
|
||||
|
||||
This generates the atom, which is a function. You'll need to assign that function to a variable of your choice
|
||||
which you can then import, i.e.: you'll need to modify the atom with a variable assignment:
|
||||
|
||||
var myVar = <atom code>
|
||||
|
||||
You can now import this atom and call it with myVar(). Please note the name of the function as a comment above this line to help readability in the atoms file.
|
||||
|
||||
For more information on atoms, refer to http://code.google.com/p/selenium/wiki/AutomationAtoms#Atoms_Summary
|
||||
|
||||
Currently bundled atoms (please update as you add more):
|
||||
- clearElement
|
||||
- click
|
||||
- getAttributeValue
|
||||
- getElementText
|
||||
- isElementDisplayed
|
||||
- isElementEnabled
|
||||
- isElementSelected
|
||||
- sendKeysToElement/type
|
@ -12,11 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["atom"];
|
||||
|
||||
this.atom = {};
|
||||
|
||||
atom.clearElement = function(element, window){return function(){function g(a){throw a;}var h=void 0,i=!0,k=null,l=!1;function n(a){return function(){return this[a]}}function o(a){return function(){return a}}var p,q=this;
|
||||
//clearElement
|
||||
var clearElement = function(){return function(){function g(a){throw a;}var h=void 0,i=!0,k=null,l=!1;function n(a){return function(){return this[a]}}function o(a){return function(){return a}}var p,q=this;
|
||||
function aa(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
|
||||
else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function r(a){return a!==h}function ba(a){var b=aa(a);return"array"==b||"object"==b&&"number"==typeof a.length}function t(a){return"string"==typeof a}function w(a){return"function"==aa(a)}function ca(a){a=aa(a);return"object"==a||"array"==a||"function"==a}var da="closure_uid_"+Math.floor(2147483648*Math.random()).toString(36),ea=0,fa=Date.now||function(){return+new Date};
|
||||
function x(a,b){function c(){}c.prototype=b.prototype;a.$=b.prototype;a.prototype=new c};function ga(a,b){for(var c=1;c<arguments.length;c++)var d=(""+arguments[c]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a}function ha(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")}function ia(a){if(!ja.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(ka,"&"));-1!=a.indexOf("<")&&(a=a.replace(la,"<"));-1!=a.indexOf(">")&&(a=a.replace(ma,">"));-1!=a.indexOf('"')&&(a=a.replace(na,"""));return a}var ka=/&/g,la=/</g,ma=/>/g,na=/\"/g,ja=/[&<>\"]/;
|
||||
@ -89,8 +86,8 @@ p.H=k;p.V=0;p.b=function(){return this.H[0].b()};p.g=function(){return z(this.H)
|
||||
function Lc(a,b,c,d){if(a==c)return d<b;var e;if(1==a.nodeType&&b)if(e=a.childNodes[b])a=e,b=0;else if(G(a,c))return i;if(1==c.nodeType&&d)if(e=c.childNodes[d])c=e,d=0;else if(G(c,a))return l;return 0<(cb(a,c)||b-d)};function dd(){N.call(this);this.ja=k;this.B=new B(0,0);this.sa=l}x(dd,N);var Z={};Z[Zb]=[0,1,2,k];Z[$b]=[k,k,2,k];Z[fc]=[0,1,2,k];Z[dc]=[0,0,0,0];Z[cc]=[0,0,0,0];Z[ac]=Z[Zb];Z[bc]=Z[fc];Z[ec]=Z[dc];dd.prototype.move=function(a,b){var c=xb(a);this.B.x=b.x+c.x;this.B.y=b.y+c.y;a!=this.z()&&(c=this.z()===Ca.document.documentElement||this.z()===Ca.document.body,c=!this.sa&&c?k:this.z(),ed(this,dc,a),Ob(this,a),ed(this,ec,c));ed(this,cc)};
|
||||
function ed(a,b,c){a.sa=i;var d=a.B,e;b in Z?(e=Z[b][a.ja===k?3:a.ja],e===k&&g(new y(13,"Event does not permit the specified mouse button."))):e=0;Mb(a.w,i)&&Eb(a.w)&&(c&&!(ec==b||dc==b)&&g(new y(12,"Event type does not allow related target: "+b)),c={clientX:d.x,clientY:d.y,button:e,altKey:l,ctrlKey:l,shiftKey:l,metaKey:l,wheelDelta:0,relatedTarget:c||k},(a=a.w)&&Sb(a,b,c))};function fd(){N.call(this);this.B=new B(0,0);this.ca=new B(0,0)}x(fd,N);fd.prototype.xa=0;fd.prototype.wa=0;fd.prototype.move=function(a,b,c){this.X()||Ob(this,a);a=xb(a);this.B.x=b.x+a.x;this.B.y=b.y+a.y;r(c)&&(this.ca.x=c.x+a.x,this.ca.y=c.y+a.y);if(this.X()){b=Rb;this.X()||g(new y(13,"Should never fire event when touchscreen is not pressed."));var d,e;this.wa&&(d=this.wa,e=this.ca);Pb(this,b,this.xa,this.B,d,e)}};fd.prototype.X=function(){return!!this.xa};function gd(a,b){this.x=a;this.y=b}x(gd,B);gd.prototype.scale=function(a){this.x*=a;this.y*=a;return this};gd.prototype.add=function(a){this.x+=a.x;this.y+=a.y;return this};function hd(){N.call(this)}x(hd,N);(function(a){a.Ba=function(){return a.Da||(a.Da=new a)}})(hd);function id(a){(!Mb(a,i)||!Eb(a))&&g(new y(12,"Element is not currently interactable and may not be manipulated"));(!Gb(a)||Bb(a,"readOnly"))&&g(new y(12,"Element must be user-editable in order to clear it."));var b=hd.Ba();Ob(b,a);var b=b.ua||b.w,c=D(b).activeElement;if(b!=c){if(c&&w(c.blur))try{c.blur()}catch(d){g(d)}w(b.focus)&&b.focus()}a.value&&(a.value="",Sb(a,Yb));Hb(a)&&(a.innerHTML=" ")}var jd=["_"],$=q;!(jd[0]in $)&&$.execScript&&$.execScript("var "+jd[0]);
|
||||
for(var ld;jd.length&&(ld=jd.shift());)!jd.length&&r(id)?$[ld]=id:$=$[ld]?$[ld]:$[ld]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
|
||||
|
||||
atom.getElementAttribute = function(element, name, window){return function(){var f=null,g=!1,h=this;
|
||||
//getElementAttribute
|
||||
var getElementAttribute = function(){return function(){var f=null,g=!1,h=this;
|
||||
function i(a){var c=typeof a;if("object"==c)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return c;var b=Object.prototype.toString.call(a);if("[object Window]"==b)return"object";if("[object Array]"==b||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==b||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if("function"==
|
||||
c&&"undefined"==typeof a.call)return"object";return c}function j(a,c){function b(){}b.prototype=c.prototype;a.f=c.prototype;a.prototype=new b};function k(a,c){for(var b=1;b<arguments.length;b++)var d=(""+arguments[b]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a}function l(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")};var m,n="",o=/rv\:([^\);]+)(\)|;)/.exec(h.navigator?h.navigator.userAgent:f);m=n=o?o[1]:"";var p={};function q(a,c){this.code=a;this.message=c||"";this.name=r[a]||r[13];var b=Error(this.message);b.name=this.name;this.stack=b.stack||""}j(q,Error);
|
||||
var r={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
|
||||
@ -101,8 +98,8 @@ L.prototype.splice=function(a){var c=this.b,b=this.a?1:-1;this.c==b&&(this.c=-1*
|
||||
function S(a,c){var b=Q[c]||c,d=a[b];if(void 0===d&&0<=u(R,b))return g;if(b="value"==c)if(b=O(a,"OPTION")){var e;b=c.toLowerCase();if(a.hasAttribute)e=a.hasAttribute(b);else try{e=a.attributes[b].specified}catch(Y){e=g}b=!e}b&&(d=[],I(a,d,g),d=d.join(""));return d}var T="async,autofocus,autoplay,checked,compact,complete,controls,declare,defaultchecked,defaultselected,defer,disabled,draggable,ended,formnovalidate,hidden,indeterminate,iscontenteditable,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,paused,pubdate,readonly,required,reversed,scoped,seamless,seeking,selected,spellcheck,truespeed,willvalidate".split(",");
|
||||
function U(a,c){if(8==a.nodeType)return f;c=c.toLowerCase();if("style"==c){var b=l(a.style.cssText).toLowerCase();return b=";"==b.charAt(b.length-1)?b:b+";"}b=a.getAttributeNode(c);return!b?f:0<=u(T,c)?"true":b.specified?b.value:f};function V(a,c){var b=f,d=c.toLowerCase();if("style"==c.toLowerCase()){if((b=a.style)&&"string"!=typeof b)b=b.cssText;return b}if("selected"==d||"checked"==d&&P(a)){if(!P(a))throw new q(15,"Element is not selectable");var e="selected",d=a.type&&a.type.toLowerCase();if("checkbox"==d||"radio"==d)e="checked";return S(a,e)?"true":f}b=O(a,"A");if(O(a,"IMG")&&"src"==d||b&&"href"==d)return(b=U(a,d))&&(b=S(a,d)),b;try{e=S(a,c)}catch(Y){}if(!(d=e==f))d=i(e),d="object"==d||"array"==d||"function"==d;b=d?U(a,
|
||||
c):e;return b!=f?b.toString():f}var W=["_"],X=h;!(W[0]in X)&&X.execScript&&X.execScript("var "+W[0]);for(var Z;W.length&&(Z=W.shift());)!W.length&&void 0!==V?X[Z]=V:X=X[Z]?X[Z]:X[Z]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
|
||||
|
||||
atom.getElementText = function(element, window){return function(){var g=void 0,h=!0,i=null,j=!1,k=this;
|
||||
//getElementText
|
||||
var getElementText = function(){return function(){var g=void 0,h=!0,i=null,j=!1,k=this;
|
||||
function l(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
|
||||
else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function m(a){return"string"==typeof a}function n(a,b){function c(){}c.prototype=b.prototype;a.h=b.prototype;a.prototype=new c};function o(a){var b=a.length-1;return 0<=b&&a.indexOf(" ",b)==b}function aa(a,b){for(var c=1;c<arguments.length;c++)var d=(""+arguments[c]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a}function p(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")}var q={};function ba(a){return q[a]||(q[a]=(""+a).replace(/\-([a-z])/g,function(a,c){return c.toUpperCase()}))};var s,ca="",u=/rv\:([^\);]+)(\)|;)/.exec(k.navigator?k.navigator.userAgent:i);s=ca=u?u[1]:"";var v={};var da=window;function w(a,b){this.code=a;this.message=b||"";this.name=x[a]||x[13];var c=Error(this.message);c.name=this.name;this.stack=c.stack||""}n(w,Error);
|
||||
var x={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
|
||||
@ -127,19 +124,20 @@ function Aa(a,b){if(U(a,"BR"))b.push("");else{var c=U(a,"TD"),d=W(a,"display"),e
|
||||
function Ca(a,b,c,d){a=a.nodeValue.replace(/\u200b/g,"");a=a.replace(/(\r\n|\r|\n)/g,"\n");if("normal"==c||"nowrap"==c)a=a.replace(/\n/g," ");a="pre"==c||"pre-wrap"==c?a.replace(/[ \f\t\v\u2028\u2029]/g,"\u00a0"):a.replace(/[\ \f\t\v\u2028\u2029]+/g," ");"capitalize"==d?a=a.replace(/(^|\s)(\S)/g,function(a,b,c){return b+c.toUpperCase()}):"uppercase"==d?a=a.toUpperCase():"lowercase"==d&&(a=a.toLowerCase());c=b.pop()||"";o(c)&&0==a.lastIndexOf(" ",0)&&(a=a.substr(1));b.push(c+a)}
|
||||
function ya(a){var b=1,c=W(a,"opacity");c&&(b=Number(c));(a=V(a))&&(b*=ya(a));return b};function Da(a){var b;a:{for(b=a;b;){if(b.tagName&&"head"==b.tagName.toLowerCase()){b=h;break a}try{b=b.parentNode}catch(c){break}}b=j}if(b)return b=L(a),"TITLE"==a.tagName.toUpperCase()&&(b?b.parentWindow||b.defaultView:window)==da.top?p(b.title):"";b=[];Aa(a,b);var d=b,a=d.length;b=Array(a);for(var d=m(d)?d.split(""):d,e=0;e<a;e++)e in d&&(b[e]=za.call(g,d[e]));return za(b.join("\n")).replace(/\xa0/g," ")}var Y=["_"],Z=k;!(Y[0]in Z)&&Z.execScript&&Z.execScript("var "+Y[0]);
|
||||
for(var $;Y.length&&($=Y.shift());)!Y.length&&Da!==g?Z[$]=Da:Z=Z[$]?Z[$]:Z[$]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
|
||||
|
||||
atom.isElementEnabled = function(element, window){return function(){var e=this;function f(a,c){function b(){}b.prototype=c.prototype;a.d=c.prototype;a.prototype=new b};function g(a,c){for(var b=1;b<arguments.length;b++)var d=(""+arguments[b]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a};var h,i="",j=/rv\:([^\);]+)(\)|;)/.exec(e.navigator?e.navigator.userAgent:null);h=i=j?j[1]:"";var k={};function l(a,c){this.code=a;this.message=c||"";this.name=m[a]||m[13];var b=Error(this.message);b.name=this.name;this.stack=b.stack||""}f(l,Error);
|
||||
//isElementEnabled
|
||||
var isElementEnabled = function(){return function(){var e=this;function f(a,c){function b(){}b.prototype=c.prototype;a.d=c.prototype;a.prototype=new b};function g(a,c){for(var b=1;b<arguments.length;b++)var d=(""+arguments[b]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a};var h,i="",j=/rv\:([^\);]+)(\)|;)/.exec(e.navigator?e.navigator.userAgent:null);h=i=j?j[1]:"";var k={};function l(a,c){this.code=a;this.message=c||"";this.name=m[a]||m[13];var b=Error(this.message);b.name=this.name;this.stack=b.stack||""}f(l,Error);
|
||||
var m={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
|
||||
l.prototype.toString=function(){return"["+this.name+"] "+this.message};function n(a){this.stack=Error().stack||"";a&&(this.message=""+a)}f(n,Error);n.prototype.name="CustomError";function o(a,c){c.unshift(a);n.call(this,g.apply(null,c));c.shift()}f(o,n);o.prototype.name="AssertionError";function p(a,c){var b;a:if("string"==typeof a)b="string"!=typeof c||1!=c.length?-1:a.indexOf(c,0);else{for(b=0;b<a.length;b++)if(b in a&&a[b]===c)break a;b=-1}return 0<=b};if(!k["1.9.1"]){for(var q=0,r=(""+h).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),s="1.9.1".replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),t=Math.max(r.length,s.length),u=0;0==q&&u<t;u++){var v=r[u]||"",w=s[u]||"",x=RegExp("(\\d*)(\\D*)","g"),z=RegExp("(\\d*)(\\D*)","g");do{var A=x.exec(v)||["","",""],B=z.exec(w)||["","",""];if(0==A[0].length&&0==B[0].length)break;q=((0==A[1].length?0:parseInt(A[1],10))<(0==B[1].length?0:parseInt(B[1],10))?-1:(0==A[1].length?0:parseInt(A[1],10))>(0==B[1].length?
|
||||
0:parseInt(B[1],10))?1:0)||((0==A[2].length)<(0==B[2].length)?-1:(0==A[2].length)>(0==B[2].length)?1:0)||(A[2]<B[2]?-1:A[2]>B[2]?1:0)}while(0==q)}k["1.9.1"]=0<=q};(function(){var a=e.Components;if(!a)return!1;try{if(!a.classes)return!1}catch(c){return!1}var b=a.classes,a=a.interfaces;b["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator);b["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo);return!0})();function C(a,c,b,d,y){this.b=!!c;if(a&&(this.a=a))this.c="number"==typeof d?d:1!=this.a.nodeType?0:this.b?-1:1;this.depth=void 0!=y?y:this.c||0;this.b&&(this.depth*=-1)}f(C,function(){});C.prototype.a=null;C.prototype.c=0;f(function(a,c,b,d){C.call(this,a,c,0,null,d)},C);var D={"class":"className",readonly:"readOnly"},E=["checked","disabled","draggable","hidden"],F="BUTTON,INPUT,OPTGROUP,OPTION,SELECT,TEXTAREA".split(",");function G(a){var c=a.tagName.toUpperCase();if(p(F,c)){var b;b=D.disabled||"disabled";var d=a[b];b=void 0===d&&p(E,b)?!1:d;a=b?!1:a.parentNode&&1==a.parentNode.nodeType&&"OPTGROUP"==c||"OPTION"==c?G(a.parentNode):!0}else a=!0;return a};var H=G,I=["_"],J=e;!(I[0]in J)&&J.execScript&&J.execScript("var "+I[0]);for(var K;I.length&&(K=I.shift());)!I.length&&void 0!==H?J[K]=H:J=J[K]?J[K]:J[K]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
|
||||
|
||||
atom.isElementSelected = function(element, window){return function(){var f=!1,g=this;function h(a,b){function c(){}c.prototype=b.prototype;a.d=b.prototype;a.prototype=new c};function i(a,b){for(var c=1;c<arguments.length;c++)var d=(""+arguments[c]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a};var k,l="",m=/rv\:([^\);]+)(\)|;)/.exec(g.navigator?g.navigator.userAgent:null);k=l=m?m[1]:"";var n={};function o(a,b){this.code=a;this.message=b||"";this.name=p[a]||p[13];var c=Error(this.message);c.name=this.name;this.stack=c.stack||""}h(o,Error);
|
||||
//isElementSelected
|
||||
var isElementSelected = function(){return function(){var f=!1,g=this;function h(a,b){function c(){}c.prototype=b.prototype;a.d=b.prototype;a.prototype=new c};function i(a,b){for(var c=1;c<arguments.length;c++)var d=(""+arguments[c]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a};var k,l="",m=/rv\:([^\);]+)(\)|;)/.exec(g.navigator?g.navigator.userAgent:null);k=l=m?m[1]:"";var n={};function o(a,b){this.code=a;this.message=b||"";this.name=p[a]||p[13];var c=Error(this.message);c.name=this.name;this.stack=c.stack||""}h(o,Error);
|
||||
var p={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
|
||||
o.prototype.toString=function(){return"["+this.name+"] "+this.message};function q(a){this.stack=Error().stack||"";a&&(this.message=""+a)}h(q,Error);q.prototype.name="CustomError";function r(a,b){b.unshift(a);q.call(this,i.apply(null,b));b.shift()}h(r,q);r.prototype.name="AssertionError";if(!n["1.9.1"]){for(var s=0,t=(""+k).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),u="1.9.1".replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),v=Math.max(t.length,u.length),w=0;0==s&&w<v;w++){var x=t[w]||"",y=u[w]||"",z=RegExp("(\\d*)(\\D*)","g"),A=RegExp("(\\d*)(\\D*)","g");do{var B=z.exec(x)||["","",""],C=A.exec(y)||["","",""];if(0==B[0].length&&0==C[0].length)break;s=((0==B[1].length?0:parseInt(B[1],10))<(0==C[1].length?0:parseInt(C[1],10))?-1:(0==B[1].length?0:parseInt(B[1],10))>(0==C[1].length?
|
||||
0:parseInt(C[1],10))?1:0)||((0==B[2].length)<(0==C[2].length)?-1:(0==B[2].length)>(0==C[2].length)?1:0)||(B[2]<C[2]?-1:B[2]>C[2]?1:0)}while(0==s)}n["1.9.1"]=0<=s};var D={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},E={IMG:" ",BR:"\n"};function F(a,b,c){if(!(a.nodeName in D))if(3==a.nodeType)c?b.push((""+a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):b.push(a.nodeValue);else if(a.nodeName in E)b.push(E[a.nodeName]);else for(a=a.firstChild;a;)F(a,b,c),a=a.nextSibling};(function(){var a=g.Components;if(!a)return f;try{if(!a.classes)return f}catch(b){return f}var c=a.classes,a=a.interfaces;c["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator);c["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo);return!0})();function G(a,b,c,d,e){this.b=!!b;if(a&&(this.a=a))this.c="number"==typeof d?d:1!=this.a.nodeType?0:this.b?-1:1;this.depth=void 0!=e?e:this.c||0;this.b&&(this.depth*=-1)}h(G,function(){});G.prototype.a=null;G.prototype.c=0;h(function(a,b,c,d){G.call(this,a,b,0,null,d)},G);function H(a,b){return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)}function I(a){return H(a,"OPTION")?!0:H(a,"INPUT")?(a=a.type.toLowerCase(),"checkbox"==a||"radio"==a):f}var J={"class":"className",readonly:"readOnly"},K=["checked","disabled","draggable","hidden"];function L(a){if(I(a)){if(!I(a))throw new o(15,"Element is not selectable");var b="selected",c=a.type&&a.type.toLowerCase();if("checkbox"==c||"radio"==c)b="checked";var c=b,d=J[c]||c,b=a[d],e;if(e=void 0===b){b:if("string"==typeof K)d="string"!=typeof d||1!=d.length?-1:K.indexOf(d,0);else{for(e=0;e<K.length;e++)if(e in K&&K[e]===d){d=e;break b}d=-1}e=0<=d}if(e)a=f;else{if(d="value"==c)if(d=H(a,"OPTION")){var j;c=c.toLowerCase();if(a.hasAttribute)j=a.hasAttribute(c);else try{j=a.attributes[c].specified}catch(P){j=
|
||||
f}d=!j}d&&(j=[],F(a,j,f),b=j.join(""));a=b}a=!!a}else a=f;return a}var M=["_"],N=g;!(M[0]in N)&&N.execScript&&N.execScript("var "+M[0]);for(var O;M.length&&(O=M.shift());)!M.length&&void 0!==L?N[O]=L:N=N[O]?N[O]:N[O]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}
|
||||
|
||||
atom.isElementDisplayed = function(element, window){return function(){function h(a){return function(){return a}}var k=this;
|
||||
// isElementDisplayed
|
||||
var isElementDisplayed = function(){return function(){function h(a){return function(){return a}}var k=this;
|
||||
function m(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
|
||||
else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function n(a){return"string"==typeof a};function q(a){var b=0,c=String(r).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split(".");a=String(a).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split(".");for(var d=Math.max(c.length,a.length),e=0;0==b&&e<d;e++){var f=c[e]||"",g=a[e]||"",w=RegExp("(\\d*)(\\D*)","g"),p=RegExp("(\\d*)(\\D*)","g");do{var l=w.exec(f)||["","",""],v=p.exec(g)||["","",""];if(0==l[0].length&&0==v[0].length)break;b=((0==l[1].length?0:parseInt(l[1],10))<(0==v[1].length?0:parseInt(v[1],10))?-1:(0==l[1].length?0:parseInt(l[1],10))>(0==v[1].length?
|
||||
0:parseInt(v[1],10))?1:0)||((0==l[2].length)<(0==v[2].length)?-1:(0==l[2].length)>(0==v[2].length)?1:0)||(l[2]<v[2]?-1:l[2]>v[2]?1:0)}while(0==b)}return b}function aa(a){return String(a).replace(/\-([a-z])/g,function(a,c){return c.toUpperCase()})};var s=Array.prototype;function t(a,b){for(var c=a.length,d=n(a)?a.split(""):a,e=0;e<c;e++)e in d&&b.call(void 0,d[e],e,a)}function ba(a,b){if(a.reduce)return a.reduce(b,"");var c="";t(a,function(d,e){c=b.call(void 0,c,d,e,a)});return c}function ca(a,b){for(var c=a.length,d=n(a)?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a))return!0;return!1}
|
7
testing/marionette/atoms/jar.mn
Normal file
7
testing/marionette/atoms/jar.mn
Normal file
@ -0,0 +1,7 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
marionette.jar:
|
||||
% content marionette %content/
|
||||
content/atoms.js (atoms.js)
|
7
testing/marionette/atoms/moz.build
Normal file
7
testing/marionette/atoms/moz.build
Normal file
@ -0,0 +1,7 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
@ -23,12 +23,9 @@ this.DevToolsUtils = devtools.require("devtools/shared/DevToolsUtils");
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this, "cookieManager", "@mozilla.org/cookiemanager;1", "nsICookieManager2");
|
||||
|
||||
Cu.import("chrome://marionette/content/action.js");
|
||||
Cu.import("chrome://marionette/content/atom.js");
|
||||
Cu.import("chrome://marionette/content/interaction.js");
|
||||
Cu.import("chrome://marionette/content/element.js");
|
||||
Cu.import("chrome://marionette/content/event.js");
|
||||
Cu.import("chrome://marionette/content/frame.js");
|
||||
Cu.import("chrome://marionette/content/actions.js");
|
||||
Cu.import("chrome://marionette/content/interactions.js");
|
||||
Cu.import("chrome://marionette/content/elements.js");
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
Cu.import("chrome://marionette/content/modal.js");
|
||||
Cu.import("chrome://marionette/content/proxy.js");
|
||||
@ -36,6 +33,14 @@ Cu.import("chrome://marionette/content/simpletest.js");
|
||||
|
||||
loader.loadSubScript("chrome://marionette/content/common.js");
|
||||
|
||||
// preserve this import order:
|
||||
var utils = {};
|
||||
loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
|
||||
loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
|
||||
loader.loadSubScript("chrome://marionette/content/atoms.js", utils);
|
||||
loader.loadSubScript("chrome://marionette/content/sendkeys.js", utils);
|
||||
loader.loadSubScript("chrome://marionette/content/frame-manager.js");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["GeckoDriver", "Context"];
|
||||
|
||||
var FRAME_SCRIPT = "chrome://marionette/content/listener.js";
|
||||
@ -133,7 +138,7 @@ this.GeckoDriver = function(appName, device, stopSignal, emulator) {
|
||||
this.oopFrameId = null;
|
||||
this.observing = null;
|
||||
this._browserIds = new WeakMap();
|
||||
this.actions = new action.Chain();
|
||||
this.actions = new actions.Chain(utils);
|
||||
|
||||
this.sessionCapabilities = {
|
||||
// mandated capabilities
|
||||
@ -161,7 +166,7 @@ this.GeckoDriver = function(appName, device, stopSignal, emulator) {
|
||||
"version": Services.appinfo.version,
|
||||
};
|
||||
|
||||
this.interactions = new Interactions(() => this.sessionCapabilities);
|
||||
this.interactions = new Interactions(utils, () => this.sessionCapabilities);
|
||||
|
||||
this.mm = globalMessageManager;
|
||||
this.listener = proxy.toListener(() => this.mm, this.sendAsync.bind(this));
|
||||
@ -349,6 +354,8 @@ GeckoDriver.prototype.startBrowser = function(win, isNewSession=false) {
|
||||
* True if this is the first time we're talking to this browser.
|
||||
*/
|
||||
GeckoDriver.prototype.whenBrowserStarted = function(win, isNewSession) {
|
||||
utils.window = win;
|
||||
|
||||
try {
|
||||
let mm = win.window.messageManager;
|
||||
if (!isNewSession) {
|
||||
@ -383,7 +390,7 @@ GeckoDriver.prototype.whenBrowserStarted = function(win, isNewSession) {
|
||||
*/
|
||||
GeckoDriver.prototype.getVisibleText = function(el, lines) {
|
||||
try {
|
||||
if (atom.isElementDisplayed(el, this.getCurrentWindow())) {
|
||||
if (utils.isElementDisplayed(el)) {
|
||||
if (el.value) {
|
||||
lines.push(el.value);
|
||||
}
|
||||
@ -779,6 +786,7 @@ GeckoDriver.prototype.createExecuteSandbox = function(win, mn, sandboxName) {
|
||||
let sb = new Cu.Sandbox(principal,
|
||||
{sandboxPrototype: win, wantXrays: false, sandboxName: ""});
|
||||
sb.global = sb;
|
||||
sb.testUtils = utils;
|
||||
sb.proto = win;
|
||||
|
||||
mn.exports.forEach(function(fn) {
|
||||
@ -1582,6 +1590,7 @@ GeckoDriver.prototype.switchToWindow = function*(cmd, resp) {
|
||||
yield browserListening;
|
||||
}
|
||||
} else {
|
||||
utils.window = found.win;
|
||||
this.curBrowser = this.browsers[found.outerId];
|
||||
|
||||
if ("tabIndex" in found) {
|
||||
@ -2008,7 +2017,7 @@ GeckoDriver.prototype.getElementAttribute = function*(cmd, resp) {
|
||||
case Context.CHROME:
|
||||
let win = this.getCurrentWindow();
|
||||
let el = this.curBrowser.elementManager.getKnownElement(id, {frame: win});
|
||||
resp.body.value = atom.getElementAttribute(el, name, this.getCurrentWindow());
|
||||
resp.body.value = utils.getElementAttribute(el, name);
|
||||
break;
|
||||
|
||||
case Context.CONTENT:
|
||||
@ -2783,11 +2792,11 @@ GeckoDriver.prototype.sendKeysToDialog = function(cmd, resp) {
|
||||
}
|
||||
|
||||
let win = this.dialog.window ? this.dialog.window : this.getCurrentWindow();
|
||||
event.sendKeysToElement(
|
||||
cmd.parameters.value,
|
||||
utils.sendKeysToElement(
|
||||
win,
|
||||
loginTextbox,
|
||||
{ignoreVisibility: true},
|
||||
win);
|
||||
cmd.parameters.value,
|
||||
true /* ignore visibility check */);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -3059,7 +3068,7 @@ var BrowserObj = function(win, driver) {
|
||||
this.pendingCommands = [];
|
||||
|
||||
// we should have one FM per BO so that we can handle modals in each Browser
|
||||
this.frameManager = new frame.Manager(driver);
|
||||
this.frameManager = new FrameManager(driver);
|
||||
this.frameRegsPending = 0;
|
||||
|
||||
// register all message listeners
|
||||
|
@ -2,11 +2,8 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
let {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("chrome://marionette/content/atom.js");
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
|
||||
/**
|
||||
@ -302,7 +299,7 @@ ElementManager.prototype = {
|
||||
* as its members.
|
||||
*/
|
||||
applyNamedArgs: function EM_applyNamedArgs(args) {
|
||||
let namedArgs = {};
|
||||
namedArgs = {};
|
||||
args.forEach(function(arg) {
|
||||
if (arg && typeof(arg['__marionetteArgs']) === 'object') {
|
||||
for (let prop in arg['__marionetteArgs']) {
|
||||
@ -595,113 +592,10 @@ ElementManager.prototype = {
|
||||
}
|
||||
return elements;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
this.elements = {};
|
||||
|
||||
elements.generateUUID = function() {
|
||||
let uuid = uuidGen.generateUUID().toString();
|
||||
return uuid.substring(1, uuid.length - 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* This function generates a pair of coordinates relative to the viewport
|
||||
* given a target element and coordinates relative to that element's
|
||||
* top-left corner.
|
||||
*
|
||||
* @param {Node} node
|
||||
* Target node.
|
||||
* @param {number=} x
|
||||
* Horizontal offset relative to target. Defaults to the centre of
|
||||
* the target's bounding box.
|
||||
* @param {number=} y
|
||||
* Vertical offset relative to target. Defaults to the centre of
|
||||
* the target's bounding box.
|
||||
*/
|
||||
// TODO(ato): Replicated from listener.js for the time being
|
||||
elements.coordinates = function(node, x = undefined, y = undefined) {
|
||||
let box = node.getBoundingClientRect();
|
||||
if (!x) {
|
||||
x = box.width / 2.0;
|
||||
}
|
||||
if (!y) {
|
||||
y = box.height / 2.0;
|
||||
}
|
||||
return {
|
||||
x: box.left + x,
|
||||
y: box.top + y,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns true if the node is in the viewport.
|
||||
*
|
||||
* @param {Element} element
|
||||
* Target element.
|
||||
* @param {number=} x
|
||||
* Horizontal offset relative to target. Defaults to the centre of
|
||||
* the target's bounding box.
|
||||
* @param {number=} y
|
||||
* Vertical offset relative to target. Defaults to the centre of
|
||||
* the target's bounding box.
|
||||
*/
|
||||
elements.inViewport = function(el, x = undefined, y = undefined) {
|
||||
let win = el.ownerDocument.defaultView;
|
||||
let c = elements.coordinates(el, x, y);
|
||||
let vp = {
|
||||
top: win.pageYOffset,
|
||||
left: win.pageXOffset,
|
||||
bottom: (win.pageYOffset + win.innerHeight),
|
||||
right: (win.pageXOffset + win.innerWidth)
|
||||
};
|
||||
|
||||
return (vp.left <= c.x + win.pageXOffset &&
|
||||
c.x + win.pageXOffset <= vp.right &&
|
||||
vp.top <= c.y + win.pageYOffset &&
|
||||
c.y + win.pageYOffset <= vp.bottom);
|
||||
};
|
||||
|
||||
/**
|
||||
* This function throws the visibility of the element error if the element is
|
||||
* not displayed or the given coordinates are not within the viewport.
|
||||
*
|
||||
* @param {Element} element
|
||||
* Element to check if visible.
|
||||
* @param {Window} window
|
||||
* Window object.
|
||||
* @param {number=} x
|
||||
* Horizontal offset relative to target. Defaults to the centre of
|
||||
* the target's bounding box.
|
||||
* @param {number=} y
|
||||
* Vertical offset relative to target. Defaults to the centre of
|
||||
* the target's bounding box.
|
||||
*/
|
||||
elements.checkVisible = function(el, win, x = undefined, y = undefined) {
|
||||
// Bug 1094246: Webdriver's isShown doesn't work with content xul
|
||||
let ns = atom.getElementAttribute(el, "namespaceURI", win);
|
||||
if (ns.indexOf("there.is.only.xul") < 0 &&
|
||||
!atom.isElementDisplayed(el, win)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (el.tagName.toLowerCase() == "body") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!elements.inViewport(el, x, y)) {
|
||||
if (el.scrollIntoView) {
|
||||
el.scrollIntoView(false);
|
||||
if (!elements.inViewport(el)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
elements.isXULElement = function(el) {
|
||||
let ns = atom.getElementAttribute(el, "namespaceURI");
|
||||
return ns.indexOf("there.is.only.xul") >= 0;
|
||||
};
|
@ -6,7 +6,7 @@
|
||||
|
||||
const {interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
const ERRORS = [
|
||||
const errors = [
|
||||
"ElementNotAccessibleError",
|
||||
"ElementNotVisibleError",
|
||||
"InvalidArgumentError",
|
||||
@ -29,21 +29,10 @@ const ERRORS = [
|
||||
"WebDriverError",
|
||||
];
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["error"].concat(ERRORS);
|
||||
this.EXPORTED_SYMBOLS = ["error"].concat(errors);
|
||||
|
||||
this.error = {};
|
||||
|
||||
error.BuiltinErrors = {
|
||||
Error: 0,
|
||||
EvalError: 1,
|
||||
InternalError: 2,
|
||||
RangeError: 3,
|
||||
ReferenceError: 4,
|
||||
SyntaxError: 5,
|
||||
TypeError: 6,
|
||||
URIError: 7,
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if obj is an instance of the Error prototype in a safe manner.
|
||||
* Prefer using this over using instanceof since the Error prototype
|
||||
@ -61,7 +50,7 @@ error.isError = function(val) {
|
||||
} else if (val instanceof Ci.nsIException) {
|
||||
return true;
|
||||
} else {
|
||||
return Object.getPrototypeOf(val) in error.BuiltinErrors;
|
||||
return Object.getPrototypeOf(val) == "Error";
|
||||
}
|
||||
};
|
||||
|
||||
@ -70,18 +59,7 @@ error.isError = function(val) {
|
||||
*/
|
||||
error.isWebDriverError = function(obj) {
|
||||
return error.isError(obj) &&
|
||||
("name" in obj && ERRORS.indexOf(obj.name) >= 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps an Error prototype in a WebDriverError. If the given error is
|
||||
* already a WebDriverError, this is effectively a no-op.
|
||||
*/
|
||||
error.wrap = function(err) {
|
||||
if (error.isWebDriverError(err)) {
|
||||
return err;
|
||||
}
|
||||
return new WebDriverError(`${err.name}: ${err.message}`, err.stack);
|
||||
("name" in obj && errors.indexOf(obj.name) >= 0);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -162,12 +140,11 @@ error.fromJson = function(json) {
|
||||
* It should not be used directly, as it does not correspond to a real
|
||||
* error in the specification.
|
||||
*/
|
||||
this.WebDriverError = function(msg, stack = undefined) {
|
||||
this.WebDriverError = function(msg) {
|
||||
Error.call(this, msg);
|
||||
this.name = "WebDriverError";
|
||||
this.message = msg;
|
||||
this.status = "webdriver error";
|
||||
this.stack = stack;
|
||||
};
|
||||
WebDriverError.prototype = Object.create(Error.prototype);
|
||||
|
||||
@ -351,7 +328,7 @@ UnsupportedOperationError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
const nameLookup = new Map();
|
||||
const statusLookup = new Map();
|
||||
for (let s of ERRORS) {
|
||||
for (let s of errors) {
|
||||
let cls = this[s];
|
||||
let inst = new cls();
|
||||
nameLookup.set(inst.name, cls);
|
||||
|
@ -1,954 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Provides functionality for creating and sending DOM events.
|
||||
|
||||
"use strict";
|
||||
|
||||
const {interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
const logger = Log.repository.getLogger("Marionette");
|
||||
|
||||
Cu.import("chrome://marionette/content/element.js");
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["event"];
|
||||
|
||||
// must be synchronised with nsIDOMWindowUtils
|
||||
const COMPOSITION_ATTR_RAWINPUT = 0x02;
|
||||
const COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03;
|
||||
const COMPOSITION_ATTR_CONVERTEDTEXT = 0x04;
|
||||
const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;
|
||||
|
||||
// TODO(ato): Document!
|
||||
let seenEvent = false;
|
||||
|
||||
function getDOMWindowUtils(win) {
|
||||
if (!win) {
|
||||
win = window;
|
||||
}
|
||||
|
||||
// this assumes we are operating in chrome space
|
||||
return win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
}
|
||||
|
||||
this.event = {};
|
||||
|
||||
event.MouseEvents = {
|
||||
click: 0,
|
||||
dblclick: 1,
|
||||
mousedown: 2,
|
||||
mouseup: 3,
|
||||
mouseover: 4,
|
||||
mouseout: 5,
|
||||
};
|
||||
|
||||
event.Modifiers = {
|
||||
shiftKey: 0,
|
||||
ctrlKey: 1,
|
||||
altKey: 2,
|
||||
metaKey: 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a mouse event to given target.
|
||||
*
|
||||
* @param {nsIDOMMouseEvent} mouseEvent
|
||||
* Event to send.
|
||||
* @param {(Element|string)} target
|
||||
* Target of event. Can either be an Element or the ID of an element.
|
||||
* @param {Window=} window
|
||||
* Window object. Defaults to the current window.
|
||||
*
|
||||
* @throws {TypeError}
|
||||
* If the event is unsupported.
|
||||
*/
|
||||
event.sendMouseEvent = function(mouseEvent, target, window = undefined) {
|
||||
if (event.MouseEvents.hasOwnProperty(mouseEvent.type)) {
|
||||
throw new TypeError("Unsupported event type: " + mouseEvent.type);
|
||||
}
|
||||
|
||||
if (!(target instanceof Element)) {
|
||||
target = window.document.getElementById(target);
|
||||
}
|
||||
|
||||
let ev = window.document.createEvent("MouseEvent");
|
||||
|
||||
let type = mouseEvent.type;
|
||||
let view = window;
|
||||
|
||||
let detail = mouseEvent.detail;
|
||||
if (!detail) {
|
||||
if (mouseEvent.type in ["click", "mousedown", "mouseup"]) {
|
||||
detail = 1;
|
||||
} else if (mouseEvent.type == "dblclick") {
|
||||
detail = 2;
|
||||
} else {
|
||||
detail = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let screenX = mouseEvent.screenX || 0;
|
||||
let screenY = mouseEvent.screenY || 0;
|
||||
let clientX = mouseEvent.clientX || 0;
|
||||
let clientY = mouseEvent.clientY || 0;
|
||||
let ctrlKey = mouseEvent.ctrlKey || false;
|
||||
let altKey = mouseEvent.altKey || false;
|
||||
let shiftKey = mouseEvent.shiftKey || false;
|
||||
let metaKey = mouseEvent.metaKey || false;
|
||||
let button = mouseEvent.button || 0;
|
||||
let relatedTarget = mouseEvent.relatedTarget || null;
|
||||
|
||||
ev.initMouseEvent(
|
||||
mouseEvent.type,
|
||||
/* canBubble */ true,
|
||||
/* cancelable */ true,
|
||||
view,
|
||||
detail,
|
||||
screenX,
|
||||
screenY,
|
||||
clientX,
|
||||
clientY,
|
||||
ctrlKey,
|
||||
altKey,
|
||||
shiftKey,
|
||||
metaKey,
|
||||
button,
|
||||
relatedTarget);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send character to the currently focused element.
|
||||
*
|
||||
* This function handles casing of characters (sends the right charcode,
|
||||
* and sends a shift key for uppercase chars). No other modifiers are
|
||||
* handled at this point.
|
||||
*
|
||||
* For now this method only works for English letters (lower and upper
|
||||
* case) and the digits 0-9.
|
||||
*/
|
||||
event.sendChar = function(char, window = undefined) {
|
||||
// DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9
|
||||
let hasShift = (char == char.toUpperCase());
|
||||
event.synthesizeKey(char, {shiftKey: hasShift}, window);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send string to the focused element.
|
||||
*
|
||||
* For now this method only works for English letters (lower and upper
|
||||
* case) and the digits 0-9.
|
||||
*/
|
||||
event.sendString = function(string, window = undefined) {
|
||||
for (let i = 0; i < string.length; ++i) {
|
||||
event.sendChar(string.charAt(i), window);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send the non-character key to the focused element.
|
||||
*
|
||||
* The name of the key should be the part that comes after "DOM_VK_"
|
||||
* in the nsIDOMKeyEvent constant name for this key. No modifiers are
|
||||
* handled at this point.
|
||||
*/
|
||||
event.sendKey = function(key, window = undefined) {
|
||||
let keyName = "VK_" + key.toUpperCase();
|
||||
event.synthesizeKey(keyName, {shiftKey: false}, window);
|
||||
};
|
||||
|
||||
// TODO(ato): Unexpose this when action.Chain#emitMouseEvent
|
||||
// no longer emits its own events
|
||||
event.parseModifiers_ = function(event) {
|
||||
let mval = 0;
|
||||
if (event.shiftKey) {
|
||||
mval |= Ci.nsIDOMNSEvent.SHIFT_MASK;
|
||||
}
|
||||
if (event.ctrlKey) {
|
||||
mval |= Ci.nsIDOMNSEvent.CONTROL_MASK;
|
||||
}
|
||||
if (event.altKey) {
|
||||
mval |= Ci.nsIDOMNSEvent.ALT_MASK;
|
||||
}
|
||||
if (event.metaKey) {
|
||||
mval |= Ci.nsIDOMNSEvent.META_MASK;
|
||||
}
|
||||
if (event.accelKey) {
|
||||
if (navigator.platform.indexOf("Mac") >= 0) {
|
||||
mval |= Ci.nsIDOMNSEvent.META_MASK;
|
||||
} else {
|
||||
mval |= Ci.nsIDOMNSEvent.CONTROL_MASK;
|
||||
}
|
||||
}
|
||||
return mval;
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesise a mouse event on a target.
|
||||
*
|
||||
* The actual client point is determined by taking the aTarget's client
|
||||
* box and offseting it by offsetX and offsetY. This allows mouse clicks
|
||||
* to be simulated by calling this method.
|
||||
*
|
||||
* If the type is specified, an mouse event of that type is
|
||||
* fired. Otherwise, a mousedown followed by a mouse up is performed.
|
||||
*
|
||||
* @param {Element} element
|
||||
* Element to click.
|
||||
* @param {number} offsetX
|
||||
* Horizontal offset to click from the target's bounding box.
|
||||
* @param {number} offsetY
|
||||
* Vertical offset to click from the target's bounding box.
|
||||
* @param {Object.<string, ?>} opts
|
||||
* Object which may contain the properties "shiftKey", "ctrlKey",
|
||||
* "altKey", "metaKey", "accessKey", "clickCount", "button", and
|
||||
* "type".
|
||||
* @param {Window=} window
|
||||
* Window object. Defaults to the current window.
|
||||
*/
|
||||
event.synthesizeMouse = function(
|
||||
element, offsetX, offsetY, opts, window = undefined) {
|
||||
let rect = element.getBoundingClientRect();
|
||||
event.synthesizeMouseAtPoint(
|
||||
rect.left + offsetX, rect.top + offsetY, opts, window);
|
||||
};
|
||||
|
||||
/*
|
||||
* Synthesize a mouse event at a particular point in a window.
|
||||
*
|
||||
* If the type of the event is specified, a mouse event of that type is
|
||||
* fired. Otherwise, a mousedown followed by a mouse up is performed.
|
||||
*
|
||||
* @param {number} left
|
||||
* CSS pixels from the left document margin.
|
||||
* @param {number} top
|
||||
* CSS pixels from the top document margin.
|
||||
* @param {Object.<string, ?>} event
|
||||
* Object which may contain the properties "shiftKey", "ctrlKey",
|
||||
* "altKey", "metaKey", "accessKey", "clickCount", "button", and
|
||||
* "type".
|
||||
* @param {Window=} window
|
||||
* Window object. Defaults to the current window.
|
||||
*/
|
||||
event.synthesizeMouseAtPoint = function(
|
||||
left, top, opts, window = undefined) {
|
||||
|
||||
let domutils = getDOMWindowUtils(window);
|
||||
|
||||
let button = event.button || 0;
|
||||
let clickCount = event.clickCount || 1;
|
||||
let modifiers = event.parseModifiers_(event);
|
||||
|
||||
if (("type" in event) && event.type) {
|
||||
domutils.sendMouseEvent(
|
||||
event.type, left, top, button, clickCount, modifiers);
|
||||
} else {
|
||||
domutils.sendMouseEvent(
|
||||
"mousedown", left, top, button, clickCount, modifiers);
|
||||
domutils.sendMouseEvent(
|
||||
"mouseup", left, top, button, clickCount, modifiers);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Call event.synthesizeMouse with coordinates at the centre of the
|
||||
* target.
|
||||
*/
|
||||
event.synthesizeMouseAtCenter = function(element, event, window) {
|
||||
let rect = element.getBoundingClientRect();
|
||||
event.synthesizeMouse(
|
||||
element,
|
||||
rect.width / 2,
|
||||
rect.height / 2,
|
||||
event,
|
||||
window);
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesise a mouse scroll event on a target.
|
||||
*
|
||||
* The actual client point is determined by taking the target's client
|
||||
* box and offseting it by |offsetX| and |offsetY|.
|
||||
*
|
||||
* If the |type| property is specified for the |event| argument, a mouse
|
||||
* scroll event of that type is fired. Otherwise, DOMMouseScroll is used.
|
||||
*
|
||||
* If the |axis| is specified, it must be one of "horizontal" or
|
||||
* "vertical". If not specified, "vertical" is used.
|
||||
*
|
||||
* |delta| is the amount to scroll by (can be positive or negative).
|
||||
* It must be specified.
|
||||
*
|
||||
* |hasPixels| specifies whether kHasPixels should be set in the
|
||||
* |scrollFlags|.
|
||||
*
|
||||
* |isMomentum| specifies whether kIsMomentum should be set in the
|
||||
* |scrollFlags|.
|
||||
*
|
||||
* @param {Element} target
|
||||
* @param {number} offsetY
|
||||
* @param {number} offsetY
|
||||
* @param {Object.<string, ?>} event
|
||||
* Object which may contain the properties shiftKey, ctrlKey, altKey,
|
||||
* metaKey, accessKey, button, type, axis, delta, and hasPixels.
|
||||
* @param {Window=} window
|
||||
* Window object. Defaults to the current window.
|
||||
*/
|
||||
event.synthesizeMouseScroll = function(
|
||||
target, offsetX, offsetY, ev, window = undefined) {
|
||||
|
||||
let domutils = getDOMWindowUtils(window);
|
||||
|
||||
// see nsMouseScrollFlags in nsGUIEvent.h
|
||||
const kIsVertical = 0x02;
|
||||
const kIsHorizontal = 0x04;
|
||||
const kHasPixels = 0x08;
|
||||
const kIsMomentum = 0x40;
|
||||
|
||||
let button = ev.button || 0;
|
||||
let modifiers = event.parseModifiers_(ev);
|
||||
|
||||
let rect = target.getBoundingClientRect();
|
||||
let left = rect.left;
|
||||
let top = rect.top;
|
||||
|
||||
let type = (("type" in ev) && ev.type) || "DOMMouseScroll";
|
||||
let axis = ev.axis || "vertical";
|
||||
let scrollFlags = (axis == "horizontal") ? kIsHorizontal : kIsVertical;
|
||||
if (ev.hasPixels) {
|
||||
scrollFlags |= kHasPixels;
|
||||
}
|
||||
if (ev.isMomentum) {
|
||||
scrollFlags |= kIsMomentum;
|
||||
}
|
||||
|
||||
domutils.sendMouseScrollEvent(
|
||||
type,
|
||||
left + offsetX,
|
||||
top + offsetY,
|
||||
button,
|
||||
scrollFlags,
|
||||
ev.delta,
|
||||
modifiers);
|
||||
};
|
||||
|
||||
function computeKeyCodeFromChar_(char) {
|
||||
if (char.length != 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (char >= "a" && char <= "z") {
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_A + char.charCodeAt(0) - "a".charCodeAt(0);
|
||||
}
|
||||
if (char >= "A" && char <= "Z") {
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_A + char.charCodeAt(0) - "A".charCodeAt(0);
|
||||
}
|
||||
if (char >= "0" && char <= "9") {
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_0 + char.charCodeAt(0) - "0".charCodeAt(0);
|
||||
}
|
||||
|
||||
// returns US keyboard layout's keycode
|
||||
switch (char) {
|
||||
case "~":
|
||||
case "`":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_BACK_QUOTE;
|
||||
|
||||
case "!":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_1;
|
||||
|
||||
case "@":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_2;
|
||||
|
||||
case "#":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_3;
|
||||
|
||||
case "$":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_4;
|
||||
|
||||
case "%":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_5;
|
||||
|
||||
case "^":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_6;
|
||||
|
||||
case "&":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_7;
|
||||
|
||||
case "*":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_8;
|
||||
|
||||
case "(":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_9;
|
||||
|
||||
case ")":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_0;
|
||||
|
||||
case "-":
|
||||
case "_":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_SUBTRACT;
|
||||
|
||||
case "+":
|
||||
case "=":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_EQUALS;
|
||||
|
||||
case "{":
|
||||
case "[":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET;
|
||||
|
||||
case "}":
|
||||
case "]":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET;
|
||||
|
||||
case "|":
|
||||
case "\\":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_BACK_SLASH;
|
||||
|
||||
case ":":
|
||||
case ";":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_SEMICOLON;
|
||||
|
||||
case "'":
|
||||
case "\"":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_QUOTE;
|
||||
|
||||
case "<":
|
||||
case ",":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_COMMA;
|
||||
|
||||
case ">":
|
||||
case ".":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_PERIOD;
|
||||
|
||||
case "?":
|
||||
case "/":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_SLASH;
|
||||
|
||||
case "\n":
|
||||
return Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given key should cause keypress event when widget
|
||||
* handles the native key event. Otherwise, false.
|
||||
*
|
||||
* The key code should be one of consts of nsIDOMKeyEvent.DOM_VK_*,
|
||||
* or a key name begins with "VK_", or a character.
|
||||
*/
|
||||
event.isKeypressFiredKey = function(key) {
|
||||
if (typeof key == "string") {
|
||||
if (key.indexOf("VK_") === 0) {
|
||||
key = Ci.nsIDOMKeyEvent["DOM_" + key];
|
||||
if (!key) {
|
||||
throw new TypeError("Unknown key: " + key);
|
||||
}
|
||||
|
||||
// if key generates a character, it must cause a keypress event
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case Ci.nsIDOMKeyEvent.DOM_VK_SHIFT:
|
||||
case Ci.nsIDOMKeyEvent.DOM_VK_CONTROL:
|
||||
case Ci.nsIDOMKeyEvent.DOM_VK_ALT:
|
||||
case Ci.nsIDOMKeyEvent.DOM_VK_CAPS_LOCK:
|
||||
case Ci.nsIDOMKeyEvent.DOM_VK_NUM_LOCK:
|
||||
case Ci.nsIDOMKeyEvent.DOM_VK_SCROLL_LOCK:
|
||||
case Ci.nsIDOMKeyEvent.DOM_VK_META:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesise a key event.
|
||||
*
|
||||
* It is targeted at whatever would be targeted by an actual keypress
|
||||
* by the user, typically the focused element.
|
||||
*
|
||||
* @param {string} key
|
||||
* Key to synthesise. Should either be a character or a key code
|
||||
* starting with "VK_" such as VK_RETURN.
|
||||
* @param {Object.<string, ?>} event
|
||||
* Object which may contain the properties shiftKey, ctrlKey, altKey,
|
||||
* metaKey, accessKey, type. If the type is specified, a key event
|
||||
* of that type is fired. Otherwise, a keydown, a keypress, and then a
|
||||
* keyup event are fired in sequence.
|
||||
* @param {Window=} window
|
||||
* Window object. Defaults to the current window.
|
||||
*
|
||||
* @throws {TypeError}
|
||||
* If unknown key.
|
||||
*/
|
||||
event.synthesizeKey = function(key, ev, window = undefined) {
|
||||
let domutils = getDOMWindowUtils(window);
|
||||
|
||||
let keyCode = 0;
|
||||
let charCode = 0;
|
||||
if (key.indexOf("VK_") === 0) {
|
||||
keyCode = Ci.nsIDOMKeyEvent["DOM_" + key];
|
||||
if (!keyCode) {
|
||||
throw new TypeError("Unknown key: " + key);
|
||||
}
|
||||
} else {
|
||||
charCode = key.charCodeAt(0);
|
||||
keyCode = computeKeyCodeFromChar_(key.charAt(0));
|
||||
}
|
||||
|
||||
let modifiers = event.parseModifiers_(ev);
|
||||
|
||||
// send keydown + (optional) keypress + keyup events
|
||||
if (!("type" in ev) || !ev.type) {
|
||||
let keyDownDefaultHappened = domutils.sendKeyEvent(
|
||||
"keydown", keyCode, 0, modifiers);
|
||||
if (event.isKeypressFiredKey(keyCode)) {
|
||||
domutils.sendKeyEvent(
|
||||
"keypress",
|
||||
charCode ? 0 : keyCode,
|
||||
charCode,
|
||||
modifiers,
|
||||
!keyDownDefaultHappened);
|
||||
}
|
||||
domutils.sendKeyEvent("keyup", keyCode, 0, modifiers);
|
||||
|
||||
// send standalone keypress event
|
||||
} else if (ev.type == "keypress") {
|
||||
domutils.sendKeyEvent(
|
||||
ev.type,
|
||||
charCode ? 0 : keyCode,
|
||||
charCode,
|
||||
modifiers);
|
||||
|
||||
// send other standalone event than keypress
|
||||
} else {
|
||||
domutils.sendKeyEvent(ev.type, keyCode, 0, modifiers);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicate that an event with an original target and type is expected
|
||||
* to be fired, or not expected to be fired.
|
||||
*/
|
||||
function expectEvent_(expectedTarget, expectedEvent, testName) {
|
||||
if (!expectedTarget || !expectedEvent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
seenEvent = false;
|
||||
|
||||
let type;
|
||||
if (expectedEvent.charAt(0) == "!") {
|
||||
type = expectedEvent.substring(1);
|
||||
} else {
|
||||
type = expectedEvent;
|
||||
}
|
||||
|
||||
let handler = ev => {
|
||||
let pass = (!seenEvent && ev.originalTarget == expectedTarget && ev.type == type);
|
||||
is(pass, true, `${testName} ${type} event target ${seenEvent ? "twice" : ""}`);
|
||||
seenEvent = true;
|
||||
};
|
||||
|
||||
expectedTarget.addEventListener(type, handler, false);
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the event was fired or not. The provided event handler will
|
||||
* be removed.
|
||||
*/
|
||||
function checkExpectedEvent_(
|
||||
expectedTarget, expectedEvent, eventHandler, testName) {
|
||||
|
||||
if (eventHandler) {
|
||||
let expectEvent = (expectedEvent.charAt(0) != "!");
|
||||
let type = expectEvent;
|
||||
if (!type) {
|
||||
type = expectedEvent.substring(1);
|
||||
}
|
||||
expectedTarget.removeEventListener(type, eventHandler, false);
|
||||
|
||||
let desc = `${type} event`;
|
||||
if (!expectEvent) {
|
||||
desc += " not";
|
||||
}
|
||||
is(seenEvent, expectEvent, `${testName} ${desc} fired`);
|
||||
}
|
||||
|
||||
seenEvent = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to event.synthesizeMouse except that a test is performed to
|
||||
* see if an event is fired at the right target as a result.
|
||||
*
|
||||
* To test that an event is not fired, use an expected type preceded by
|
||||
* an exclamation mark, such as "!select". This might be used to test that
|
||||
* a click on a disabled element doesn't fire certain events for instance.
|
||||
*
|
||||
* @param {Element} target
|
||||
* Synthesise the mouse event on this target.
|
||||
* @param {number} offsetX
|
||||
* Horizontal offset from the target's bounding box.
|
||||
* @param {number} offsetY
|
||||
* Vertical offset from the target's bounding box.
|
||||
* @param {Object.<string, ?>} ev
|
||||
* Object which may contain the properties shiftKey, ctrlKey, altKey,
|
||||
* metaKey, accessKey, type.
|
||||
* @param {Element} expectedTarget
|
||||
* Expected originalTarget of the event.
|
||||
* @param {DOMEvent} expectedEvent
|
||||
* Expected type of the event, such as "select".
|
||||
* @param {string} testName
|
||||
* Test name when outputing results.
|
||||
* @param {Window=} window
|
||||
* Window object. Defaults to the current window.
|
||||
*/
|
||||
event.synthesizeMouseExpectEvent = function(
|
||||
target, offsetX, offsetY, ev, expectedTarget, expectedEvent,
|
||||
testName, window = undefined) {
|
||||
|
||||
let eventHandler = expectEvent_(
|
||||
expectedTarget,
|
||||
expectedEvent,
|
||||
testName);
|
||||
event.synthesizeMouse(target, offsetX, offsetY, ev, window);
|
||||
checkExpectedEvent_(
|
||||
expectedTarget,
|
||||
expectedEvent,
|
||||
eventHandler,
|
||||
testName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Similar to synthesizeKey except that a test is performed to see if
|
||||
* an event is fired at the right target as a result.
|
||||
*
|
||||
* @param {string} key
|
||||
* Key to synthesise.
|
||||
* @param {Object.<string, ?>} ev
|
||||
* Object which may contain the properties shiftKey, ctrlKey, altKey,
|
||||
* metaKey, accessKey, type.
|
||||
* @param {Element} expectedTarget
|
||||
* Expected originalTarget of the event.
|
||||
* @param {DOMEvent} expectedEvent
|
||||
* Expected type of the event, such as "select".
|
||||
* @param {string} testName
|
||||
* Test name when outputing results
|
||||
* @param {Window=} window
|
||||
* Window object. Defaults to the current window.
|
||||
*
|
||||
* To test that an event is not fired, use an expected type preceded by an
|
||||
* exclamation mark, such as "!select".
|
||||
*
|
||||
* aWindow is optional, and defaults to the current window object.
|
||||
*/
|
||||
event.synthesizeKeyExpectEvent = function(
|
||||
key, ev, expectedTarget, expectedEvent, testName,
|
||||
window = undefined) {
|
||||
|
||||
let eventHandler = expectEvent_(
|
||||
expectedTarget,
|
||||
expectedEvent,
|
||||
testName);
|
||||
event.synthesizeKey(key, ev, window);
|
||||
checkExpectedEvent_(
|
||||
expectedTarget,
|
||||
expectedEvent,
|
||||
eventHandler,
|
||||
testName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesize a composition event.
|
||||
*
|
||||
* @param {DOMEvent} ev
|
||||
* The composition event information. This must have |type|
|
||||
* member. The value must be "compositionstart", "compositionend" or
|
||||
* "compositionupdate". And also this may have |data| and |locale|
|
||||
* which would be used for the value of each property of the
|
||||
* composition event. Note that the data would be ignored if the
|
||||
* event type were "compositionstart".
|
||||
* @param {Window=} window
|
||||
* Window object. Defaults to the current window.
|
||||
*/
|
||||
event.synthesizeComposition = function(ev, window = undefined) {
|
||||
let domutils = getDOMWindowUtils(window);
|
||||
domutils.sendCompositionEvent(ev.type, ev.data || "", ev.locale || "");
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesize a text event.
|
||||
*
|
||||
* The text event's information, this has |composition| and |caret|
|
||||
* members. |composition| has |string| and |clauses| members. |clauses|
|
||||
* must be array object. Each object has |length| and |attr|.
|
||||
* And |caret| has |start| and |length|. See the following tree image.
|
||||
*
|
||||
* ev
|
||||
* +-- composition
|
||||
* | +-- string
|
||||
* | +-- clauses[]
|
||||
* | +-- length
|
||||
* | +-- attr
|
||||
* +-- caret
|
||||
* +-- start
|
||||
* +-- length
|
||||
*
|
||||
* Set the composition string to |composition.string|. Set its clauses
|
||||
* information to the |clauses| array.
|
||||
*
|
||||
* When it's composing, set the each clauses' length
|
||||
* to the |composition.clauses[n].length|. The sum
|
||||
* of the all length values must be same as the length of
|
||||
* |composition.string|. Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the
|
||||
* |composition.clauses[n].attr|.
|
||||
*
|
||||
* When it's not composing, set 0 to the |composition.clauses[0].length|
|
||||
* and |composition.clauses[0].attr|.
|
||||
*
|
||||
* Set caret position to the |caret.start|. Its offset from the start of
|
||||
* the composition string. Set caret length to |caret.length|. If it's
|
||||
* larger than 0, it should be wide caret. However, current nsEditor
|
||||
* doesn't support wide caret, therefore, you should always set 0 now.
|
||||
*
|
||||
* @param {Object.<string, ?>} ev
|
||||
* The text event's information,
|
||||
* @param {Window=} window
|
||||
* Window object. Defaults to the current window.
|
||||
*/
|
||||
event.synthesizeText = function(ev, window = undefined) {
|
||||
let domutils = getDOMWindowUtils(window);
|
||||
|
||||
if (!ev.composition ||
|
||||
!ev.composition.clauses ||
|
||||
!ev.composition.clauses[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let firstClauseLength = ev.composition.clauses[0].length;
|
||||
let firstClauseAttr = ev.composition.clauses[0].attr;
|
||||
let secondClauseLength = 0;
|
||||
let secondClauseAttr = 0;
|
||||
let thirdClauseLength = 0;
|
||||
let thirdClauseAttr = 0;
|
||||
if (ev.composition.clauses[1]) {
|
||||
secondClauseLength = ev.composition.clauses[1].length;
|
||||
secondClauseAttr = ev.composition.clauses[1].attr;
|
||||
if (event.composition.clauses[2]) {
|
||||
thirdClauseLength = ev.composition.clauses[2].length;
|
||||
thirdClauseAttr = ev.composition.clauses[2].attr;
|
||||
}
|
||||
}
|
||||
|
||||
let caretStart = -1;
|
||||
let caretLength = 0;
|
||||
if (event.caret) {
|
||||
caretStart = ev.caret.start;
|
||||
caretLength = ev.caret.length;
|
||||
}
|
||||
|
||||
domutils.sendTextEvent(
|
||||
ev.composition.string,
|
||||
firstClauseLength,
|
||||
firstClauseAttr,
|
||||
secondClauseLength,
|
||||
secondClauseAttr,
|
||||
thirdClauseLength,
|
||||
thirdClauseAttr,
|
||||
caretStart,
|
||||
caretLength);
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesize a query selected text event.
|
||||
*
|
||||
* @param {Window=}
|
||||
* Window object. Defaults to the current window.
|
||||
*
|
||||
* @return {(nsIQueryContentEventResult|null)}
|
||||
* Event's result, or null if it failed.
|
||||
*/
|
||||
event.synthesizeQuerySelectedText = function(window = undefined) {
|
||||
let domutils = getDOMWindowUtils(window);
|
||||
return domutils.sendQueryContentEvent(
|
||||
domutils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Synthesize a selection set event.
|
||||
*
|
||||
* @param {number} offset
|
||||
* Character offset. 0 means the first character in the selection
|
||||
* root.
|
||||
* @param {number} length
|
||||
* Length of the text. If the length is too long, the extra length
|
||||
* is ignored.
|
||||
* @param {boolean} reverse
|
||||
* If true, the selection is from |aOffset + aLength| to |aOffset|.
|
||||
* Otherwise, from |aOffset| to |aOffset + aLength|.
|
||||
* @param {Window=} window
|
||||
* Window object. Defaults to the current window.
|
||||
*
|
||||
* @return True, if succeeded. Otherwise false.
|
||||
*/
|
||||
event.synthesizeSelectionSet = function(
|
||||
offset, length, reverse, window = undefined) {
|
||||
let domutils = getDOMWindowUtils(window);
|
||||
return domutils.sendSelectionSetEvent(offset, length, reverse);
|
||||
};
|
||||
|
||||
const KEYCODES_LOOKUP = {
|
||||
"VK_SHIFT": "shiftKey",
|
||||
"VK_CONTROL": "ctrlKey",
|
||||
"VK_ALT": "altKey",
|
||||
"VK_META": "metaKey",
|
||||
};
|
||||
|
||||
const VIRTUAL_KEYCODE_LOOKUP = {
|
||||
"\uE001": "VK_CANCEL",
|
||||
"\uE002": "VK_HELP",
|
||||
"\uE003": "VK_BACK_SPACE",
|
||||
"\uE004": "VK_TAB",
|
||||
"\uE005": "VK_CLEAR",
|
||||
"\uE006": "VK_RETURN",
|
||||
"\uE007": "VK_RETURN",
|
||||
"\uE008": "VK_SHIFT",
|
||||
"\uE009": "VK_CONTROL",
|
||||
"\uE00A": "VK_ALT",
|
||||
"\uE03D": "VK_META",
|
||||
"\uE00B": "VK_PAUSE",
|
||||
"\uE00C": "VK_ESCAPE",
|
||||
"\uE00D": "VK_SPACE", // printable
|
||||
"\uE00E": "VK_PAGE_UP",
|
||||
"\uE00F": "VK_PAGE_DOWN",
|
||||
"\uE010": "VK_END",
|
||||
"\uE011": "VK_HOME",
|
||||
"\uE012": "VK_LEFT",
|
||||
"\uE013": "VK_UP",
|
||||
"\uE014": "VK_RIGHT",
|
||||
"\uE015": "VK_DOWN",
|
||||
"\uE016": "VK_INSERT",
|
||||
"\uE017": "VK_DELETE",
|
||||
"\uE018": "VK_SEMICOLON",
|
||||
"\uE019": "VK_EQUALS",
|
||||
"\uE01A": "VK_NUMPAD0",
|
||||
"\uE01B": "VK_NUMPAD1",
|
||||
"\uE01C": "VK_NUMPAD2",
|
||||
"\uE01D": "VK_NUMPAD3",
|
||||
"\uE01E": "VK_NUMPAD4",
|
||||
"\uE01F": "VK_NUMPAD5",
|
||||
"\uE020": "VK_NUMPAD6",
|
||||
"\uE021": "VK_NUMPAD7",
|
||||
"\uE022": "VK_NUMPAD8",
|
||||
"\uE023": "VK_NUMPAD9",
|
||||
"\uE024": "VK_MULTIPLY",
|
||||
"\uE025": "VK_ADD",
|
||||
"\uE026": "VK_SEPARATOR",
|
||||
"\uE027": "VK_SUBTRACT",
|
||||
"\uE028": "VK_DECIMAL",
|
||||
"\uE029": "VK_DIVIDE",
|
||||
"\uE031": "VK_F1",
|
||||
"\uE032": "VK_F2",
|
||||
"\uE033": "VK_F3",
|
||||
"\uE034": "VK_F4",
|
||||
"\uE035": "VK_F5",
|
||||
"\uE036": "VK_F6",
|
||||
"\uE037": "VK_F7",
|
||||
"\uE038": "VK_F8",
|
||||
"\uE039": "VK_F9",
|
||||
"\uE03A": "VK_F10",
|
||||
"\uE03B": "VK_F11",
|
||||
"\uE03C": "VK_F12",
|
||||
};
|
||||
|
||||
function getKeyCode(c) {
|
||||
if (c in VIRTUAL_KEYCODE_LOOKUP) {
|
||||
return VIRTUAL_KEYCODE_LOOKUP[c];
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
event.sendKeyDown = function(keyToSend, modifiers, document) {
|
||||
modifiers.type = "keydown";
|
||||
event.sendSingleKey(keyToSend, modifiers, document);
|
||||
if (["VK_SHIFT", "VK_CONTROL", "VK_ALT", "VK_META"].indexOf(getKeyCode(keyToSend)) < 0) {
|
||||
modifiers.type = "keypress";
|
||||
event.sendSingleKey(keyToSend, modifiers, document);
|
||||
}
|
||||
delete modifiers.type;
|
||||
};
|
||||
|
||||
event.sendKeyUp = function(keyToSend, modifiers, window = undefined) {
|
||||
modifiers.type = "keyup";
|
||||
event.sendSingleKey(keyToSend, modifiers, window);
|
||||
delete modifiers.type;
|
||||
};
|
||||
|
||||
event.sendSingleKey = function(keyToSend, modifiers, window = undefined) {
|
||||
let keyCode = getKeyCode(keyToSend);
|
||||
if (keyCode in KEYCODES_LOOKUP) {
|
||||
let modName = KEYCODES_LOOKUP[keyCode];
|
||||
modifiers[modName] = !modifiers[modName];
|
||||
} else if (modifiers.shiftKey) {
|
||||
keyCode = keyCode.toUpperCase();
|
||||
}
|
||||
event.synthesizeKey(keyCode, modifiers, window);
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus element and, if a textual input field and no previous selection
|
||||
* state exists, move the caret to the end of the input field.
|
||||
*
|
||||
* @param {Element} element
|
||||
* Element to focus.
|
||||
*/
|
||||
function focusElement(element) {
|
||||
let t = element.type;
|
||||
if (t && (t == "text" || t == "textarea")) {
|
||||
if (element.selectionEnd == 0) {
|
||||
let len = element.value.length;
|
||||
element.setSelectionRange(len, len);
|
||||
}
|
||||
}
|
||||
element.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array.<string>} keySequence
|
||||
* @param {Element} element
|
||||
* @param {Object.<string, boolean>=} opts
|
||||
* @param {Window=} window
|
||||
*/
|
||||
event.sendKeysToElement = function(
|
||||
keySequence, element, opts = {}, window = undefined) {
|
||||
|
||||
if (opts.ignoreVisibility || elements.checkVisible(element, window)) {
|
||||
focusElement(element);
|
||||
|
||||
// make Object.<modifier, false> map
|
||||
let modifiers = Object.create(event.Modifiers);
|
||||
for (let modifier in event.Modifiers) {
|
||||
modifiers[modifier] = false;
|
||||
}
|
||||
|
||||
let value = keySequence.join("");
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
let c = value.charAt(i);
|
||||
event.sendSingleKey(c, modifiers, window);
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new ElementNotVisibleError("Element is not visible");
|
||||
}
|
||||
};
|
230
testing/marionette/frame-manager.js
Normal file
230
testing/marionette/frame-manager.js
Normal file
@ -0,0 +1,230 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["FrameManager"];
|
||||
|
||||
var FRAME_SCRIPT = "chrome://marionette/content/listener.js";
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader);
|
||||
|
||||
//list of OOP frames that has the frame script loaded
|
||||
var remoteFrames = [];
|
||||
|
||||
/**
|
||||
* An object representing a frame that Marionette has loaded a
|
||||
* frame script in.
|
||||
*/
|
||||
function MarionetteRemoteFrame(windowId, frameId) {
|
||||
this.windowId = windowId; //outerWindowId relative to main process
|
||||
this.frameId = frameId; //actual frame relative to windowId's frames list
|
||||
this.targetFrameId = this.frameId; //assigned FrameId, used for messaging
|
||||
};
|
||||
|
||||
/**
|
||||
* The FrameManager will maintain the list of Out Of Process (OOP) frames and will handle
|
||||
* frame switching between them.
|
||||
* It handles explicit frame switching (switchToFrame), and implicit frame switching, which
|
||||
* occurs when a modal dialog is triggered in B2G.
|
||||
*
|
||||
*/
|
||||
this.FrameManager = function FrameManager(server) {
|
||||
//messageManager maintains the messageManager for the current process' chrome frame or the global message manager
|
||||
this.currentRemoteFrame = null; //holds a member of remoteFrames (for an OOP frame) or null (for the main process)
|
||||
this.previousRemoteFrame = null; //frame we'll need to restore once interrupt is gone
|
||||
this.handledModal = false; //set to true when we have been interrupted by a modal
|
||||
this.server = server; // a reference to the marionette server
|
||||
};
|
||||
|
||||
FrameManager.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
/**
|
||||
* Receives all messages from content messageManager
|
||||
*/
|
||||
receiveMessage: function FM_receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "MarionetteFrame:getInterruptedState":
|
||||
// This will return true if the calling frame was interrupted by a modal dialog
|
||||
if (this.previousRemoteFrame) {
|
||||
let interruptedFrame = Services.wm.getOuterWindowWithId(this.previousRemoteFrame.windowId);//get the frame window of the interrupted frame
|
||||
if (this.previousRemoteFrame.frameId != null) {
|
||||
interruptedFrame = interruptedFrame.document.getElementsByTagName("iframe")[this.previousRemoteFrame.frameId]; //find the OOP frame
|
||||
}
|
||||
//check if the interrupted frame is the same as the calling frame
|
||||
if (interruptedFrame.src == message.target.src) {
|
||||
return {value: this.handledModal};
|
||||
}
|
||||
}
|
||||
else if (this.currentRemoteFrame == null) {
|
||||
// we get here if previousRemoteFrame and currentRemoteFrame are null, ie: if we're in a non-OOP process, or we haven't switched into an OOP frame, in which case, handledModal can't be set to true.
|
||||
return {value: this.handledModal};
|
||||
}
|
||||
return {value: false};
|
||||
case "MarionetteFrame:handleModal":
|
||||
/*
|
||||
* handleModal is called when we need to switch frames to the main process due to a modal dialog interrupt.
|
||||
*/
|
||||
// If previousRemoteFrame was set, that means we switched into a remote frame.
|
||||
// If this is the case, then we want to switch back into the system frame.
|
||||
// If it isn't the case, then we're in a non-OOP environment, so we don't need to handle remote frames
|
||||
let isLocal = true;
|
||||
if (this.currentRemoteFrame != null) {
|
||||
isLocal = false;
|
||||
this.removeMessageManagerListeners(this.currentRemoteFrame.messageManager.get());
|
||||
//store the previous frame so we can switch back to it when the modal is dismissed
|
||||
this.previousRemoteFrame = this.currentRemoteFrame;
|
||||
//by setting currentRemoteFrame to null, it signifies we're in the main process
|
||||
this.currentRemoteFrame = null;
|
||||
this.server.messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
|
||||
.getService(Ci.nsIMessageBroadcaster);
|
||||
}
|
||||
this.handledModal = true;
|
||||
this.server.sendOk(this.server.command_id);
|
||||
return {value: isLocal};
|
||||
case "MarionetteFrame:getCurrentFrameId":
|
||||
if (this.currentRemoteFrame != null) {
|
||||
return this.currentRemoteFrame.frameId;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getOopFrame: function FM_getOopFrame(winId, frameId) {
|
||||
// get original frame window
|
||||
let outerWin = Services.wm.getOuterWindowWithId(winId);
|
||||
// find the OOP frame
|
||||
let f = outerWin.document.getElementsByTagName("iframe")[frameId];
|
||||
return f;
|
||||
},
|
||||
|
||||
getFrameMM: function FM_getFrameMM(winId, frameId) {
|
||||
let oopFrame = this.getOopFrame(winId, frameId);
|
||||
let mm = oopFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
|
||||
.frameLoader.messageManager;
|
||||
return mm;
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch to OOP frame. We're handling this here
|
||||
* so we can maintain a list of remote frames.
|
||||
*/
|
||||
switchToFrame: function FM_switchToFrame(winId, frameId) {
|
||||
let oopFrame = this.getOopFrame(winId, frameId);
|
||||
let mm = this.getFrameMM(winId, frameId);
|
||||
|
||||
// See if this frame already has our frame script loaded in it;
|
||||
// if so, just wake it up.
|
||||
for (let i = 0; i < remoteFrames.length; i++) {
|
||||
let frame = remoteFrames[i];
|
||||
let frameMessageManager = frame.messageManager.get();
|
||||
try {
|
||||
frameMessageManager.sendAsyncMessage("aliveCheck", {});
|
||||
} catch (e) {
|
||||
if (e.result == Components.results.NS_ERROR_NOT_INITIALIZED) {
|
||||
remoteFrames.splice(i--, 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (frameMessageManager == mm) {
|
||||
this.currentRemoteFrame = frame;
|
||||
this.addMessageManagerListeners(mm);
|
||||
|
||||
mm.sendAsyncMessage("Marionette:restart");
|
||||
return oopFrame.id;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, then we need to load the frame script in this frame,
|
||||
// and set the frame's ChromeMessageSender as the active message manager
|
||||
// the server will listen to.
|
||||
this.addMessageManagerListeners(mm);
|
||||
let aFrame = new MarionetteRemoteFrame(winId, frameId);
|
||||
aFrame.messageManager = Cu.getWeakReference(mm);
|
||||
remoteFrames.push(aFrame);
|
||||
this.currentRemoteFrame = aFrame;
|
||||
|
||||
mm.loadFrameScript(FRAME_SCRIPT, true, true);
|
||||
|
||||
return oopFrame.id;
|
||||
},
|
||||
|
||||
/*
|
||||
* This function handles switching back to the frame that was interrupted by the modal dialog.
|
||||
* This function gets called by the interrupted frame once the dialog is dismissed and the frame resumes its process
|
||||
*/
|
||||
switchToModalOrigin: function FM_switchToModalOrigin() {
|
||||
//only handle this if we indeed switched out of the modal's originating frame
|
||||
if (this.previousRemoteFrame != null) {
|
||||
this.currentRemoteFrame = this.previousRemoteFrame;
|
||||
this.addMessageManagerListeners(this.currentRemoteFrame.messageManager.get());
|
||||
}
|
||||
this.handledModal = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds message listeners to the server,
|
||||
* listening for messages from content frame scripts.
|
||||
* It also adds a MarionetteFrame:getInterruptedState
|
||||
* message listener to the FrameManager,
|
||||
* so the frame manager's state can be checked by the frame.
|
||||
*
|
||||
* @param {nsIMessageListenerManager} mm
|
||||
* The message manager object, typically
|
||||
* ChromeMessageBroadcaster or ChromeMessageSender.
|
||||
*/
|
||||
addMessageManagerListeners: function FM_addMessageManagerListeners(mm) {
|
||||
mm.addWeakMessageListener("Marionette:ok", this.server);
|
||||
mm.addWeakMessageListener("Marionette:done", this.server);
|
||||
mm.addWeakMessageListener("Marionette:error", this.server);
|
||||
mm.addWeakMessageListener("Marionette:emitTouchEvent", this.server);
|
||||
mm.addWeakMessageListener("Marionette:log", this.server);
|
||||
mm.addWeakMessageListener("Marionette:runEmulatorCmd", this.server.emulator);
|
||||
mm.addWeakMessageListener("Marionette:runEmulatorShell", this.server.emulator);
|
||||
mm.addWeakMessageListener("Marionette:shareData", this.server);
|
||||
mm.addWeakMessageListener("Marionette:switchToModalOrigin", this.server);
|
||||
mm.addWeakMessageListener("Marionette:switchedToFrame", this.server);
|
||||
mm.addWeakMessageListener("Marionette:getVisibleCookies", this.server);
|
||||
mm.addWeakMessageListener("Marionette:register", this.server);
|
||||
mm.addWeakMessageListener("Marionette:listenersAttached", this.server);
|
||||
mm.addWeakMessageListener("Marionette:getFiles", this.server);
|
||||
mm.addWeakMessageListener("MarionetteFrame:handleModal", this);
|
||||
mm.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
|
||||
mm.addWeakMessageListener("MarionetteFrame:getInterruptedState", this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes listeners for messages from content frame scripts.
|
||||
* We do not remove the MarionetteFrame:getInterruptedState
|
||||
* or the Marionette:switchToModalOrigin message listener,
|
||||
* because we want to allow all known frames to contact the frame manager
|
||||
* so that it can check if it was interrupted, and if so,
|
||||
* it will call switchToModalOrigin when its process gets resumed.
|
||||
*
|
||||
* @param {nsIMessageListenerManager} mm
|
||||
* The message manager object, typically
|
||||
* ChromeMessageBroadcaster or ChromeMessageSender.
|
||||
*/
|
||||
removeMessageManagerListeners: function FM_removeMessageManagerListeners(mm) {
|
||||
mm.removeWeakMessageListener("Marionette:ok", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:done", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:error", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:log", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:shareData", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:runEmulatorCmd", this.server.emulator);
|
||||
mm.removeWeakMessageListener("Marionette:runEmulatorShell", this.server.emulator);
|
||||
mm.removeWeakMessageListener("Marionette:switchedToFrame", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:getVisibleCookies", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:listenersAttached", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:register", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:getFiles", this.server);
|
||||
mm.removeWeakMessageListener("MarionetteFrame:handleModal", this);
|
||||
mm.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
|
||||
}
|
||||
};
|
@ -1,264 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["frame"];
|
||||
|
||||
this.frame = {};
|
||||
|
||||
const FRAME_SCRIPT = "chrome://marionette/content/listener.js";
|
||||
|
||||
// list of OOP frames that has the frame script loaded
|
||||
var remoteFrames = [];
|
||||
|
||||
/**
|
||||
* An object representing a frame that Marionette has loaded a
|
||||
* frame script in.
|
||||
*/
|
||||
frame.RemoteFrame = function(windowId, frameId) {
|
||||
// outerWindowId relative to main process
|
||||
this.windowId = windowId;
|
||||
// actual frame relative to the windowId's frames list
|
||||
this.frameId = frameId;
|
||||
// assigned frame ID, used for messaging
|
||||
this.targetFrameId = this.frameId;
|
||||
// list of OOP frames that has the frame script loaded
|
||||
this.remoteFrames = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* The FrameManager will maintain the list of Out Of Process (OOP)
|
||||
* frames and will handle frame switching between them.
|
||||
*
|
||||
* It handles explicit frame switching (switchToFrame), and implicit
|
||||
* frame switching, which occurs when a modal dialog is triggered in B2G.
|
||||
*
|
||||
* @param {GeckoDriver} driver
|
||||
* Reference to the driver instance.
|
||||
*/
|
||||
frame.Manager = class {
|
||||
constructor(driver) {
|
||||
// messageManager maintains the messageManager
|
||||
// for the current process' chrome frame or the global message manager
|
||||
|
||||
// holds a member of the remoteFrames (for an OOP frame)
|
||||
// or null (for the main process)
|
||||
this.currentRemoteFrame = null;
|
||||
// frame we'll need to restore once interrupt is gone
|
||||
this.previousRemoteFrame = null;
|
||||
// set to true when we have been interrupted by a modal
|
||||
this.handledModal = false;
|
||||
this.driver = driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives all messages from content messageManager.
|
||||
*/
|
||||
receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "MarionetteFrame:getInterruptedState":
|
||||
// this will return true if the calling frame was interrupted by a modal dialog
|
||||
if (this.previousRemoteFrame) {
|
||||
// get the frame window of the interrupted frame
|
||||
let interruptedFrame = Services.wm.getOuterWindowWithId(
|
||||
this.previousRemoteFrame.windowId);
|
||||
|
||||
if (this.previousRemoteFrame.frameId !== null) {
|
||||
// find OOP frame
|
||||
let iframes = interruptedFrame.document.getElementsByTagName("iframe");
|
||||
interruptedFrame = iframes[this.previousRemoteFrame.frameId];
|
||||
}
|
||||
|
||||
// check if the interrupted frame is the same as the calling frame
|
||||
if (interruptedFrame.src == message.target.src) {
|
||||
return {value: this.handledModal};
|
||||
}
|
||||
|
||||
// we get here if previousRemoteFrame and currentRemoteFrame are null,
|
||||
// i.e. if we're in a non-OOP process, or we haven't switched into an OOP frame,
|
||||
// in which case, handledModal can't be set to true
|
||||
} else if (this.currentRemoteFrame === null) {
|
||||
return {value: this.handledModal};
|
||||
}
|
||||
return {value: false};
|
||||
|
||||
// handleModal is called when we need to switch frames to the main
|
||||
// process due to a modal dialog interrupt
|
||||
case "MarionetteFrame:handleModal":
|
||||
// If previousRemoteFrame was set, that means we switched into a
|
||||
// remote frame. If this is the case, then we want to switch back
|
||||
// into the system frame. If it isn't the case, then we're in a
|
||||
// non-OOP environment, so we don't need to handle remote frames.
|
||||
let isLocal = true;
|
||||
if (this.currentRemoteFrame !== null) {
|
||||
isLocal = false;
|
||||
this.removeMessageManagerListeners(
|
||||
this.currentRemoteFrame.messageManager.get());
|
||||
|
||||
// store the previous frame so we can switch back to it when
|
||||
// the modal is dismissed
|
||||
this.previousRemoteFrame = this.currentRemoteFrame;
|
||||
|
||||
// by setting currentRemoteFrame to null,
|
||||
// it signifies we're in the main process
|
||||
this.currentRemoteFrame = null;
|
||||
this.driver.messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
|
||||
.getService(Ci.nsIMessageBroadcaster);
|
||||
}
|
||||
|
||||
this.handledModal = true;
|
||||
this.driver.sendOk(this.driver.command_id);
|
||||
return {value: isLocal};
|
||||
|
||||
case "MarionetteFrame:getCurrentFrameId":
|
||||
if (this.currentRemoteFrame !== null) {
|
||||
return this.currentRemoteFrame.frameId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getOopFrame(winId, frameId) {
|
||||
// get original frame window
|
||||
let outerWin = Services.wm.getOuterWindowWithId(winId);
|
||||
// find the OOP frame
|
||||
let f = outerWin.document.getElementsByTagName("iframe")[frameId];
|
||||
return f;
|
||||
}
|
||||
|
||||
getFrameMM(winId, frameId) {
|
||||
let oopFrame = this.getOopFrame(winId, frameId);
|
||||
let mm = oopFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
|
||||
.frameLoader.messageManager;
|
||||
return mm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to OOP frame. We're handling this here so we can maintain
|
||||
* a list of remote frames.
|
||||
*/
|
||||
switchToFrame(winId, frameId) {
|
||||
let oopFrame = this.getOopFrame(winId, frameId);
|
||||
let mm = this.getFrameMM(winId, frameId);
|
||||
|
||||
// see if this frame already has our frame script loaded in it;
|
||||
// if so, just wake it up
|
||||
for (let i = 0; i < remoteFrames.length; i++) {
|
||||
let f = remoteFrames[i];
|
||||
let fmm = f.messageManager.get();
|
||||
try {
|
||||
fmm.sendAsyncMessage("aliveCheck", {});
|
||||
} catch (e) {
|
||||
if (e.result == Cr.NS_ERROR_NOT_INITIALIZED) {
|
||||
remoteFrames.splice(i--, 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (fmm == mm) {
|
||||
this.currentRemoteFrame = f;
|
||||
this.addMessageManagerListeners(mm);
|
||||
|
||||
mm.sendAsyncMessage("Marionette:restart");
|
||||
return oopFrame.id;
|
||||
}
|
||||
}
|
||||
|
||||
// if we get here, then we need to load the frame script in this frame,
|
||||
// and set the frame's ChromeMessageSender as the active message manager
|
||||
// the driver will listen to.
|
||||
this.addMessageManagerListeners(mm);
|
||||
let f = new frame.RemoteFrame(winId, frameId);
|
||||
f.messageManager = Cu.getWeakReference(mm);
|
||||
remoteFrames.push(f);
|
||||
this.currentRemoteFrame = f;
|
||||
|
||||
mm.loadFrameScript(FRAME_SCRIPT, true, true);
|
||||
|
||||
return oopFrame.id;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function handles switching back to the frame that was
|
||||
* interrupted by the modal dialog. It gets called by the interrupted
|
||||
* frame once the dialog is dismissed and the frame resumes its process.
|
||||
*/
|
||||
switchToModalOrigin() {
|
||||
// only handle this if we indeed switched out of the modal's
|
||||
// originating frame
|
||||
if (this.previousRemoteFrame !== null) {
|
||||
this.currentRemoteFrame = this.previousRemoteFrame;
|
||||
let mm = this.currentRemoteFrame.messageManager.get();
|
||||
this.addMessageManagerListeners(mm);
|
||||
}
|
||||
this.handledModal = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds message listeners to the driver, listening for
|
||||
* messages from content frame scripts. It also adds a
|
||||
* MarionetteFrame:getInterruptedState message listener to the
|
||||
* FrameManager, so the frame manager's state can be checked by the frame.
|
||||
*
|
||||
* @param {nsIMessageListenerManager} mm
|
||||
* The message manager object, typically
|
||||
* ChromeMessageBroadcaster or ChromeMessageSender.
|
||||
*/
|
||||
addMessageManagerListeners(mm) {
|
||||
mm.addWeakMessageListener("Marionette:ok", this.driver);
|
||||
mm.addWeakMessageListener("Marionette:done", this.driver);
|
||||
mm.addWeakMessageListener("Marionette:error", this.driver);
|
||||
mm.addWeakMessageListener("Marionette:emitTouchEvent", this.driver);
|
||||
mm.addWeakMessageListener("Marionette:log", this.driver);
|
||||
mm.addWeakMessageListener("Marionette:runEmulatorCmd", this.driver.emulator);
|
||||
mm.addWeakMessageListener("Marionette:runEmulatorShell", this.driver.emulator);
|
||||
mm.addWeakMessageListener("Marionette:shareData", this.driver);
|
||||
mm.addWeakMessageListener("Marionette:switchToModalOrigin", this.driver);
|
||||
mm.addWeakMessageListener("Marionette:switchedToFrame", this.driver);
|
||||
mm.addWeakMessageListener("Marionette:getVisibleCookies", this.driver);
|
||||
mm.addWeakMessageListener("Marionette:register", this.driver);
|
||||
mm.addWeakMessageListener("Marionette:listenersAttached", this.driver);
|
||||
mm.addWeakMessageListener("Marionette:getFiles", this.driver);
|
||||
mm.addWeakMessageListener("MarionetteFrame:handleModal", this);
|
||||
mm.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
|
||||
mm.addWeakMessageListener("MarionetteFrame:getInterruptedState", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes listeners for messages from content frame scripts.
|
||||
* We do not remove the MarionetteFrame:getInterruptedState or
|
||||
* the Marionette:switchToModalOrigin message listener, because we
|
||||
* want to allow all known frames to contact the frame manager so
|
||||
* that it can check if it was interrupted, and if so, it will call
|
||||
* switchToModalOrigin when its process gets resumed.
|
||||
*
|
||||
* @param {nsIMessageListenerManager} mm
|
||||
* The message manager object, typically
|
||||
* ChromeMessageBroadcaster or ChromeMessageSender.
|
||||
*/
|
||||
removeMessageManagerListeners(mm) {
|
||||
mm.removeWeakMessageListener("Marionette:ok", this.driver);
|
||||
mm.removeWeakMessageListener("Marionette:done", this.driver);
|
||||
mm.removeWeakMessageListener("Marionette:error", this.driver);
|
||||
mm.removeWeakMessageListener("Marionette:log", this.driver);
|
||||
mm.removeWeakMessageListener("Marionette:shareData", this.driver);
|
||||
mm.removeWeakMessageListener("Marionette:runEmulatorCmd", this.driver.emulator);
|
||||
mm.removeWeakMessageListener("Marionette:runEmulatorShell", this.driver.emulator);
|
||||
mm.removeWeakMessageListener("Marionette:switchedToFrame", this.driver);
|
||||
mm.removeWeakMessageListener("Marionette:getVisibleCookies", this.driver);
|
||||
mm.removeWeakMessageListener("Marionette:listenersAttached", this.driver);
|
||||
mm.removeWeakMessageListener("Marionette:register", this.driver);
|
||||
mm.removeWeakMessageListener("Marionette:getFiles", this.driver);
|
||||
mm.removeWeakMessageListener("MarionetteFrame:handleModal", this);
|
||||
mm.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
|
||||
}
|
||||
};
|
||||
|
||||
frame.Manager.prototype.QueryInterface = XPCOMUtils.generateQI(
|
||||
[Ci.nsIMessageListener, Ci.nsISupportsWeakReference]);
|
@ -2,17 +2,15 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
/* global Components, Accessibility, ElementNotVisibleError,
|
||||
InvalidElementStateError, Interactions */
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
var {utils: Cu} = Components;
|
||||
|
||||
Cu.import("chrome://marionette/content/accessibility.js");
|
||||
Cu.import("chrome://marionette/content/atom.js");
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
Cu.import("chrome://marionette/content/element.js");
|
||||
Cu.import("chrome://marionette/content/event.js");
|
||||
this.EXPORTED_SYMBOLS = ['Interactions'];
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Interactions"];
|
||||
Cu.import('chrome://marionette/content/accessibility.js');
|
||||
Cu.import('chrome://marionette/content/error.js');
|
||||
|
||||
/**
|
||||
* XUL elements that support disabled attribtue.
|
||||
@ -74,11 +72,32 @@ const SELECTED_PROPERTY_SUPPORTED_XUL = new Set([
|
||||
'TAB'
|
||||
]);
|
||||
|
||||
/**
|
||||
* This function generates a pair of coordinates relative to the viewport given
|
||||
* a target element and coordinates relative to that element's top-left corner.
|
||||
* @param 'x', and 'y' are the relative to the target.
|
||||
* If they are not specified, then the center of the target is used.
|
||||
*/
|
||||
function coordinates(target, x, y) {
|
||||
let box = target.getBoundingClientRect();
|
||||
if (typeof x === 'undefined') {
|
||||
x = box.width / 2;
|
||||
}
|
||||
if (typeof y === 'undefined') {
|
||||
y = box.height / 2;
|
||||
}
|
||||
return {
|
||||
x: box.left + x,
|
||||
y: box.top + y
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of interactions available in marionette.
|
||||
* @type {Object}
|
||||
*/
|
||||
this.Interactions = function(getCapabilies) {
|
||||
this.Interactions = function(utils, getCapabilies) {
|
||||
this.utils = utils;
|
||||
this.accessibility = new Accessibility(getCapabilies);
|
||||
};
|
||||
|
||||
@ -96,25 +115,22 @@ Interactions.prototype = {
|
||||
*/
|
||||
clickElement(container, elementManager, id) {
|
||||
let el = elementManager.getKnownElement(id, container);
|
||||
let visible = elements.checkVisible(el, container.frame);
|
||||
let visible = this.checkVisible(container, el);
|
||||
if (!visible) {
|
||||
throw new ElementNotVisibleError('Element is not visible');
|
||||
}
|
||||
return this.accessibility.getAccessibleObject(el, true).then(acc => {
|
||||
this.accessibility.checkVisible(acc, el, visible);
|
||||
if (atom.isElementEnabled(el)) {
|
||||
if (this.utils.isElementEnabled(el)) {
|
||||
this.accessibility.checkEnabled(acc, el, true, container);
|
||||
this.accessibility.checkActionable(acc, el);
|
||||
if (elements.isXULElement(el)) {
|
||||
if (this.isXULElement(el)) {
|
||||
el.click();
|
||||
} else {
|
||||
let rects = el.getClientRects();
|
||||
let win = el.ownerDocument.defaultView;
|
||||
event.synthesizeMouseAtPoint(
|
||||
rects[0].left + rects[0].width / 2,
|
||||
rects[0].top + rects[0].height / 2,
|
||||
{} /* opts */,
|
||||
win);
|
||||
this.utils.synthesizeMouseAtPoint(rects[0].left + rects[0].width/2,
|
||||
rects[0].top + rects[0].height/2,
|
||||
{}, el.ownerDocument.defaultView);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidElementStateError('Element is not enabled');
|
||||
@ -143,8 +159,8 @@ Interactions.prototype = {
|
||||
let el = elementManager.getKnownElement(id, container);
|
||||
return this.accessibility.getAccessibleObject(el, true).then(acc => {
|
||||
this.accessibility.checkActionable(acc, el);
|
||||
event.sendKeysToElement(
|
||||
value, el, {ignoreVisibility: false}, container.frame);
|
||||
this.utils.sendKeysToElement(
|
||||
container.frame, el, value, ignoreVisibility);
|
||||
});
|
||||
},
|
||||
|
||||
@ -164,7 +180,7 @@ Interactions.prototype = {
|
||||
*/
|
||||
isElementDisplayed(container, elementManager, id) {
|
||||
let el = elementManager.getKnownElement(id, container);
|
||||
let displayed = atom.isElementDisplayed(el, container.frame);
|
||||
let displayed = this.utils.isElementDisplayed(el);
|
||||
return this.accessibility.getAccessibleObject(el).then(acc => {
|
||||
this.accessibility.checkVisible(acc, el, displayed);
|
||||
return displayed;
|
||||
@ -188,16 +204,16 @@ Interactions.prototype = {
|
||||
isElementEnabled(container, elementManager, id) {
|
||||
let el = elementManager.getKnownElement(id, container);
|
||||
let enabled = true;
|
||||
if (elements.isXULElement(el)) {
|
||||
if (this.isXULElement(el)) {
|
||||
// Check if XUL element supports disabled attribute
|
||||
if (DISABLED_ATTRIBUTE_SUPPORTED_XUL.has(el.tagName.toUpperCase())) {
|
||||
let disabled = atom.getElementAttribute(el, 'disabled', container.frame);
|
||||
let disabled = this.utils.getElementAttribute(el, 'disabled');
|
||||
if (disabled && disabled === 'true') {
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
enabled = atom.isElementEnabled(el, container.frame);
|
||||
enabled = this.utils.isElementEnabled(el);
|
||||
}
|
||||
return this.accessibility.getAccessibleObject(el).then(acc => {
|
||||
this.accessibility.checkEnabled(acc, el, enabled, container);
|
||||
@ -222,7 +238,7 @@ Interactions.prototype = {
|
||||
isElementSelected(container, elementManager, id) {
|
||||
let el = elementManager.getKnownElement(id, container);
|
||||
let selected = true;
|
||||
if (elements.isXULElement(el)) {
|
||||
if (this.isXULElement(el)) {
|
||||
let tagName = el.tagName.toUpperCase();
|
||||
if (CHECKED_PROPERTY_SUPPORTED_XUL.has(tagName)) {
|
||||
selected = el.checked;
|
||||
@ -231,11 +247,71 @@ Interactions.prototype = {
|
||||
selected = el.selected;
|
||||
}
|
||||
} else {
|
||||
selected = atom.isElementSelected(el, container.frame);
|
||||
selected = this.utils.isElementSelected(el);
|
||||
}
|
||||
return this.accessibility.getAccessibleObject(el).then(acc => {
|
||||
this.accessibility.checkSelected(acc, el, selected);
|
||||
return selected;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* This function throws the visibility of the element error if the element is
|
||||
* not displayed or the given coordinates are not within the viewport.
|
||||
*
|
||||
* @param 'x', and 'y' are the coordinates relative to the target.
|
||||
* If they are not specified, then the center of the target is used.
|
||||
*/
|
||||
checkVisible(container, el, x, y) {
|
||||
// Bug 1094246 - Webdriver's isShown doesn't work with content xul
|
||||
if (!this.isXULElement(el)) {
|
||||
//check if the element is visible
|
||||
let visible = this.utils.isElementDisplayed(el);
|
||||
if (!visible) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (el.tagName.toLowerCase() === 'body') {
|
||||
return true;
|
||||
}
|
||||
if (!this.elementInViewport(container, el, x, y)) {
|
||||
//check if scroll function exist. If so, call it.
|
||||
if (el.scrollIntoView) {
|
||||
el.scrollIntoView(false);
|
||||
if (!this.elementInViewport(container, el)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
isXULElement(el) {
|
||||
return this.utils.getElementAttribute(el, 'namespaceURI').indexOf(
|
||||
'there.is.only.xul') >= 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* This function returns true if the given coordinates are in the viewport.
|
||||
* @param 'x', and 'y' are the coordinates relative to the target.
|
||||
* If they are not specified, then the center of the target is used.
|
||||
*/
|
||||
elementInViewport(container, el, x, y) {
|
||||
let c = coordinates(el, x, y);
|
||||
let win = container.frame;
|
||||
let viewPort = {
|
||||
top: win.pageYOffset,
|
||||
left: win.pageXOffset,
|
||||
bottom: win.pageYOffset + win.innerHeight,
|
||||
right: win.pageXOffset + win.innerWidth
|
||||
};
|
||||
return (viewPort.left <= c.x + win.pageXOffset &&
|
||||
c.x + win.pageXOffset <= viewPort.right &&
|
||||
viewPort.top <= c.y + win.pageYOffset &&
|
||||
c.y + win.pageYOffset <= viewPort.bottom);
|
||||
}
|
||||
};
|
@ -6,15 +6,17 @@ marionette.jar:
|
||||
% content marionette %content/
|
||||
content/server.js (server.js)
|
||||
content/driver.js (driver.js)
|
||||
content/action.js (action.js)
|
||||
content/interaction.js (interaction.js)
|
||||
content/actions.js (actions.js)
|
||||
content/interactions.js (interactions.js)
|
||||
content/accessibility.js (accessibility.js)
|
||||
content/listener.js (listener.js)
|
||||
content/element.js (element.js)
|
||||
content/elements.js (elements.js)
|
||||
content/sendkeys.js (sendkeys.js)
|
||||
content/common.js (common.js)
|
||||
content/simpletest.js (simpletest.js)
|
||||
content/frame.js (frame.js)
|
||||
content/event.js (event.js)
|
||||
content/frame-manager.js (frame-manager.js)
|
||||
content/EventUtils.js (EventUtils.js)
|
||||
content/ChromeUtils.js (ChromeUtils.js)
|
||||
content/error.js (error.js)
|
||||
content/message.js (message.js)
|
||||
content/dispatcher.js (dispatcher.js)
|
||||
@ -23,7 +25,6 @@ marionette.jar:
|
||||
content/proxy.js (proxy.js)
|
||||
content/capture.js (capture.js)
|
||||
content/cookies.js (cookies.js)
|
||||
content/atom.js (atom.js)
|
||||
#ifdef ENABLE_TESTS
|
||||
content/test.xul (client/marionette/chrome/test.xul)
|
||||
content/test2.xul (client/marionette/chrome/test2.xul)
|
||||
|
@ -5,28 +5,33 @@
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
var uuidGen = Cc["@mozilla.org/uuid-generator;1"]
|
||||
.getService(Ci.nsIUUIDGenerator);
|
||||
.getService(Ci.nsIUUIDGenerator);
|
||||
|
||||
var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader);
|
||||
.getService(Ci.mozIJSSubScriptLoader);
|
||||
|
||||
loader.loadSubScript("chrome://marionette/content/simpletest.js");
|
||||
loader.loadSubScript("chrome://marionette/content/common.js");
|
||||
|
||||
Cu.import("chrome://marionette/content/action.js");
|
||||
Cu.import("chrome://marionette/content/atom.js");
|
||||
Cu.import("chrome://marionette/content/actions.js");
|
||||
Cu.import("chrome://marionette/content/capture.js");
|
||||
Cu.import("chrome://marionette/content/cookies.js");
|
||||
Cu.import("chrome://marionette/content/element.js");
|
||||
Cu.import("chrome://marionette/content/elements.js");
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
Cu.import("chrome://marionette/content/event.js");
|
||||
Cu.import("chrome://marionette/content/proxy.js");
|
||||
Cu.import("chrome://marionette/content/interaction.js");
|
||||
Cu.import("chrome://marionette/content/interactions.js");
|
||||
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
var utils = {};
|
||||
utils.window = content;
|
||||
// Load Event/ChromeUtils for use with JS scripts:
|
||||
loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
|
||||
loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
|
||||
loader.loadSubScript("chrome://marionette/content/atoms.js", utils);
|
||||
loader.loadSubScript("chrome://marionette/content/sendkeys.js", utils);
|
||||
|
||||
var marionetteLogObj = new MarionetteLogObj();
|
||||
|
||||
@ -43,9 +48,9 @@ var elementManager = new ElementManager([]);
|
||||
|
||||
// Holds session capabilities.
|
||||
var capabilities = {};
|
||||
var interactions = new Interactions(() => capabilities);
|
||||
var interactions = new Interactions(utils, () => capabilities);
|
||||
|
||||
var actions = new action.Chain(checkForInterrupted);
|
||||
var actions = new actions.Chain(utils, checkForInterrupted);
|
||||
var importedScripts = null;
|
||||
|
||||
// Contains the last file input element that was the target of
|
||||
@ -524,6 +529,7 @@ function createExecuteContentSandbox(win, timeout) {
|
||||
sandbox.window = win;
|
||||
sandbox.document = sandbox.window.document;
|
||||
sandbox.navigator = sandbox.window.navigator;
|
||||
sandbox.testUtils = utils;
|
||||
sandbox.asyncTestCommandId = asyncTestCommandId;
|
||||
sandbox.marionette = mn;
|
||||
|
||||
@ -887,13 +893,67 @@ function coordinates(target, x, y) {
|
||||
return coords;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function returns true if the given coordinates are in the viewport.
|
||||
* @param 'x', and 'y' are the coordinates relative to the target.
|
||||
* If they are not specified, then the center of the target is used.
|
||||
*/
|
||||
function elementInViewport(el, x, y) {
|
||||
let c = coordinates(el, x, y);
|
||||
let curFrame = curContainer.frame;
|
||||
let viewPort = {top: curFrame.pageYOffset,
|
||||
left: curFrame.pageXOffset,
|
||||
bottom: (curFrame.pageYOffset + curFrame.innerHeight),
|
||||
right:(curFrame.pageXOffset + curFrame.innerWidth)};
|
||||
return (viewPort.left <= c.x + curFrame.pageXOffset &&
|
||||
c.x + curFrame.pageXOffset <= viewPort.right &&
|
||||
viewPort.top <= c.y + curFrame.pageYOffset &&
|
||||
c.y + curFrame.pageYOffset <= viewPort.bottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function throws the visibility of the element error if the element is
|
||||
* not displayed or the given coordinates are not within the viewport.
|
||||
* @param 'x', and 'y' are the coordinates relative to the target.
|
||||
* If they are not specified, then the center of the target is used.
|
||||
*/
|
||||
function checkVisible(el, x, y) {
|
||||
// Bug 1094246 - Webdriver's isShown doesn't work with content xul
|
||||
if (utils.getElementAttribute(el, "namespaceURI").indexOf("there.is.only.xul") == -1) {
|
||||
//check if the element is visible
|
||||
let visible = utils.isElementDisplayed(el);
|
||||
if (!visible) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (el.tagName.toLowerCase() === 'body') {
|
||||
return true;
|
||||
}
|
||||
if (!elementInViewport(el, x, y)) {
|
||||
//check if scroll function exist. If so, call it.
|
||||
if (el.scrollIntoView) {
|
||||
el.scrollIntoView(false);
|
||||
if (!elementInViewport(el)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function that perform a single tap
|
||||
*/
|
||||
function singleTap(id, corx, cory) {
|
||||
let el = elementManager.getKnownElement(id, curContainer);
|
||||
// after this block, the element will be scrolled into view
|
||||
let visible = elements.checkVisible(el, curContainer.frame, corx, cory);
|
||||
let visible = checkVisible(el, corx, cory);
|
||||
if (!visible) {
|
||||
throw new ElementNotVisibleError("Element is not currently visible and may not be manipulated");
|
||||
}
|
||||
@ -1333,7 +1393,7 @@ function clickElement(id) {
|
||||
*/
|
||||
function getElementAttribute(id, name) {
|
||||
let el = elementManager.getKnownElement(id, curContainer);
|
||||
return atom.getElementAttribute(el, name, curContainer.frame);
|
||||
return utils.getElementAttribute(el, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1347,7 +1407,7 @@ function getElementAttribute(id, name) {
|
||||
*/
|
||||
function getElementText(id) {
|
||||
let el = elementManager.getKnownElement(id, curContainer);
|
||||
return atom.getElementText(el, curContainer.frame);
|
||||
return utils.getElementText(el);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1451,11 +1511,11 @@ function sendKeysToElement(msg) {
|
||||
// so pass the filename to driver.js, which in turn passes them back
|
||||
// to this frame script in receiveFiles.
|
||||
sendSyncMessage("Marionette:getFiles",
|
||||
{value: p, command_id: command_id});
|
||||
{value: p, command_id: command_id});
|
||||
} else {
|
||||
interactions.sendKeysToElement(curContainer, elementManager, id, val)
|
||||
.then(() => sendOk(command_id))
|
||||
.catch(e => sendError(e, command_id));
|
||||
.then(() => sendOk(command_id))
|
||||
.catch(e => sendError(e, command_id));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1468,7 +1528,7 @@ function clearElement(id) {
|
||||
if (el.type == "file") {
|
||||
el.value = null;
|
||||
} else {
|
||||
atom.clearElement(el, curContainer.frame);
|
||||
utils.clearElement(el);
|
||||
}
|
||||
} catch (e) {
|
||||
// Bug 964738: Newer atoms contain status codes which makes wrapping
|
||||
|
@ -2,7 +2,7 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += ["components"]
|
||||
DIRS += ["components", "atoms"]
|
||||
|
||||
JAR_MANIFESTS += ["jar.mn"]
|
||||
MARIONETTE_UNIT_MANIFESTS += ['client/marionette/tests/unit/unit-tests.ini']
|
||||
|
@ -171,8 +171,7 @@ proxy.AsyncMessageChannel = class {
|
||||
if (typeof obj == "undefined") {
|
||||
this.sendReply_(uuid, proxy.AsyncMessageChannel.ReplyType.Ok);
|
||||
} else if (error.isError(obj)) {
|
||||
let err = error.wrap(obj);
|
||||
let serr = error.toJson(err);
|
||||
let serr = error.toJson(obj);
|
||||
this.sendReply_(uuid, proxy.AsyncMessageChannel.ReplyType.Error, serr);
|
||||
} else {
|
||||
this.sendReply_(uuid, proxy.AsyncMessageChannel.ReplyType.Value, obj);
|
||||
|
165
testing/marionette/sendkeys.js
Normal file
165
testing/marionette/sendkeys.js
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2007-2009 WebDriver committers
|
||||
* Copyright 2007-2009 Google Inc.
|
||||
* Portions copyright 2012 Software Freedom Conservancy
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
|
||||
var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader);
|
||||
|
||||
var utils = {};
|
||||
loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
|
||||
loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
|
||||
|
||||
var keyModifierNames = {
|
||||
"VK_SHIFT": 'shiftKey',
|
||||
"VK_CONTROL": 'ctrlKey',
|
||||
"VK_ALT": 'altKey',
|
||||
"VK_META": 'metaKey'
|
||||
};
|
||||
|
||||
var keyCodes = {
|
||||
'\uE001': "VK_CANCEL",
|
||||
'\uE002': "VK_HELP",
|
||||
'\uE003': "VK_BACK_SPACE",
|
||||
'\uE004': "VK_TAB",
|
||||
'\uE005': "VK_CLEAR",
|
||||
'\uE006': "VK_RETURN",
|
||||
'\uE007': "VK_RETURN",
|
||||
'\uE008': "VK_SHIFT",
|
||||
'\uE009': "VK_CONTROL",
|
||||
'\uE00A': "VK_ALT",
|
||||
'\uE03D': "VK_META",
|
||||
'\uE00B': "VK_PAUSE",
|
||||
'\uE00C': "VK_ESCAPE",
|
||||
'\uE00D': "VK_SPACE", // printable
|
||||
'\uE00E': "VK_PAGE_UP",
|
||||
'\uE00F': "VK_PAGE_DOWN",
|
||||
'\uE010': "VK_END",
|
||||
'\uE011': "VK_HOME",
|
||||
'\uE012': "VK_LEFT",
|
||||
'\uE013': "VK_UP",
|
||||
'\uE014': "VK_RIGHT",
|
||||
'\uE015': "VK_DOWN",
|
||||
'\uE016': "VK_INSERT",
|
||||
'\uE017': "VK_DELETE",
|
||||
'\uE018': "VK_SEMICOLON",
|
||||
'\uE019': "VK_EQUALS",
|
||||
'\uE01A': "VK_NUMPAD0",
|
||||
'\uE01B': "VK_NUMPAD1",
|
||||
'\uE01C': "VK_NUMPAD2",
|
||||
'\uE01D': "VK_NUMPAD3",
|
||||
'\uE01E': "VK_NUMPAD4",
|
||||
'\uE01F': "VK_NUMPAD5",
|
||||
'\uE020': "VK_NUMPAD6",
|
||||
'\uE021': "VK_NUMPAD7",
|
||||
'\uE022': "VK_NUMPAD8",
|
||||
'\uE023': "VK_NUMPAD9",
|
||||
'\uE024': "VK_MULTIPLY",
|
||||
'\uE025': "VK_ADD",
|
||||
'\uE026': "VK_SEPARATOR",
|
||||
'\uE027': "VK_SUBTRACT",
|
||||
'\uE028': "VK_DECIMAL",
|
||||
'\uE029': "VK_DIVIDE",
|
||||
'\uE031': "VK_F1",
|
||||
'\uE032': "VK_F2",
|
||||
'\uE033': "VK_F3",
|
||||
'\uE034': "VK_F4",
|
||||
'\uE035': "VK_F5",
|
||||
'\uE036': "VK_F6",
|
||||
'\uE037': "VK_F7",
|
||||
'\uE038': "VK_F8",
|
||||
'\uE039': "VK_F9",
|
||||
'\uE03A': "VK_F10",
|
||||
'\uE03B': "VK_F11",
|
||||
'\uE03C': "VK_F12"
|
||||
};
|
||||
|
||||
function getKeyCode (c) {
|
||||
if (c in keyCodes) {
|
||||
return keyCodes[c];
|
||||
}
|
||||
return c;
|
||||
};
|
||||
|
||||
function sendKeyDown (keyToSend, modifiers, document) {
|
||||
modifiers.type = "keydown";
|
||||
sendSingleKey(keyToSend, modifiers, document);
|
||||
if (["VK_SHIFT", "VK_CONTROL",
|
||||
"VK_ALT", "VK_META"].indexOf(getKeyCode(keyToSend)) == -1) {
|
||||
modifiers.type = "keypress";
|
||||
sendSingleKey(keyToSend, modifiers, document);
|
||||
}
|
||||
delete modifiers.type;
|
||||
}
|
||||
|
||||
function sendKeyUp (keyToSend, modifiers, document) {
|
||||
modifiers.type = "keyup";
|
||||
sendSingleKey(keyToSend, modifiers, document);
|
||||
delete modifiers.type;
|
||||
}
|
||||
|
||||
function sendSingleKey (keyToSend, modifiers, document) {
|
||||
let keyCode = getKeyCode(keyToSend);
|
||||
if (keyCode in keyModifierNames) {
|
||||
let modName = keyModifierNames[keyCode];
|
||||
modifiers[modName] = !modifiers[modName];
|
||||
} else if (modifiers.shiftKey) {
|
||||
keyCode = keyCode.toUpperCase();
|
||||
}
|
||||
utils.synthesizeKey(keyCode, modifiers, document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus element and, if a textual input field and no previous selection
|
||||
* state exists, move the caret to the end of the input field.
|
||||
*
|
||||
* @param {Element} el
|
||||
* Element to focus.
|
||||
*/
|
||||
function focusElement(el) {
|
||||
let t = el.type;
|
||||
if (t && (t == "text" || t == "textarea")) {
|
||||
if (el.selectionEnd == 0) {
|
||||
let len = el.value.length;
|
||||
el.setSelectionRange(len, len);
|
||||
}
|
||||
}
|
||||
el.focus();
|
||||
}
|
||||
|
||||
function sendKeysToElement(document, element, keysToSend, ignoreVisibility) {
|
||||
if (ignoreVisibility || checkVisible(element)) {
|
||||
focusElement(element);
|
||||
|
||||
let modifiers = {
|
||||
shiftKey: false,
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
metaKey: false
|
||||
};
|
||||
let value = keysToSend.join("");
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
var c = value.charAt(i);
|
||||
sendSingleKey(c, modifiers, document);
|
||||
}
|
||||
} else {
|
||||
throw new ElementNotVisibleError("Element is not visible");
|
||||
}
|
||||
};
|
@ -15,12 +15,18 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
Cu.import("chrome://marionette/content/dispatcher.js");
|
||||
Cu.import("chrome://marionette/content/driver.js");
|
||||
Cu.import("chrome://marionette/content/element.js");
|
||||
Cu.import("chrome://marionette/content/elements.js");
|
||||
Cu.import("chrome://marionette/content/simpletest.js");
|
||||
|
||||
// Bug 1083711: Load transport.js as an SDK module instead of subscript
|
||||
loader.loadSubScript("resource://devtools/shared/transport/transport.js");
|
||||
|
||||
// Preserve this import order:
|
||||
var events = {};
|
||||
loader.loadSubScript("chrome://marionette/content/EventUtils.js", events);
|
||||
loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", events);
|
||||
loader.loadSubScript("chrome://marionette/content/frame-manager.js");
|
||||
|
||||
const logger = Log.repository.getLogger("Marionette");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["MarionetteServer"];
|
||||
|
@ -14,19 +14,6 @@ function notok(condition) {
|
||||
ok(!(condition));
|
||||
}
|
||||
|
||||
add_test(function test_BuiltinErrors() {
|
||||
ok("Error" in error.BuiltinErrors);
|
||||
ok("EvalError" in error.BuiltinErrors);
|
||||
ok("InternalError" in error.BuiltinErrors);
|
||||
ok("RangeError" in error.BuiltinErrors);
|
||||
ok("ReferenceError" in error.BuiltinErrors);
|
||||
ok("SyntaxError" in error.BuiltinErrors);
|
||||
ok("TypeError" in error.BuiltinErrors);
|
||||
ok("URIError" in error.BuiltinErrors);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_isError() {
|
||||
notok(error.isError(null));
|
||||
notok(error.isError([]));
|
||||
@ -34,13 +21,6 @@ add_test(function test_isError() {
|
||||
|
||||
ok(error.isError(new Components.Exception()));
|
||||
ok(error.isError(new Error()));
|
||||
ok(error.isError(new EvalError()));
|
||||
ok(error.isError(new InternalError()));
|
||||
ok(error.isError(new RangeError()));
|
||||
ok(error.isError(new ReferenceError()));
|
||||
ok(error.isError(new SyntaxError()));
|
||||
ok(error.isError(new TypeError()));
|
||||
ok(error.isError(new URIError()));
|
||||
ok(error.isError(new WebDriverError()));
|
||||
ok(error.isError(new InvalidArgumentError()));
|
||||
|
||||
@ -50,13 +30,6 @@ add_test(function test_isError() {
|
||||
add_test(function test_isWebDriverError() {
|
||||
notok(error.isWebDriverError(new Components.Exception()));
|
||||
notok(error.isWebDriverError(new Error()));
|
||||
notok(error.isWebDriverError(new EvalError()));
|
||||
notok(error.isWebDriverError(new InternalError()));
|
||||
notok(error.isWebDriverError(new RangeError()));
|
||||
notok(error.isWebDriverError(new ReferenceError()));
|
||||
notok(error.isWebDriverError(new SyntaxError()));
|
||||
notok(error.isWebDriverError(new TypeError()));
|
||||
notok(error.isWebDriverError(new URIError()));
|
||||
|
||||
ok(error.isWebDriverError(new WebDriverError()));
|
||||
ok(error.isWebDriverError(new InvalidArgumentError()));
|
||||
@ -64,21 +37,6 @@ add_test(function test_isWebDriverError() {
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_wrap() {
|
||||
equal(error.wrap(new WebDriverError()).name, "WebDriverError");
|
||||
equal(error.wrap(new InvalidArgumentError()).name, "InvalidArgumentError");
|
||||
equal(error.wrap(new Error()).name, "WebDriverError");
|
||||
equal(error.wrap(new EvalError()).name, "WebDriverError");
|
||||
equal(error.wrap(new InternalError()).name, "WebDriverError");
|
||||
equal(error.wrap(new RangeError()).name, "WebDriverError");
|
||||
equal(error.wrap(new ReferenceError()).name, "WebDriverError");
|
||||
equal(error.wrap(new SyntaxError()).name, "WebDriverError");
|
||||
equal(error.wrap(new TypeError()).name, "WebDriverError");
|
||||
equal(error.wrap(new URIError()).name, "WebDriverError");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_stringify() {
|
||||
equal("<unprintable error>", error.stringify());
|
||||
equal("<unprintable error>", error.stringify("foo"));
|
||||
|
@ -600,75 +600,75 @@ function _computeKeyCodeFromChar(aChar)
|
||||
if (aChar.length != 1) {
|
||||
return 0;
|
||||
}
|
||||
const KeyEvent = _EU_Ci.nsIDOMKeyEvent;
|
||||
const nsIDOMKeyEvent = _EU_Ci.nsIDOMKeyEvent;
|
||||
if (aChar >= 'a' && aChar <= 'z') {
|
||||
return KeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'a'.charCodeAt(0);
|
||||
return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'a'.charCodeAt(0);
|
||||
}
|
||||
if (aChar >= 'A' && aChar <= 'Z') {
|
||||
return KeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'A'.charCodeAt(0);
|
||||
return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'A'.charCodeAt(0);
|
||||
}
|
||||
if (aChar >= '0' && aChar <= '9') {
|
||||
return KeyEvent.DOM_VK_0 + aChar.charCodeAt(0) - '0'.charCodeAt(0);
|
||||
return nsIDOMKeyEvent.DOM_VK_0 + aChar.charCodeAt(0) - '0'.charCodeAt(0);
|
||||
}
|
||||
// returns US keyboard layout's keycode
|
||||
switch (aChar) {
|
||||
case '~':
|
||||
case '`':
|
||||
return KeyEvent.DOM_VK_BACK_QUOTE;
|
||||
return nsIDOMKeyEvent.DOM_VK_BACK_QUOTE;
|
||||
case '!':
|
||||
return KeyEvent.DOM_VK_1;
|
||||
return nsIDOMKeyEvent.DOM_VK_1;
|
||||
case '@':
|
||||
return KeyEvent.DOM_VK_2;
|
||||
return nsIDOMKeyEvent.DOM_VK_2;
|
||||
case '#':
|
||||
return KeyEvent.DOM_VK_3;
|
||||
return nsIDOMKeyEvent.DOM_VK_3;
|
||||
case '$':
|
||||
return KeyEvent.DOM_VK_4;
|
||||
return nsIDOMKeyEvent.DOM_VK_4;
|
||||
case '%':
|
||||
return KeyEvent.DOM_VK_5;
|
||||
return nsIDOMKeyEvent.DOM_VK_5;
|
||||
case '^':
|
||||
return KeyEvent.DOM_VK_6;
|
||||
return nsIDOMKeyEvent.DOM_VK_6;
|
||||
case '&':
|
||||
return KeyEvent.DOM_VK_7;
|
||||
return nsIDOMKeyEvent.DOM_VK_7;
|
||||
case '*':
|
||||
return KeyEvent.DOM_VK_8;
|
||||
return nsIDOMKeyEvent.DOM_VK_8;
|
||||
case '(':
|
||||
return KeyEvent.DOM_VK_9;
|
||||
return nsIDOMKeyEvent.DOM_VK_9;
|
||||
case ')':
|
||||
return KeyEvent.DOM_VK_0;
|
||||
return nsIDOMKeyEvent.DOM_VK_0;
|
||||
case '-':
|
||||
case '_':
|
||||
return KeyEvent.DOM_VK_SUBTRACT;
|
||||
return nsIDOMKeyEvent.DOM_VK_SUBTRACT;
|
||||
case '+':
|
||||
case '=':
|
||||
return KeyEvent.DOM_VK_EQUALS;
|
||||
return nsIDOMKeyEvent.DOM_VK_EQUALS;
|
||||
case '{':
|
||||
case '[':
|
||||
return KeyEvent.DOM_VK_OPEN_BRACKET;
|
||||
return nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET;
|
||||
case '}':
|
||||
case ']':
|
||||
return KeyEvent.DOM_VK_CLOSE_BRACKET;
|
||||
return nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET;
|
||||
case '|':
|
||||
case '\\':
|
||||
return KeyEvent.DOM_VK_BACK_SLASH;
|
||||
return nsIDOMKeyEvent.DOM_VK_BACK_SLASH;
|
||||
case ':':
|
||||
case ';':
|
||||
return KeyEvent.DOM_VK_SEMICOLON;
|
||||
return nsIDOMKeyEvent.DOM_VK_SEMICOLON;
|
||||
case '\'':
|
||||
case '"':
|
||||
return KeyEvent.DOM_VK_QUOTE;
|
||||
return nsIDOMKeyEvent.DOM_VK_QUOTE;
|
||||
case '<':
|
||||
case ',':
|
||||
return KeyEvent.DOM_VK_COMMA;
|
||||
return nsIDOMKeyEvent.DOM_VK_COMMA;
|
||||
case '>':
|
||||
case '.':
|
||||
return KeyEvent.DOM_VK_PERIOD;
|
||||
return nsIDOMKeyEvent.DOM_VK_PERIOD;
|
||||
case '?':
|
||||
case '/':
|
||||
return KeyEvent.DOM_VK_SLASH;
|
||||
return nsIDOMKeyEvent.DOM_VK_SLASH;
|
||||
case '\n':
|
||||
return KeyEvent.DOM_VK_RETURN;
|
||||
return nsIDOMKeyEvent.DOM_VK_RETURN;
|
||||
case ' ':
|
||||
return KeyEvent.DOM_VK_SPACE;
|
||||
return nsIDOMKeyEvent.DOM_VK_SPACE;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@ -711,15 +711,15 @@ function _computeKeyCodeFromChar(aChar)
|
||||
*
|
||||
* aWindow is optional, and defaults to the current window object.
|
||||
*/
|
||||
function synthesizeKey(aKey, aEvent, aWindow = window)
|
||||
function synthesizeKey(aKey, aEvent, aWindow)
|
||||
{
|
||||
var TIP = _getTIP(aWindow);
|
||||
if (!TIP) {
|
||||
return;
|
||||
}
|
||||
var modifiers = _emulateToActivateModifiers(TIP, aEvent, aWindow);
|
||||
var keyEventDict = _createKeyboardEventDictionary(aKey, aEvent, aWindow);
|
||||
var keyEvent = new aWindow.KeyboardEvent("", keyEventDict.dictionary);
|
||||
var modifiers = _emulateToActivateModifiers(TIP, aEvent);
|
||||
var keyEventDict = _createKeyboardEventDictionary(aKey, aEvent);
|
||||
var keyEvent = new KeyboardEvent("", keyEventDict.dictionary);
|
||||
var dispatchKeydown =
|
||||
!("type" in aEvent) || aEvent.type === "keydown" || !aEvent.type;
|
||||
var dispatchKeyup =
|
||||
@ -730,7 +730,7 @@ function synthesizeKey(aKey, aEvent, aWindow = window)
|
||||
TIP.keydown(keyEvent, keyEventDict.flags);
|
||||
if ("repeat" in aEvent && aEvent.repeat > 1) {
|
||||
keyEventDict.dictionary.repeat = true;
|
||||
var repeatedKeyEvent = new aWindow.KeyboardEvent("", keyEventDict.dictionary);
|
||||
var repeatedKeyEvent = new KeyboardEvent("", keyEventDict.dictionary);
|
||||
for (var i = 1; i < aEvent.repeat; i++) {
|
||||
TIP.keydown(repeatedKeyEvent, keyEventDict.flags);
|
||||
}
|
||||
@ -740,7 +740,7 @@ function synthesizeKey(aKey, aEvent, aWindow = window)
|
||||
TIP.keyup(keyEvent, keyEventDict.flags);
|
||||
}
|
||||
} finally {
|
||||
_emulateToInactivateModifiers(TIP, modifiers, aWindow);
|
||||
_emulateToInactivateModifiers(TIP, modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
@ -864,9 +864,9 @@ const KEYBOARD_LAYOUT_THAI =
|
||||
*/
|
||||
|
||||
function synthesizeNativeKey(aKeyboardLayout, aNativeKeyCode, aModifiers,
|
||||
aChars, aUnmodifiedChars, aCallback, aWindow = window)
|
||||
aChars, aUnmodifiedChars, aCallback)
|
||||
{
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
var utils = _getDOMWindowUtils(window);
|
||||
if (!utils) {
|
||||
return false;
|
||||
}
|
||||
@ -989,8 +989,12 @@ function disableNonTestMouseEvents(aDisable)
|
||||
domutils.disableNonTestMouseEvents(aDisable);
|
||||
}
|
||||
|
||||
function _getDOMWindowUtils(aWindow = window)
|
||||
function _getDOMWindowUtils(aWindow)
|
||||
{
|
||||
if (!aWindow) {
|
||||
aWindow = window;
|
||||
}
|
||||
|
||||
// we need parent.SpecialPowers for:
|
||||
// layout/base/tests/test_reftests_with_caret.html
|
||||
// chrome: toolkit/content/tests/chrome/test_findbar.xul
|
||||
@ -1051,9 +1055,8 @@ function _getTIP(aWindow, aCallback)
|
||||
return tip;
|
||||
}
|
||||
|
||||
function _guessKeyNameFromKeyCode(aKeyCode, aWindow = window)
|
||||
function _guessKeyNameFromKeyCode(aKeyCode)
|
||||
{
|
||||
const KeyboardEvent = aWindow.KeyboardEvent;
|
||||
switch (aKeyCode) {
|
||||
case KeyboardEvent.DOM_VK_CANCEL:
|
||||
return "Cancel";
|
||||
@ -1198,8 +1201,10 @@ function _guessKeyNameFromKeyCode(aKeyCode, aWindow = window)
|
||||
}
|
||||
}
|
||||
|
||||
function _createKeyboardEventDictionary(aKey, aKeyEvent, aWindow = window) {
|
||||
function _createKeyboardEventDictionary(aKey, aKeyEvent)
|
||||
{
|
||||
var result = { dictionary: null, flags: 0 };
|
||||
|
||||
var keyCodeIsDefined = "keyCode" in aKeyEvent;
|
||||
var keyCode =
|
||||
(keyCodeIsDefined && aKeyEvent.keyCode >= 0 && aKeyEvent.keyCode <= 255) ?
|
||||
@ -1209,11 +1214,11 @@ function _createKeyboardEventDictionary(aKey, aKeyEvent, aWindow = window) {
|
||||
keyName = aKey.substr("KEY_".length);
|
||||
result.flags |= _EU_Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
|
||||
} else if (aKey.indexOf("VK_") == 0) {
|
||||
keyCode = _EU_Ci.nsIDOMKeyEvent["DOM_" + aKey];
|
||||
keyCode = KeyEvent["DOM_" + aKey];
|
||||
if (!keyCode) {
|
||||
throw "Unknown key: " + aKey;
|
||||
}
|
||||
keyName = _guessKeyNameFromKeyCode(keyCode, aWindow);
|
||||
keyName = _guessKeyNameFromKeyCode(keyCode);
|
||||
result.flags |= _EU_Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
|
||||
} else if (aKey != "") {
|
||||
keyName = aKey;
|
||||
@ -1239,7 +1244,7 @@ function _createKeyboardEventDictionary(aKey, aKeyEvent, aWindow = window) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function _emulateToActivateModifiers(aTIP, aKeyEvent, aWindow = window)
|
||||
function _emulateToActivateModifiers(aTIP, aKeyEvent)
|
||||
{
|
||||
if (!aKeyEvent) {
|
||||
return null;
|
||||
@ -1254,7 +1259,7 @@ function _emulateToActivateModifiers(aTIP, aKeyEvent, aWindow = window)
|
||||
{ key: "OS", attr: "osKey" },
|
||||
{ key: "Shift", attr: "shiftKey" },
|
||||
{ key: "Symbol", attr: "symbolKey" },
|
||||
{ key: (aWindow.navigator.platform.indexOf("Mac") >= 0) ? "Meta" : "Control",
|
||||
{ key: (navigator.platform.indexOf("Mac") >= 0) ? "Meta" : "Control",
|
||||
attr: "accelKey" },
|
||||
],
|
||||
lockable: [
|
||||
@ -1273,7 +1278,7 @@ function _emulateToActivateModifiers(aTIP, aKeyEvent, aWindow = window)
|
||||
if (aTIP.getModifierState(modifiers.normal[i].key)) {
|
||||
continue; // already activated.
|
||||
}
|
||||
var event = new aWindow.KeyboardEvent("", { key: modifiers.normal[i].key });
|
||||
var event = new KeyboardEvent("", { key: modifiers.normal[i].key });
|
||||
aTIP.keydown(event,
|
||||
aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
|
||||
modifiers.normal[i].activated = true;
|
||||
@ -1285,7 +1290,7 @@ function _emulateToActivateModifiers(aTIP, aKeyEvent, aWindow = window)
|
||||
if (aTIP.getModifierState(modifiers.lockable[i].key)) {
|
||||
continue; // already activated.
|
||||
}
|
||||
var event = new aWindow.KeyboardEvent("", { key: modifiers.lockable[i].key });
|
||||
var event = new KeyboardEvent("", { key: modifiers.lockable[i].key });
|
||||
aTIP.keydown(event,
|
||||
aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
|
||||
aTIP.keyup(event,
|
||||
@ -1295,7 +1300,7 @@ function _emulateToActivateModifiers(aTIP, aKeyEvent, aWindow = window)
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
function _emulateToInactivateModifiers(aTIP, aModifiers, aWindow = window)
|
||||
function _emulateToInactivateModifiers(aTIP, aModifiers)
|
||||
{
|
||||
if (!aModifiers) {
|
||||
return;
|
||||
@ -1304,7 +1309,7 @@ function _emulateToInactivateModifiers(aTIP, aModifiers, aWindow = window)
|
||||
if (!aModifiers.normal[i].activated) {
|
||||
continue;
|
||||
}
|
||||
var event = new aWindow.KeyboardEvent("", { key: aModifiers.normal[i].key });
|
||||
var event = new KeyboardEvent("", { key: aModifiers.normal[i].key });
|
||||
aTIP.keyup(event,
|
||||
aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
|
||||
}
|
||||
@ -1315,7 +1320,7 @@ function _emulateToInactivateModifiers(aTIP, aModifiers, aWindow = window)
|
||||
if (!aTIP.getModifierState(aModifiers.lockable[i].key)) {
|
||||
continue; // who already inactivated this?
|
||||
}
|
||||
var event = new aWindow.KeyboardEvent("", { key: aModifiers.lockable[i].key });
|
||||
var event = new KeyboardEvent("", { key: aModifiers.lockable[i].key });
|
||||
aTIP.keydown(event,
|
||||
aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
|
||||
aTIP.keyup(event,
|
||||
@ -1350,15 +1355,15 @@ function synthesizeComposition(aEvent, aWindow, aCallback)
|
||||
if (!TIP) {
|
||||
return false;
|
||||
}
|
||||
var modifiers = _emulateToActivateModifiers(TIP, aEvent.key, aWindow);
|
||||
var modifiers = _emulateToActivateModifiers(TIP, aEvent.key);
|
||||
var ret = false;
|
||||
var keyEventDict =
|
||||
"key" in aEvent ?
|
||||
_createKeyboardEventDictionary(aEvent.key.key, aEvent.key, aWindow) :
|
||||
_createKeyboardEventDictionary(aEvent.key.key, aEvent.key) :
|
||||
{ dictionary: null, flags: 0 };
|
||||
var keyEvent =
|
||||
"key" in aEvent ?
|
||||
new aWindow.KeyboardEvent(aEvent.type === "keydown" ? "keydown" : "",
|
||||
new KeyboardEvent(aEvent.type === "keydown" ? "keydown" : "",
|
||||
keyEventDict.dictionary) :
|
||||
null;
|
||||
try {
|
||||
@ -1375,7 +1380,7 @@ function synthesizeComposition(aEvent, aWindow, aCallback)
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
_emulateToInactivateModifiers(TIP, modifiers, aWindow);
|
||||
_emulateToInactivateModifiers(TIP, modifiers);
|
||||
}
|
||||
}
|
||||
/**
|
||||
@ -1465,20 +1470,20 @@ function synthesizeCompositionChange(aEvent, aWindow, aCallback)
|
||||
TIP.setCaretInPendingComposition(aEvent.caret.start);
|
||||
}
|
||||
|
||||
var modifiers = _emulateToActivateModifiers(TIP, aEvent.key, aWindow);
|
||||
var modifiers = _emulateToActivateModifiers(TIP, aEvent.key);
|
||||
try {
|
||||
var keyEventDict =
|
||||
"key" in aEvent ?
|
||||
_createKeyboardEventDictionary(aEvent.key.key, aEvent.key, aWindow) :
|
||||
_createKeyboardEventDictionary(aEvent.key.key, aEvent.key) :
|
||||
{ dictionary: null, flags: 0 };
|
||||
var keyEvent =
|
||||
"key" in aEvent ?
|
||||
new aWindow.KeyboardEvent(aEvent.type === "keydown" ? "keydown" : "",
|
||||
new KeyboardEvent(aEvent.type === "keydown" ? "keydown" : "",
|
||||
keyEventDict.dictionary) :
|
||||
null;
|
||||
TIP.flushPendingComposition(keyEvent, keyEventDict.flags);
|
||||
} finally {
|
||||
_emulateToInactivateModifiers(TIP, modifiers, aWindow);
|
||||
_emulateToInactivateModifiers(TIP, modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user