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