gecko/browser/metro/base/tests/head.js

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();
});
}