mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
852 lines
26 KiB
JavaScript
852 lines
26 KiB
JavaScript
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=2 et sw=2 tw=80: */
|
|
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
/*=============================================================================
|
|
Globals
|
|
=============================================================================*/
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/commonjs/sdk/core/promise.js");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
|
|
|
|
/*=============================================================================
|
|
Useful constants
|
|
=============================================================================*/
|
|
const serverRoot = "http://example.com/browser/metro/";
|
|
const baseURI = "http://mochi.test:8888/browser/metro/";
|
|
const chromeRoot = getRootDirectory(gTestPath);
|
|
const kDefaultWait = 2000;
|
|
const kDefaultInterval = 50;
|
|
|
|
/*=============================================================================
|
|
Metro ui helpers
|
|
=============================================================================*/
|
|
|
|
function isLandscapeMode()
|
|
{
|
|
return (MetroUtils.snappedState == Ci.nsIWinMetroUtils.fullScreenLandscape);
|
|
}
|
|
|
|
function setDevPixelEqualToPx()
|
|
{
|
|
todo(false, "test depends on devPixelsPerPx set to 1.0 - see bugs 886624 and 859742");
|
|
SpecialPowers.setCharPref("layout.css.devPixelsPerPx", "1.0");
|
|
registerCleanupFunction(function () {
|
|
SpecialPowers.clearUserPref("layout.css.devPixelsPerPx");
|
|
});
|
|
}
|
|
|
|
function checkContextUIMenuItemCount(aCount)
|
|
{
|
|
let visibleCount = 0;
|
|
for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) {
|
|
if (!ContextMenuUI.commands.childNodes[idx].hidden)
|
|
visibleCount++;
|
|
}
|
|
is(visibleCount, aCount, "command list count");
|
|
}
|
|
|
|
function checkContextUIMenuItemVisibility(aVisibleList)
|
|
{
|
|
let errors = 0;
|
|
for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) {
|
|
let item = ContextMenuUI.commands.childNodes[idx];
|
|
if (aVisibleList.indexOf(item.id) != -1 && item.hidden) {
|
|
// item should be visible
|
|
errors++;
|
|
info("should be visible:" + item.id);
|
|
} else if (aVisibleList.indexOf(item.id) == -1 && !item.hidden) {
|
|
// item should be hidden
|
|
errors++;
|
|
info("should be hidden:" + item.id);
|
|
}
|
|
}
|
|
is(errors, 0, "context menu item list visibility");
|
|
}
|
|
|
|
function checkMonoclePositionRange(aMonocle, aMinX, aMaxX, aMinY, aMaxY)
|
|
{
|
|
let monocle = null;
|
|
if (aMonocle == "start")
|
|
monocle = SelectionHelperUI._startMark;
|
|
else if (aMonocle == "end")
|
|
monocle = SelectionHelperUI._endMark;
|
|
else if (aMonocle == "caret")
|
|
monocle = SelectionHelperUI._caretMark;
|
|
else
|
|
ok(false, "bad monocle id");
|
|
|
|
ok(monocle.xPos > aMinX && monocle.xPos < aMaxX,
|
|
"X position is " + monocle.xPos + ", expected between " + aMinX + " and " + aMaxX);
|
|
ok(monocle.yPos > aMinY && monocle.yPos < aMaxY,
|
|
"Y position is " + monocle.yPos + ", expected between " + aMinY + " and " + aMaxY);
|
|
}
|
|
|
|
/*
|
|
* showNotification - displays a test notification with the current
|
|
* browser and waits for the noticiation to be fully displayed.
|
|
*
|
|
* Usage: yield showNotification();
|
|
*/
|
|
function showNotification()
|
|
{
|
|
return Task.spawn(function() {
|
|
try {
|
|
let strings = Strings.browser;
|
|
var buttons = [
|
|
{
|
|
isDefault: false,
|
|
label: strings.GetStringFromName("popupButtonAllowOnce2"),
|
|
accessKey: "",
|
|
callback: function() { }
|
|
},
|
|
{
|
|
label: strings.GetStringFromName("popupButtonAlwaysAllow3"),
|
|
accessKey: "",
|
|
callback: function() { }
|
|
},
|
|
{
|
|
label: strings.GetStringFromName("popupButtonNeverWarn3"),
|
|
accessKey: "",
|
|
callback: function() { }
|
|
}
|
|
];
|
|
let notificationBox = Browser.getNotificationBox();
|
|
const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
|
|
notificationBox.appendNotification("test notification", "popup-blocked",
|
|
"chrome://browser/skin/images/infobar-popup.png",
|
|
priority, buttons);
|
|
yield waitForEvent(notificationBox, "transitionend");
|
|
return;
|
|
} catch (ex) {
|
|
throw new Task.Result(ex);
|
|
}
|
|
});
|
|
}
|
|
|
|
function getSelection(aElement) {
|
|
if (!aElement)
|
|
return null;
|
|
|
|
// chrome text edit
|
|
if (aElement instanceof Ci.nsIDOMXULTextBoxElement) {
|
|
return aElement.QueryInterface(Components.interfaces.nsIDOMXULTextBoxElement)
|
|
.editor.selection;
|
|
}
|
|
|
|
// editable content element
|
|
if (aElement instanceof Ci.nsIDOMNSEditableElement) {
|
|
return aElement.QueryInterface(Ci.nsIDOMNSEditableElement)
|
|
.editor.selection;
|
|
}
|
|
|
|
// document or window
|
|
if (aElement instanceof HTMLDocument || aElement instanceof Window) {
|
|
return aElement.getSelection();
|
|
}
|
|
|
|
// browser
|
|
return aElement.contentWindow.getSelection();
|
|
};
|
|
|
|
function getTrimmedSelection(aElement) {
|
|
let sel = getSelection(aElement);
|
|
if (!sel)
|
|
return "";
|
|
return sel.toString().trim();
|
|
}
|
|
|
|
/*
|
|
* clearSelection(aTarget) - clears the current selection in
|
|
* aTarget, shuts down the selection manager and purges all
|
|
* message manager events to insure a reset state for the ui.
|
|
*/
|
|
function clearSelection(aTarget) {
|
|
SelectionHelperUI.closeEditSession(true);
|
|
getSelection(aTarget).removeAllRanges();
|
|
purgeEventQueue();
|
|
}
|
|
|
|
/*=============================================================================
|
|
Asynchronous Metro ui helpers
|
|
=============================================================================*/
|
|
|
|
// Hides the tab and context app bar if they are visible
|
|
function hideContextUI()
|
|
{
|
|
purgeEventQueue();
|
|
|
|
return Task.spawn(function() {
|
|
if (ContextUI.tabbarVisible) {
|
|
let promise = waitForEvent(Elements.tray, "transitionend", null, Elements.tray);
|
|
if (ContextUI.dismiss()) {
|
|
yield promise;
|
|
}
|
|
}
|
|
|
|
if (ContextUI.contextAppbarVisible) {
|
|
let promise = waitForEvent(Elements.contextappbar, "transitionend", null, Elements.contextappbar);
|
|
ContextUI.dismissContextAppbar();
|
|
yield promise;
|
|
}
|
|
});
|
|
}
|
|
|
|
function showNavBar()
|
|
{
|
|
let promise = waitForEvent(Elements.navbar, "transitionend");
|
|
if (!ContextUI.navbarVisible) {
|
|
ContextUI.displayNavbar();
|
|
return promise;
|
|
}
|
|
}
|
|
|
|
function fireAppBarDisplayEvent()
|
|
{
|
|
let promise = waitForEvent(Elements.tray, "transitionend");
|
|
let event = document.createEvent("Events");
|
|
event.initEvent("MozEdgeUICompleted", true, false);
|
|
gWindow.dispatchEvent(event);
|
|
purgeEventQueue();
|
|
return promise;
|
|
}
|
|
|
|
/*=============================================================================
|
|
Asynchronous test helpers
|
|
=============================================================================*/
|
|
let gOpenedTabs = [];
|
|
|
|
/**
|
|
* Loads a URL in a new tab asynchronously.
|
|
*
|
|
* Usage:
|
|
* Task.spawn(function() {
|
|
* let tab = yield addTab("http://example.com/");
|
|
* ok(Browser.selectedTab == tab, "the new tab is selected");
|
|
* });
|
|
*
|
|
* @param aUrl the URL to load
|
|
* @returns a task that resolves to the new tab object after the URL is loaded.
|
|
*/
|
|
function addTab(aUrl) {
|
|
return Task.spawn(function() {
|
|
info("Opening "+aUrl+" in a new tab");
|
|
let tab = Browser.addTab(aUrl, true);
|
|
yield tab.pageShowPromise;
|
|
|
|
is(tab.browser.currentURI.spec, aUrl, aUrl + " is loaded");
|
|
|
|
yield hideContextUI();
|
|
|
|
gOpenedTabs.push(tab);
|
|
|
|
throw new Task.Result(tab);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Cleans up tabs left open by addTab().
|
|
* This is being called at runTests() after the test loop.
|
|
*/
|
|
function cleanUpOpenedTabs() {
|
|
let tab;
|
|
while(tab = gOpenedTabs.shift()) {
|
|
Browser.closeTab(Browser.getTabFromChrome(tab.chromeTab), { forceClose: true })
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Waits a specified number of miliseconds for a specified event to be
|
|
* fired on a specified element.
|
|
*
|
|
* Usage:
|
|
* let receivedEvent = waitForEvent(element, "eventName");
|
|
* // Do some processing here that will cause the event to be fired
|
|
* // ...
|
|
* // Now yield until the Promise is fulfilled
|
|
* yield receivedEvent;
|
|
* if (receivedEvent && !(receivedEvent instanceof Error)) {
|
|
* receivedEvent.msg == "eventName";
|
|
* // ...
|
|
* }
|
|
*
|
|
* @param aSubject the element that should receive the event
|
|
* @param aEventName the event to wait for
|
|
* @param aTimeoutMs the number of miliseconds to wait before giving up
|
|
* @returns a Promise that resolves to the received event, or to an Error
|
|
*/
|
|
function waitForEvent(aSubject, aEventName, aTimeoutMs, aTarget) {
|
|
let eventDeferred = Promise.defer();
|
|
let timeoutMs = aTimeoutMs || kDefaultWait;
|
|
let stack = new Error().stack;
|
|
let timerID = setTimeout(function wfe_canceller() {
|
|
aSubject.removeEventListener(aEventName, onEvent);
|
|
eventDeferred.reject( new Error(aEventName+" event timeout at " + stack) );
|
|
}, timeoutMs);
|
|
|
|
function onEvent(aEvent) {
|
|
if (aTarget && aTarget !== aEvent.target)
|
|
return;
|
|
|
|
// stop the timeout clock and resume
|
|
clearTimeout(timerID);
|
|
eventDeferred.resolve(aEvent);
|
|
}
|
|
|
|
function cleanup() {
|
|
// unhook listener in case of success or failure
|
|
aSubject.removeEventListener(aEventName, onEvent);
|
|
}
|
|
eventDeferred.promise.then(cleanup, cleanup);
|
|
|
|
aSubject.addEventListener(aEventName, onEvent, false);
|
|
return eventDeferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Waits a specified number of miliseconds.
|
|
*
|
|
* Usage:
|
|
* let wait = yield waitForMs(2000);
|
|
* ok(wait, "2 seconds should now have elapsed");
|
|
*
|
|
* @param aMs the number of miliseconds to wait for
|
|
* @returns a Promise that resolves to true after the time has elapsed
|
|
*/
|
|
function waitForMs(aMs) {
|
|
info("Wating for " + aMs + "ms");
|
|
let deferred = Promise.defer();
|
|
let startTime = Date.now();
|
|
setTimeout(done, aMs);
|
|
|
|
function done() {
|
|
deferred.resolve(true);
|
|
info("waitForMs finished waiting, waited for "
|
|
+ (Date.now() - startTime)
|
|
+ "ms");
|
|
}
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Waits a specified number of miliseconds for a supplied callback to
|
|
* return a truthy value.
|
|
*
|
|
* Usage:
|
|
* let success = yield waitForCondition(myTestFunction);
|
|
* if (success && !(success instanceof Error)) {
|
|
* // ...
|
|
* }
|
|
*
|
|
* @param aCondition the callback that must return a truthy value
|
|
* @param aTimeoutMs the number of miliseconds to wait before giving up
|
|
* @param aIntervalMs the number of miliseconds between calls to aCondition
|
|
* @returns a Promise that resolves to true, or to an Error
|
|
*/
|
|
function waitForCondition(aCondition, aTimeoutMs, aIntervalMs) {
|
|
let deferred = Promise.defer();
|
|
let timeoutMs = aTimeoutMs || kDefaultWait;
|
|
let intervalMs = aIntervalMs || kDefaultInterval;
|
|
let startTime = Date.now();
|
|
|
|
function testCondition() {
|
|
let now = Date.now();
|
|
if((now - startTime) > timeoutMs) {
|
|
deferred.reject( new Error("Timed out waiting for condition to be true") );
|
|
return;
|
|
}
|
|
|
|
let condition;
|
|
try {
|
|
condition = aCondition();
|
|
} catch (e) {
|
|
deferred.reject( new Error("Got exception while attempting to test condition: " + e) );
|
|
return;
|
|
}
|
|
|
|
if (condition) {
|
|
deferred.resolve(true);
|
|
} else {
|
|
setTimeout(testCondition, intervalMs);
|
|
}
|
|
}
|
|
|
|
setTimeout(testCondition, 0);
|
|
return deferred.promise;
|
|
}
|
|
|
|
/*
|
|
* Waits for an image in a page to load. Wrapper around waitForCondition.
|
|
*
|
|
* @param aWindow the tab or window that contains the image.
|
|
* @param aImageId the id of the image in the page.
|
|
* @returns a Promise that resolves to true, or to an Error
|
|
*/
|
|
function waitForImageLoad(aWindow, aImageId) {
|
|
let elem = aWindow.document.getElementById(aImageId);
|
|
return waitForCondition(function () {
|
|
let request = elem.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
|
|
if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
|
|
return true;
|
|
return false;
|
|
}, 5000, 100);
|
|
}
|
|
|
|
/**
|
|
* Waits a specified number of miliseconds for an observer event.
|
|
*
|
|
* @param aObsEvent the observer event to wait for
|
|
* @param aTimeoutMs the number of miliseconds to wait before giving up
|
|
* @returns a Promise that resolves to true, or to an Error
|
|
*/
|
|
function waitForObserver(aObsEvent, aTimeoutMs) {
|
|
try {
|
|
|
|
let deferred = Promise.defer();
|
|
let timeoutMs = aTimeoutMs || kDefaultWait;
|
|
let timerID = 0;
|
|
|
|
var observeWatcher = {
|
|
onEvent: function () {
|
|
clearTimeout(timerID);
|
|
Services.obs.removeObserver(this, aObsEvent);
|
|
deferred.resolve();
|
|
},
|
|
|
|
onError: function () {
|
|
clearTimeout(timerID);
|
|
Services.obs.removeObserver(this, aObsEvent);
|
|
deferred.reject(new Error(aObsEvent + " event timeout"));
|
|
},
|
|
|
|
observe: function (aSubject, aTopic, aData) {
|
|
if (aTopic == aObsEvent) {
|
|
this.onEvent();
|
|
}
|
|
},
|
|
|
|
QueryInterface: function (aIID) {
|
|
if (!aIID.equals(Ci.nsIObserver) &&
|
|
!aIID.equals(Ci.nsISupportsWeakReference) &&
|
|
!aIID.equals(Ci.nsISupports)) {
|
|
throw Components.results.NS_ERROR_NO_INTERFACE;
|
|
}
|
|
return this;
|
|
},
|
|
}
|
|
|
|
timerID = setTimeout(function wfo_canceller() {
|
|
observeWatcher.onError();
|
|
}, timeoutMs);
|
|
|
|
Services.obs.addObserver(observeWatcher, aObsEvent, true);
|
|
return deferred.promise;
|
|
|
|
} catch (ex) {
|
|
info(ex.message);
|
|
}
|
|
}
|
|
|
|
/*=============================================================================
|
|
Native input synthesis helpers
|
|
=============================================================================*/
|
|
// Keyboard layouts for use with synthesizeNativeKey
|
|
const usEnglish = 0x409;
|
|
const arSpanish = 0x2C0A;
|
|
|
|
// Modifiers for use with synthesizeNativeKey
|
|
const leftShift = 0x100;
|
|
const rightShift = 0x200;
|
|
const leftControl = 0x400;
|
|
const rightControl = 0x800;
|
|
const leftAlt = 0x1000;
|
|
const rightAlt = 0x2000;
|
|
|
|
function synthesizeNativeKey(aKbLayout, aVKey, aModifiers) {
|
|
Browser.windowUtils.sendNativeKeyEvent(aKbLayout, aVKey, aModifiers, '', '');
|
|
}
|
|
|
|
function synthesizeNativeMouse(aElement, aOffsetX, aOffsetY, aMsg) {
|
|
let x = aOffsetX;
|
|
let y = aOffsetY;
|
|
if (aElement) {
|
|
if (aElement.getBoundingClientRect) {
|
|
let rect = aElement.getBoundingClientRect();
|
|
x += rect.left;
|
|
y += rect.top;
|
|
} else if(aElement.left && aElement.top) {
|
|
x += aElement.left;
|
|
y += aElement.top;
|
|
}
|
|
}
|
|
Browser.windowUtils.sendNativeMouseEvent(x, y, aMsg, 0, null);
|
|
}
|
|
|
|
function synthesizeNativeMouseMove(aElement, aOffsetX, aOffsetY) {
|
|
synthesizeNativeMouse(aElement,
|
|
aOffsetX,
|
|
aOffsetY,
|
|
0x0001); // MOUSEEVENTF_MOVE
|
|
}
|
|
|
|
function synthesizeNativeMouseLDown(aElement, aOffsetX, aOffsetY) {
|
|
synthesizeNativeMouse(aElement,
|
|
aOffsetX,
|
|
aOffsetY,
|
|
0x0002); // MOUSEEVENTF_LEFTDOWN
|
|
}
|
|
|
|
function synthesizeNativeMouseLUp(aElement, aOffsetX, aOffsetY) {
|
|
synthesizeNativeMouse(aElement,
|
|
aOffsetX,
|
|
aOffsetY,
|
|
0x0004); // MOUSEEVENTF_LEFTUP
|
|
}
|
|
|
|
function synthesizeNativeMouseRDown(aElement, aOffsetX, aOffsetY) {
|
|
synthesizeNativeMouse(aElement,
|
|
aOffsetX,
|
|
aOffsetY,
|
|
0x0008); // MOUSEEVENTF_RIGHTDOWN
|
|
}
|
|
|
|
function synthesizeNativeMouseRUp(aElement, aOffsetX, aOffsetY) {
|
|
synthesizeNativeMouse(aElement,
|
|
aOffsetX,
|
|
aOffsetY,
|
|
0x0010); // MOUSEEVENTF_RIGHTUP
|
|
}
|
|
|
|
function synthesizeNativeMouseMDown(aElement, aOffsetX, aOffsetY) {
|
|
synthesizeNativeMouse(aElement,
|
|
aOffsetX,
|
|
aOffsetY,
|
|
0x0020); // MOUSEEVENTF_MIDDLEDOWN
|
|
}
|
|
|
|
function synthesizeNativeMouseMUp(aElement, aOffsetX, aOffsetY) {
|
|
synthesizeNativeMouse(aElement,
|
|
aOffsetX,
|
|
aOffsetY,
|
|
0x0040); // MOUSEEVENTF_MIDDLEUP
|
|
}
|
|
|
|
/*
|
|
* logicalCoordsForElement - given coordinates relative to top-left of
|
|
* given element, returns logical coordinates for window. If a non-numeric
|
|
* X or Y value is given, a value for the center of the element in that
|
|
* dimension is used.
|
|
*
|
|
* @param aElement element coordinates are relative to.
|
|
* @param aX, aY relative coordinates.
|
|
*/
|
|
function logicalCoordsForElement (aElement, aX, aY) {
|
|
let coords = { x: null, y: null };
|
|
let rect = aElement.getBoundingClientRect();
|
|
|
|
coords.x = isNaN(aX) ? rect.left + (rect.width / 2) : rect.left + aX;
|
|
coords.y = isNaN(aY) ? rect.top + (rect.height / 2) : rect.top + aY;
|
|
|
|
return coords;
|
|
}
|
|
|
|
function sendContextMenuMouseClickToElement(aWindow, aElement, aX, aY) {
|
|
let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
|
let coords = logicalCoordsForElement(aElement, aX, aY);
|
|
|
|
utils.sendMouseEventToWindow("mousedown", coords.x, coords.y, 2, 1, 0);
|
|
utils.sendMouseEventToWindow("mouseup", coords.x, coords.y, 2, 1, 0);
|
|
utils.sendMouseEventToWindow("contextmenu", coords.x, coords.y, 2, 1, 0);
|
|
}
|
|
|
|
function sendMouseClick(aWindow, aX, aY) {
|
|
EventUtils.synthesizeMouseAtPoint(aX, aY, {}, aWindow);
|
|
}
|
|
|
|
/*
|
|
* sendContextMenuClick - simulates a press-hold touch input event. Event
|
|
* is delivered to the main window of the application through the top-level
|
|
* widget.
|
|
*
|
|
* @param aX, aY logical coordinates of the event.
|
|
*/
|
|
function sendContextMenuClick(aX, aY) {
|
|
let mediator = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
|
.getService(Components.interfaces.nsIWindowMediator);
|
|
let mainwin = mediator.getMostRecentWindow("navigator:browser");
|
|
let utils = mainwin.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
|
.getInterface(Components.interfaces.nsIDOMWindowUtils);
|
|
utils.sendMouseEvent("contextmenu", aX, aY, 2, 1, 0, true,
|
|
1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
|
|
}
|
|
|
|
/*
|
|
* sendContextMenuClickToSelection - simulates a press-hold touch input event
|
|
* selected text in a window.
|
|
*/
|
|
function sendContextMenuClickToSelection(aWindow) {
|
|
let selection = aWindow.getSelection();
|
|
if (!selection || !selection.rangeCount) {
|
|
ok(false, "no selection to tap!");
|
|
return;
|
|
}
|
|
let range = selection.getRangeAt(0);
|
|
let rect = range.getBoundingClientRect();
|
|
let x = rect.left + (rect.width / 2);
|
|
let y = rect.top + (rect.height / 2);
|
|
let utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
|
.getInterface(Components.interfaces.nsIDOMWindowUtils);
|
|
utils.sendMouseEventToWindow("contextmenu", x, y, 2, 1, 0, true,
|
|
1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
|
|
}
|
|
|
|
/*
|
|
* sendContextMenuClickToWindow - simulates a press-hold touch input event.
|
|
*
|
|
* @param aWindow window used to retrieve dom window utils, and the
|
|
* target window for the event.
|
|
* @param aX, aY logical coordinates of the event relative to aWindow.
|
|
*/
|
|
function sendContextMenuClickToWindow(aWindow, aX, aY) {
|
|
let utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
|
.getInterface(Components.interfaces.nsIDOMWindowUtils);
|
|
|
|
utils.sendMouseEventToWindow("contextmenu", aX, aY, 2, 1, 0, true,
|
|
1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
|
|
}
|
|
|
|
function sendContextMenuClickToElement(aWindow, aElement, aX, aY) {
|
|
let utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
|
.getInterface(Components.interfaces.nsIDOMWindowUtils);
|
|
let coords = logicalCoordsForElement(aElement, aX, aY);
|
|
utils.sendMouseEventToWindow("contextmenu", coords.x, coords.y, 2, 1, 0, true,
|
|
1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
|
|
}
|
|
|
|
/*
|
|
* sendDoubleTap - simulates a double click or double tap.
|
|
*/
|
|
function sendDoubleTap(aWindow, aX, aY) {
|
|
EventUtils.synthesizeMouseAtPoint(aX, aY, {
|
|
clickCount: 1,
|
|
inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
|
|
}, aWindow);
|
|
|
|
EventUtils.synthesizeMouseAtPoint(aX, aY, {
|
|
clickCount: 2,
|
|
inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
|
|
}, aWindow);
|
|
}
|
|
|
|
function sendTap(aWindow, aX, aY) {
|
|
EventUtils.synthesizeMouseAtPoint(aX, aY, {
|
|
clickCount: 1,
|
|
inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
|
|
}, aWindow);
|
|
}
|
|
|
|
function sendElementTap(aWindow, aElement, aX, aY) {
|
|
let coords = logicalCoordsForElement(aElement, aX, aY);
|
|
EventUtils.synthesizeMouseAtPoint(coords.x, coords.y, {
|
|
clickCount: 1,
|
|
inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
|
|
}, aWindow);
|
|
}
|
|
|
|
/*
|
|
* sendTouchDrag - sends a touch series composed of a touchstart,
|
|
* touchmove, and touchend w3c event.
|
|
*/
|
|
function sendTouchDrag(aWindow, aStartX, aStartY, aEndX, aEndY) {
|
|
EventUtils.synthesizeTouchAtPoint(aStartX, aStartY, { type: "touchstart" }, aWindow);
|
|
EventUtils.synthesizeTouchAtPoint(aEndX, aEndY, { type: "touchmove" }, aWindow);
|
|
EventUtils.synthesizeTouchAtPoint(aEndX, aEndY, { type: "touchend" }, aWindow);
|
|
}
|
|
|
|
/*
|
|
* TouchDragAndHold - simulates a drag and hold sequence of events.
|
|
*/
|
|
function TouchDragAndHold() {
|
|
}
|
|
|
|
TouchDragAndHold.prototype = {
|
|
_timeoutStep: 2,
|
|
_numSteps: 50,
|
|
_debug: false,
|
|
_win: null,
|
|
|
|
callback: function callback() {
|
|
if (this._win == null)
|
|
return;
|
|
if (++this._step.steps >= this._numSteps) {
|
|
EventUtils.synthesizeTouchAtPoint(this._endPoint.xPos, this._endPoint.yPos,
|
|
{ type: "touchmove" }, this._win);
|
|
this._defer.resolve();
|
|
return;
|
|
}
|
|
this._currentPoint.xPos += this._step.x;
|
|
this._currentPoint.yPos += this._step.y;
|
|
if (this._debug) {
|
|
info("[" + this._step.steps + "] touchmove " + this._currentPoint.xPos + " x " + this._currentPoint.yPos);
|
|
}
|
|
EventUtils.synthesizeTouchAtPoint(this._currentPoint.xPos, this._currentPoint.yPos,
|
|
{ type: "touchmove" }, this._win);
|
|
let self = this;
|
|
setTimeout(function () { self.callback(); }, this._timeoutStep);
|
|
},
|
|
|
|
start: function start(aWindow, aStartX, aStartY, aEndX, aEndY) {
|
|
this._defer = Promise.defer();
|
|
this._win = aWindow;
|
|
this._endPoint = { xPos: aEndX, yPos: aEndY };
|
|
this._currentPoint = { xPos: aStartX, yPos: aStartY };
|
|
this._step = { steps: 0, x: (aEndX - aStartX) / this._numSteps, y: (aEndY - aStartY) / this._numSteps };
|
|
if (this._debug) {
|
|
info("[0] touchstart " + aStartX + " x " + aStartY);
|
|
}
|
|
EventUtils.synthesizeTouchAtPoint(aStartX, aStartY, { type: "touchstart" }, aWindow);
|
|
let self = this;
|
|
setTimeout(function () { self.callback(); }, this._timeoutStep);
|
|
return this._defer.promise;
|
|
},
|
|
|
|
move: function move(aEndX, aEndY) {
|
|
if (this._win == null)
|
|
return;
|
|
if (this._debug) {
|
|
info("[0] continuation to " + aEndX + " x " + aEndY);
|
|
}
|
|
this._defer = Promise.defer();
|
|
this._step = { steps: 0,
|
|
x: (aEndX - this._endPoint.xPos) / this._numSteps,
|
|
y: (aEndY - this._endPoint.yPos) / this._numSteps };
|
|
this._endPoint = { xPos: aEndX, yPos: aEndY };
|
|
let self = this;
|
|
setTimeout(function () { self.callback(); }, this._timeoutStep);
|
|
return this._defer.promise;
|
|
},
|
|
|
|
end: function start() {
|
|
if (this._debug) {
|
|
info("[" + this._step.steps + "] touchend " + this._endPoint.xPos + " x " + this._endPoint.yPos);
|
|
}
|
|
EventUtils.synthesizeTouchAtPoint(this._endPoint.xPos, this._endPoint.yPos,
|
|
{ type: "touchend" }, this._win);
|
|
this._win = null;
|
|
},
|
|
};
|
|
|
|
/*=============================================================================
|
|
System utilities
|
|
=============================================================================*/
|
|
|
|
/*
|
|
* emptyClipboard - clear the windows clipboard.
|
|
*/
|
|
function emptyClipboard() {
|
|
Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard)
|
|
.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
|
|
}
|
|
|
|
/*
|
|
* purgeEventQueue - purges the event queue on the calling thread.
|
|
* Pumps latent in-process message manager events awaiting delivery.
|
|
*/
|
|
function purgeEventQueue() {
|
|
let thread = Services.tm.currentThread;
|
|
while (thread.hasPendingEvents()) {
|
|
if (!thread.processNextEvent(true))
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*=============================================================================
|
|
Test-running helpers
|
|
=============================================================================*/
|
|
let gCurrentTest = null;
|
|
let gTests = [];
|
|
|
|
function runTests() {
|
|
waitForExplicitFinish();
|
|
|
|
Task.spawn(function() {
|
|
while((gCurrentTest = gTests.shift())){
|
|
try {
|
|
if ('function' == typeof gCurrentTest.setUp) {
|
|
info("SETUP " + gCurrentTest.desc);
|
|
yield Task.spawn(gCurrentTest.setUp.bind(gCurrentTest));
|
|
}
|
|
try {
|
|
info("RUN " + gCurrentTest.desc);
|
|
yield Task.spawn(gCurrentTest.run.bind(gCurrentTest));
|
|
} finally {
|
|
if ('function' == typeof gCurrentTest.tearDown) {
|
|
info("TEARDOWN " + gCurrentTest.desc);
|
|
yield Task.spawn(gCurrentTest.tearDown.bind(gCurrentTest));
|
|
}
|
|
}
|
|
} catch (ex) {
|
|
ok(false, "runTests: Task failed - " + ex + ' at ' + ex.stack);
|
|
} finally {
|
|
info("END " + gCurrentTest.desc);
|
|
}
|
|
}
|
|
|
|
try {
|
|
cleanUpOpenedTabs();
|
|
|
|
let badTabs = [];
|
|
Browser.tabs.forEach(function(item, index, array) {
|
|
let location = item.browser.currentURI.spec;
|
|
if (index == 0 && location == "about:blank" || location == "about:start") {
|
|
return;
|
|
}
|
|
ok(false, "Left over tab after test: '" + location + "'");
|
|
badTabs.push(item);
|
|
});
|
|
|
|
badTabs.forEach(function(item, index, array) {
|
|
Browser.closeTab(item, { forceClose: true });
|
|
});
|
|
} catch (ex) {
|
|
ok(false, "Cleanup tabs failed - " + ex);
|
|
}
|
|
|
|
finish();
|
|
});
|
|
}
|
|
|
|
// wrap a method with a spy that records how and how many times it gets called
|
|
// the spy is returned; use spy.restore() to put the original back
|
|
function spyOnMethod(aObj, aMethod) {
|
|
let origFunc = aObj[aMethod];
|
|
let spy = function() {
|
|
spy.calledWith = Array.slice(arguments);
|
|
spy.callCount++;
|
|
return (spy.returnValue = origFunc.apply(aObj, arguments));
|
|
};
|
|
spy.callCount = 0;
|
|
spy.restore = function() {
|
|
return (aObj[aMethod] = origFunc);
|
|
};
|
|
return (aObj[aMethod] = spy);
|
|
}
|
|
|
|
// replace a method with a stub that records how and how many times it gets called
|
|
// the stub is returned; use stub.restore() to put the original back
|
|
function stubMethod(aObj, aMethod) {
|
|
let origFunc = aObj[aMethod];
|
|
let func = function() {
|
|
func.calledWith = Array.slice(arguments);
|
|
func.callCount++;
|
|
}
|
|
func.callCount = 0;
|
|
func.restore = function() {
|
|
return (aObj[aMethod] = origFunc);
|
|
};
|
|
aObj[aMethod] = func;
|
|
return func;
|
|
}
|