mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
427 lines
13 KiB
JavaScript
427 lines
13 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 = 10000;
|
|
const kDefaultInterval = 50;
|
|
|
|
/*=============================================================================
|
|
Metro ui helpers
|
|
=============================================================================*/
|
|
|
|
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");
|
|
}
|
|
|
|
/*=============================================================================
|
|
Asynchronous Metro ui helpers
|
|
=============================================================================*/
|
|
|
|
function hideContextUI()
|
|
{
|
|
purgeEventQueue();
|
|
if (ContextUI.isVisible) {
|
|
info("is visible, waiting...");
|
|
let promise = waitForEvent(Elements.tray, "transitionend");
|
|
ContextUI.dismiss();
|
|
return promise;
|
|
}
|
|
}
|
|
|
|
/*=============================================================================
|
|
Asynchronous test helpers
|
|
=============================================================================*/
|
|
/**
|
|
* 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 waitForEvent(tab.browser, "pageshow");
|
|
|
|
is(tab.browser.currentURI.spec, aUrl, aUrl + " is loaded");
|
|
registerCleanupFunction(function() Browser.closeTab(tab));
|
|
throw new Task.Result(tab);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
info("waitForEvent: on " + aSubject + " event: " + aEventName);
|
|
let eventDeferred = Promise.defer();
|
|
let timeoutMs = aTimeoutMs || kDefaultWait;
|
|
let timerID = setTimeout(function wfe_canceller() {
|
|
aSubject.removeEventListener(aEventName, onEvent);
|
|
eventDeferred.reject( new Error(aEventName+" event timeout") );
|
|
}, timeoutMs);
|
|
|
|
function onEvent(aEvent) {
|
|
// 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 conditino: " + 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
|
|
}
|
|
|
|
/*
|
|
* sendContextMenuClick - simulates a press-hold touch input event.
|
|
*/
|
|
function sendContextMenuClick(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 rect = aElement.getBoundingClientRect();
|
|
utils.sendMouseEventToWindow("contextmenu", rect.left + aX, rect.top + aY, 2, 1, 0, true,
|
|
1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
|
|
}
|
|
|
|
/*=============================================================================
|
|
System utilities
|
|
=============================================================================*/
|
|
|
|
/*
|
|
* 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())){
|
|
info(gCurrentTest.desc);
|
|
if ('function' == typeof gCurrentTest.setUp) {
|
|
yield Task.spawn(gCurrentTest.setUp.bind(gCurrentTest));
|
|
}
|
|
yield Task.spawn(gCurrentTest.run.bind(gCurrentTest));
|
|
if ('function' == typeof gCurrentTest.tearDown) {
|
|
yield Task.spawn(gCurrentTest.tearDown.bind(gCurrentTest));
|
|
}
|
|
info("END "+gCurrentTest.desc);
|
|
}
|
|
finish();
|
|
});
|
|
}
|