gecko/docshell/test/chrome/docshell_helpers.js

431 lines
16 KiB
JavaScript
Executable File

/**
* Import common SimpleTest methods so that they're usable in this window.
*/
var imports = [ "SimpleTest", "is", "isnot", "ok", "onerror", "todo",
"todo_is", "todo_isnot" ];
for each (var import in imports) {
window[import] = window.opener.wrappedJSObject[import];
}
/**
* Define global constants and variables.
*/
const NAV_NONE = 0;
const NAV_BACK = 1;
const NAV_FORWARD = 2;
const NAV_URI = 3;
const NAV_RELOAD = 4;
var gExpectedEvents; // an array of events which are expected to
// be triggered by this navigation
var gFinalEvent; // true if the last expected event has fired
var gUrisNotInBFCache = []; // an array of uri's which shouldn't be stored
// in the bfcache
var gNavType = NAV_NONE; // defines the most recent navigation type
// executed by doPageNavigation
var gOrigMaxTotalViewers = // original value of max_total_viewers,
undefined; // to be restored at end of test
/**
* The doPageNavigation() function performs page navigations asynchronously,
* listens for specified events, and compares actual events with a list of
* expected events. When all expected events have occurred, an optional
* callback can be notified. The parameter passed to this function is an
* object with the following properties:
*
* uri: if !undefined, the browser will navigate to this uri
*
* back: if true, the browser will execute goBack()
*
* forward: if true, the browser will execute goForward()
*
* eventsToListenFor: an array containing one or more of the following event
* types to listen for: "pageshow", "pagehide", "onload",
* "onunload". If this property is undefined, only a
* single "pageshow" events will be listened for. If this
* property is explicitly empty, [], then no events will
* be listened for.
*
* expectedEvents: an array of one or more expectedEvent objects,
* corresponding to the events which are expected to be
* fired for this navigation. Each object has the
* following properties:
*
* type: one of the event type strings
* title (optional): the title of the window the
* event belongs to
* persisted (optional): the event's expected
* .persisted attribute
*
* This function will verify that events with the
* specified properties are fired in the same order as
* specified in the array. If .title or .persisted
* properties for an expectedEvent are undefined, those
* properties will not be verified for that particular
* event.
*
* This property is ignored if eventsToListenFor is
* undefined or [].
*
* preventBFCache: if true, an unload handler will be added to the loaded
* page to prevent it from being bfcached. This property
* has no effect when eventsToListenFor is [].
*
* onNavComplete: a callback which is notified after all expected events
* have occurred, or after a timeout has elapsed. This
* callback is not notified if eventsToListenFor is [].
*
* There must be an expectedEvent object for each event of the types in
* eventsToListenFor which is triggered by this navigation. For example, if
* eventsToListenFor = [ "pagehide", "pageshow" ], then expectedEvents
* must contain an object for each pagehide and pageshow event which occurs as
* a result of this navigation.
*/
function doPageNavigation(params) {
// Parse the parameters.
let back = params.back ? params.back : false;
let forward = params.forward ? params.forward : false;
let reload = params.reload ? params.reload : false;
let uri = params.uri ? params.uri : false;
let eventsToListenFor = typeof(params.eventsToListenFor) != "undefined" ?
params.eventsToListenFor : ["pageshow"];
gExpectedEvents = typeof(params.eventsToListenFor) == "undefined" ||
eventsToListenFor.length == 0 ? undefined : params.expectedEvents;
let preventBFCache = (typeof[params.preventBFCache] == "undefined") ?
false : params.preventBFCache;
let waitOnly = (typeof(params.waitForEventsOnly) == "boolean"
&& params.waitForEventsOnly);
// Do some sanity checking on arguments.
if (back && forward)
throw "Can't specify both back and forward";
if (back && uri)
throw "Can't specify both back and a uri";
if (forward && uri)
throw "Can't specify both forward and a uri";
if (reload && (forward || back || uri))
throw "Can't specify reload and another navigation type";
if (!back && !forward && !uri && !reload && !waitOnly)
throw "Must specify back or foward or reload or uri";
if (params.onNavComplete && eventsToListenFor.length == 0)
throw "Can't use onNavComplete when eventsToListenFor == []";
if (params.preventBFCache && eventsToListenFor.length == 0)
throw "Can't use preventBFCache when eventsToListenFor == []";
if (params.preventBFCache && waitOnly)
throw "Can't prevent bfcaching when only waiting for events";
if (waitOnly && typeof(params.onNavComplete) == "undefined")
throw "Must specify onNavComplete when specifying waitForEventsOnly";
if (waitOnly && (back || forward || reload || uri))
throw "Can't specify a navigation type when using waitForEventsOnly";
for each (let anEventType in eventsToListenFor) {
let eventFound = false;
if ( (anEventType == "pageshow") && (!gExpectedEvents) )
eventFound = true;
for each (let anExpectedEvent in gExpectedEvents) {
if (anExpectedEvent.type == anEventType)
eventFound = true;
}
if (!eventFound)
throw "Event type " + anEventType + " is specified in " +
"eventsToListenFor, but not in expectedEvents";
}
// If the test explicitly sets .eventsToListenFor to [], don't wait for any
// events.
gFinalEvent = eventsToListenFor.length == 0 ? true : false;
// Add an event listener for each type of event in the .eventsToListenFor
// property of the input parameters.
for each (let eventType in eventsToListenFor) {
dump("TEST: registering a listener for " + eventType + " events\n");
TestWindow.getBrowser().addEventListener(eventType, pageEventListener,
true);
}
// Perform the specified navigation.
if (back) {
gNavType = NAV_BACK;
TestWindow.getBrowser().goBack();
}
else if (forward) {
gNavType = NAV_FORWARD;
TestWindow.getBrowser().goForward();
}
else if (uri) {
gNavType = NAV_URI;
TestWindow.getBrowser().loadURI(uri);
}
else if (reload) {
gNavType = NAV_RELOAD;
TestWindow.getBrowser().reload();
}
else if (waitOnly) {
gNavType = NAV_NONE;
}
else {
throw "No valid navigation type passed to doPageNavigation!";
}
// If we're listening for events and there is an .onNavComplete callback,
// wait for all events to occur, and then call doPageNavigation_complete().
if (eventsToListenFor.length > 0 && params.onNavComplete)
{
waitForTrue(
function() { return gFinalEvent; },
function() {
doPageNavigation_complete(eventsToListenFor, params.onNavComplete,
preventBFCache);
} );
}
}
/**
* Finish doPageNavigation(), by removing event listeners, adding an unload
* handler if appropriate, and calling the onNavComplete callback. This
* function is called after all the expected events for this navigation have
* occurred.
*/
function doPageNavigation_complete(eventsToListenFor, onNavComplete,
preventBFCache) {
// Unregister our event listeners.
dump("TEST: removing event listeners\n");
for each (let eventType in eventsToListenFor) {
TestWindow.getBrowser().removeEventListener(eventType, pageEventListener,
true);
}
// If the .preventBFCache property was set, add an empty unload handler to
// prevent the page from being bfcached.
let uri = TestWindow.getBrowser().currentURI.spec;
if (preventBFCache) {
TestWindow.getWindow().addEventListener("unload", function() {
dump("TEST: Called dummy unload function to prevent page from " +
"being bfcached.\n");
}, true);
// Save the current uri in an array of uri's which shouldn't be
// stored in the bfcache, for later verification.
if (!(uri in gUrisNotInBFCache)) {
gUrisNotInBFCache.push(uri);
}
} else if (gNavType == NAV_URI) {
// If we're navigating to a uri and .preventBFCache was not
// specified, splice it out of gUrisNotInBFCache if it's there.
gUrisNotInBFCache.forEach(
function(element, index, array) {
if (element == uri) {
array.splice(index, 1);
}
}, this);
}
// Notify the callback now that we're done.
onNavComplete.call();
}
/**
* Allows a test to wait for page navigation events, and notify a
* callback when they've all been received. This works exactly the
* same as doPageNavigation(), except that no navigation is initiated.
*/
function waitForPageEvents(params) {
params.waitForEventsOnly = true;
doPageNavigation(params);
}
/**
* The event listener which listens for expectedEvents.
*/
function pageEventListener(event) {
try {
dump("TEST: eventListener received a " + event.type + " event for page " +
event.originalTarget.title + ", persisted=" + event.persisted + "\n");
} catch(e) {
// Ignore any exception.
}
// If this page shouldn't be in the bfcache because it was previously
// loaded with .preventBFCache, make sure that its pageshow event
// has .persisted = false, even if the test doesn't explicitly test
// for .persisted.
if ( (event.type == "pageshow") &&
(gNavType == NAV_BACK || gNavType == NAV_FORWARD) ) {
let uri = TestWindow.getBrowser().currentURI.spec;
if (uri in gUrisNotInBFCache) {
ok(!event.persisted, "pageshow event has .persisted = false, even " +
"though it was loaded with .preventBFCache previously\n");
}
}
// If no expected events were specified, mark the final event as having been
// triggered when a pageshow event is fired; this will allow
// doPageNavigation() to return.
if ((typeof(gExpectedEvents) == "undefined") && event.type == "pageshow")
{
setTimeout(function() { gFinalEvent = true; }, 0);
return;
}
// If there are explicitly no expected events, but we receive one, it's an
// error.
if (gExpectedEvents.length == 0) {
ok(false, "Unexpected event (" + event.type + ") occurred");
return;
}
// Grab the next expected event, and compare its attributes against the
// actual event.
let expected = gExpectedEvents.shift();
is(event.type, expected.type,
"A " + expected.type + " event was expected, but a " +
event.type + " event occurred");
if (typeof(expected.title) != "undefined") {
ok(event.originalTarget instanceof HTMLDocument,
"originalTarget for last " + event.type +
" event not an HTMLDocument");
is(event.originalTarget.title, expected.title,
"A " + event.type + " event was expected for page " +
expected.title + ", but was fired for page " +
event.originalTarget.title);
}
if (typeof(expected.persisted) != "undefined") {
is(event.persisted, expected.persisted,
"The persisted property of the " + event.type + " event on page " +
event.originalTarget.location + " had an unexpected value");
}
// If we're out of expected events, let doPageNavigation() return.
if (gExpectedEvents.length == 0)
setTimeout(function() { gFinalEvent = true; }, 0);
}
/**
* End a test.
*/
function finish() {
// Work around bug 467960.
var history = TestWindow.getBrowser().webNavigation.sessionHistory;
history.PurgeHistory(history.count);
// If the test changed the value of max_total_viewers via a call to
// enableBFCache(), then restore it now.
if (typeof(gOrigMaxTotalViewers) != "undefined") {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
prefs.setIntPref("browser.sessionhistory.max_total_viewers",
gOrigMaxTotalViewers);
}
// Close the test window and signal the framework that the test is done.
let opener = window.opener;
window.close();
opener.wrappedJSObject.SimpleTest.finish();
}
/**
* Helper function which waits until another function returns true, or until a
* timeout occurs, and then notifies a callback.
*
* Parameters:
*
* fn: a function which is evaluated repeatedly, and when it turns true,
* the onWaitComplete callback is notified.
*
* onWaitComplete: a callback which will be notified when fn() returns
* true, or when a timeout occurs.
*
* timeout: a timeout, in seconds or ms, after which waitForTrue() will
* fail an assertion and then return, even if the fn function never
* returns true. If timeout is undefined, waitForTrue() will never
* time out.
*/
function waitForTrue(fn, onWaitComplete, timeout) {
var start = new Date().valueOf();
if (typeof(timeout) != "undefined") {
// If timeoutWait is less than 500, assume it represents seconds, and
// convert to ms.
if (timeout < 500)
timeout *= 1000;
}
// Loop until the test function returns true, or until a timeout occurs,
// if a timeout is defined.
var intervalid;
intervalid =
setInterval(
function() {
var timeoutHit = false;
if (typeof(timeout) != "undefined") {
timeoutHit = new Date().valueOf() - start >=
timeout ? true : false;
if (timeoutHit) {
ok(false, "Timed out waiting for condition");
}
}
if (timeoutHit || fn.call()) {
// Stop calling the test function and notify the callback.
clearInterval(intervalid);
onWaitComplete.call();
}
}, 20);
}
/**
* Enable or disable the bfcache.
*
* Parameters:
*
* enable: if true, set max_total_viewers to -1 (the default); if false, set
* to 0 (disabled), if a number, set it to that specific number
*/
function enableBFCache(enable) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
// If this is the first time the test called enableBFCache(),
// store the original value of max_total_viewers, so it can
// be restored at the end of the test.
if (typeof(gOrigMaxTotalViewers) == "undefined") {
gOrigMaxTotalViewers =
prefs.getIntPref("browser.sessionhistory.max_total_viewers");
}
if (typeof(enable) == "boolean") {
if (enable)
prefs.setIntPref("browser.sessionhistory.max_total_viewers", -1);
else
prefs.setIntPref("browser.sessionhistory.max_total_viewers", 0);
}
else if (typeof(enable) == "number") {
prefs.setIntPref("browser.sessionhistory.max_total_viewers", enable);
}
}
/**
* Returns the full HTTP url for a file in the mochitest docshell test
* directory.
*/
function getHttpUrl(filename) {
return "http://mochi.test:8888/chrome/docshell/test/chrome/" + filename;
}
/**
* A convenience object with methods that return the current test window,
* browser, and document.
*/
var TestWindow = {};
TestWindow.getWindow = function () {
return document.getElementById("content").contentWindow;
}
TestWindow.getBrowser = function () {
return document.getElementById("content");
}
TestWindow.getDocument = function () {
return document.getElementById("content").contentDocument;
}