1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/docshell/test/chrome/docshell_helpers.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,494 @@ 1.4 +/** 1.5 + * Import common SimpleTest methods so that they're usable in this window. 1.6 + */ 1.7 +var imports = [ "SimpleTest", "is", "isnot", "ise", "ok", "onerror", "todo", 1.8 + "todo_is", "todo_isnot" ]; 1.9 +for each (var name in imports) { 1.10 + window[name] = window.opener.wrappedJSObject[name]; 1.11 +} 1.12 + 1.13 +/** 1.14 + * Define global constants and variables. 1.15 + */ 1.16 +const NAV_NONE = 0; 1.17 +const NAV_BACK = 1; 1.18 +const NAV_FORWARD = 2; 1.19 +const NAV_URI = 3; 1.20 +const NAV_RELOAD = 4; 1.21 + 1.22 +var gExpectedEvents; // an array of events which are expected to 1.23 + // be triggered by this navigation 1.24 +var gUnexpectedEvents; // an array of event names which are NOT expected 1.25 + // to be triggered by this navigation 1.26 +var gFinalEvent; // true if the last expected event has fired 1.27 +var gUrisNotInBFCache = []; // an array of uri's which shouldn't be stored 1.28 + // in the bfcache 1.29 +var gNavType = NAV_NONE; // defines the most recent navigation type 1.30 + // executed by doPageNavigation 1.31 +var gOrigMaxTotalViewers = // original value of max_total_viewers, 1.32 + undefined; // to be restored at end of test 1.33 + 1.34 +var gExtractedPath = null; //used to cache file path for extracting files from a .jar file 1.35 + 1.36 +/** 1.37 + * The doPageNavigation() function performs page navigations asynchronously, 1.38 + * listens for specified events, and compares actual events with a list of 1.39 + * expected events. When all expected events have occurred, an optional 1.40 + * callback can be notified. The parameter passed to this function is an 1.41 + * object with the following properties: 1.42 + * 1.43 + * uri: if !undefined, the browser will navigate to this uri 1.44 + * 1.45 + * back: if true, the browser will execute goBack() 1.46 + * 1.47 + * forward: if true, the browser will execute goForward() 1.48 + * 1.49 + * reload: if true, the browser will execute reload() 1.50 + * 1.51 + * eventsToListenFor: an array containing one or more of the following event 1.52 + * types to listen for: "pageshow", "pagehide", "onload", 1.53 + * "onunload". If this property is undefined, only a 1.54 + * single "pageshow" events will be listened for. If this 1.55 + * property is explicitly empty, [], then no events will 1.56 + * be listened for. 1.57 + * 1.58 + * expectedEvents: an array of one or more expectedEvent objects, 1.59 + * corresponding to the events which are expected to be 1.60 + * fired for this navigation. Each object has the 1.61 + * following properties: 1.62 + * 1.63 + * type: one of the event type strings 1.64 + * title (optional): the title of the window the 1.65 + * event belongs to 1.66 + * persisted (optional): the event's expected 1.67 + * .persisted attribute 1.68 + * 1.69 + * This function will verify that events with the 1.70 + * specified properties are fired in the same order as 1.71 + * specified in the array. If .title or .persisted 1.72 + * properties for an expectedEvent are undefined, those 1.73 + * properties will not be verified for that particular 1.74 + * event. 1.75 + * 1.76 + * This property is ignored if eventsToListenFor is 1.77 + * undefined or []. 1.78 + * 1.79 + * preventBFCache: if true, an unload handler will be added to the loaded 1.80 + * page to prevent it from being bfcached. This property 1.81 + * has no effect when eventsToListenFor is []. 1.82 + * 1.83 + * onNavComplete: a callback which is notified after all expected events 1.84 + * have occurred, or after a timeout has elapsed. This 1.85 + * callback is not notified if eventsToListenFor is []. 1.86 + * 1.87 + * There must be an expectedEvent object for each event of the types in 1.88 + * eventsToListenFor which is triggered by this navigation. For example, if 1.89 + * eventsToListenFor = [ "pagehide", "pageshow" ], then expectedEvents 1.90 + * must contain an object for each pagehide and pageshow event which occurs as 1.91 + * a result of this navigation. 1.92 + */ 1.93 +function doPageNavigation(params) { 1.94 + // Parse the parameters. 1.95 + let back = params.back ? params.back : false; 1.96 + let forward = params.forward ? params.forward : false; 1.97 + let reload = params.reload ? params.reload : false; 1.98 + let uri = params.uri ? params.uri : false; 1.99 + let eventsToListenFor = typeof(params.eventsToListenFor) != "undefined" ? 1.100 + params.eventsToListenFor : ["pageshow"]; 1.101 + gExpectedEvents = typeof(params.eventsToListenFor) == "undefined" || 1.102 + eventsToListenFor.length == 0 ? undefined : params.expectedEvents; 1.103 + gUnexpectedEvents = typeof(params.eventsToListenFor) == "undefined" || 1.104 + eventsToListenFor.length == 0 ? undefined : params.unexpectedEvents; 1.105 + let preventBFCache = (typeof[params.preventBFCache] == "undefined") ? 1.106 + false : params.preventBFCache; 1.107 + let waitOnly = (typeof(params.waitForEventsOnly) == "boolean" 1.108 + && params.waitForEventsOnly); 1.109 + 1.110 + // Do some sanity checking on arguments. 1.111 + if (back && forward) 1.112 + throw "Can't specify both back and forward"; 1.113 + if (back && uri) 1.114 + throw "Can't specify both back and a uri"; 1.115 + if (forward && uri) 1.116 + throw "Can't specify both forward and a uri"; 1.117 + if (reload && (forward || back || uri)) 1.118 + throw "Can't specify reload and another navigation type"; 1.119 + if (!back && !forward && !uri && !reload && !waitOnly) 1.120 + throw "Must specify back or foward or reload or uri"; 1.121 + if (params.onNavComplete && eventsToListenFor.length == 0) 1.122 + throw "Can't use onNavComplete when eventsToListenFor == []"; 1.123 + if (params.preventBFCache && eventsToListenFor.length == 0) 1.124 + throw "Can't use preventBFCache when eventsToListenFor == []"; 1.125 + if (params.preventBFCache && waitOnly) 1.126 + throw "Can't prevent bfcaching when only waiting for events"; 1.127 + if (waitOnly && typeof(params.onNavComplete) == "undefined") 1.128 + throw "Must specify onNavComplete when specifying waitForEventsOnly"; 1.129 + if (waitOnly && (back || forward || reload || uri)) 1.130 + throw "Can't specify a navigation type when using waitForEventsOnly"; 1.131 + for each (let anEventType in eventsToListenFor) { 1.132 + let eventFound = false; 1.133 + if ( (anEventType == "pageshow") && (!gExpectedEvents) ) 1.134 + eventFound = true; 1.135 + for each (let anExpectedEvent in gExpectedEvents) { 1.136 + if (anExpectedEvent.type == anEventType) 1.137 + eventFound = true; 1.138 + } 1.139 + for each (let anExpectedEventType in gUnexpectedEvents) { 1.140 + if (anExpectedEventType == anEventType) 1.141 + eventFound = true; 1.142 + } 1.143 + if (!eventFound) 1.144 + throw "Event type " + anEventType + " is specified in " + 1.145 + "eventsToListenFor, but not in expectedEvents"; 1.146 + } 1.147 + 1.148 + // If the test explicitly sets .eventsToListenFor to [], don't wait for any 1.149 + // events. 1.150 + gFinalEvent = eventsToListenFor.length == 0 ? true : false; 1.151 + 1.152 + // Add an event listener for each type of event in the .eventsToListenFor 1.153 + // property of the input parameters. 1.154 + for each (let eventType in eventsToListenFor) { 1.155 + dump("TEST: registering a listener for " + eventType + " events\n"); 1.156 + TestWindow.getBrowser().addEventListener(eventType, pageEventListener, 1.157 + true); 1.158 + } 1.159 + 1.160 + // Perform the specified navigation. 1.161 + if (back) { 1.162 + gNavType = NAV_BACK; 1.163 + TestWindow.getBrowser().goBack(); 1.164 + } 1.165 + else if (forward) { 1.166 + gNavType = NAV_FORWARD; 1.167 + TestWindow.getBrowser().goForward(); 1.168 + } 1.169 + else if (uri) { 1.170 + gNavType = NAV_URI; 1.171 + TestWindow.getBrowser().loadURI(uri); 1.172 + } 1.173 + else if (reload) { 1.174 + gNavType = NAV_RELOAD; 1.175 + TestWindow.getBrowser().reload(); 1.176 + } 1.177 + else if (waitOnly) { 1.178 + gNavType = NAV_NONE; 1.179 + } 1.180 + else { 1.181 + throw "No valid navigation type passed to doPageNavigation!"; 1.182 + } 1.183 + 1.184 + // If we're listening for events and there is an .onNavComplete callback, 1.185 + // wait for all events to occur, and then call doPageNavigation_complete(). 1.186 + if (eventsToListenFor.length > 0 && params.onNavComplete) 1.187 + { 1.188 + waitForTrue( 1.189 + function() { return gFinalEvent; }, 1.190 + function() { 1.191 + doPageNavigation_complete(eventsToListenFor, params.onNavComplete, 1.192 + preventBFCache); 1.193 + } ); 1.194 + } 1.195 +} 1.196 + 1.197 +/** 1.198 + * Finish doPageNavigation(), by removing event listeners, adding an unload 1.199 + * handler if appropriate, and calling the onNavComplete callback. This 1.200 + * function is called after all the expected events for this navigation have 1.201 + * occurred. 1.202 + */ 1.203 +function doPageNavigation_complete(eventsToListenFor, onNavComplete, 1.204 + preventBFCache) { 1.205 + // Unregister our event listeners. 1.206 + dump("TEST: removing event listeners\n"); 1.207 + for each (let eventType in eventsToListenFor) { 1.208 + TestWindow.getBrowser().removeEventListener(eventType, pageEventListener, 1.209 + true); 1.210 + } 1.211 + 1.212 + // If the .preventBFCache property was set, add an empty unload handler to 1.213 + // prevent the page from being bfcached. 1.214 + let uri = TestWindow.getBrowser().currentURI.spec; 1.215 + if (preventBFCache) { 1.216 + TestWindow.getWindow().addEventListener("unload", function() { 1.217 + dump("TEST: Called dummy unload function to prevent page from " + 1.218 + "being bfcached.\n"); 1.219 + }, true); 1.220 + 1.221 + // Save the current uri in an array of uri's which shouldn't be 1.222 + // stored in the bfcache, for later verification. 1.223 + if (!(uri in gUrisNotInBFCache)) { 1.224 + gUrisNotInBFCache.push(uri); 1.225 + } 1.226 + } else if (gNavType == NAV_URI) { 1.227 + // If we're navigating to a uri and .preventBFCache was not 1.228 + // specified, splice it out of gUrisNotInBFCache if it's there. 1.229 + gUrisNotInBFCache.forEach( 1.230 + function(element, index, array) { 1.231 + if (element == uri) { 1.232 + array.splice(index, 1); 1.233 + } 1.234 + }, this); 1.235 + } 1.236 + 1.237 + // Notify the callback now that we're done. 1.238 + onNavComplete.call(); 1.239 +} 1.240 + 1.241 +/** 1.242 + * Allows a test to wait for page navigation events, and notify a 1.243 + * callback when they've all been received. This works exactly the 1.244 + * same as doPageNavigation(), except that no navigation is initiated. 1.245 + */ 1.246 +function waitForPageEvents(params) { 1.247 + params.waitForEventsOnly = true; 1.248 + doPageNavigation(params); 1.249 +} 1.250 + 1.251 +/** 1.252 + * The event listener which listens for expectedEvents. 1.253 + */ 1.254 +function pageEventListener(event) { 1.255 + try { 1.256 + dump("TEST: eventListener received a " + event.type + " event for page " + 1.257 + event.originalTarget.title + ", persisted=" + event.persisted + "\n"); 1.258 + } catch(e) { 1.259 + // Ignore any exception. 1.260 + } 1.261 + 1.262 + // If this page shouldn't be in the bfcache because it was previously 1.263 + // loaded with .preventBFCache, make sure that its pageshow event 1.264 + // has .persisted = false, even if the test doesn't explicitly test 1.265 + // for .persisted. 1.266 + if ( (event.type == "pageshow") && 1.267 + (gNavType == NAV_BACK || gNavType == NAV_FORWARD) ) { 1.268 + let uri = TestWindow.getBrowser().currentURI.spec; 1.269 + if (uri in gUrisNotInBFCache) { 1.270 + ok(!event.persisted, "pageshow event has .persisted = false, even " + 1.271 + "though it was loaded with .preventBFCache previously\n"); 1.272 + } 1.273 + } 1.274 + 1.275 + if (typeof(gUnexpectedEvents) != "undefined") { 1.276 + is(gUnexpectedEvents.indexOf(event.type), -1, 1.277 + "Should not get unexpected event " + event.type); 1.278 + } 1.279 + 1.280 + // If no expected events were specified, mark the final event as having been 1.281 + // triggered when a pageshow event is fired; this will allow 1.282 + // doPageNavigation() to return. 1.283 + if ((typeof(gExpectedEvents) == "undefined") && event.type == "pageshow") 1.284 + { 1.285 + setTimeout(function() { gFinalEvent = true; }, 0); 1.286 + return; 1.287 + } 1.288 + 1.289 + // If there are explicitly no expected events, but we receive one, it's an 1.290 + // error. 1.291 + if (gExpectedEvents.length == 0) { 1.292 + ok(false, "Unexpected event (" + event.type + ") occurred"); 1.293 + return; 1.294 + } 1.295 + 1.296 + // Grab the next expected event, and compare its attributes against the 1.297 + // actual event. 1.298 + let expected = gExpectedEvents.shift(); 1.299 + 1.300 + is(event.type, expected.type, 1.301 + "A " + expected.type + " event was expected, but a " + 1.302 + event.type + " event occurred"); 1.303 + 1.304 + if (typeof(expected.title) != "undefined") { 1.305 + ok(event.originalTarget instanceof HTMLDocument, 1.306 + "originalTarget for last " + event.type + 1.307 + " event not an HTMLDocument"); 1.308 + is(event.originalTarget.title, expected.title, 1.309 + "A " + event.type + " event was expected for page " + 1.310 + expected.title + ", but was fired for page " + 1.311 + event.originalTarget.title); 1.312 + } 1.313 + 1.314 + if (typeof(expected.persisted) != "undefined") { 1.315 + is(event.persisted, expected.persisted, 1.316 + "The persisted property of the " + event.type + " event on page " + 1.317 + event.originalTarget.location + " had an unexpected value"); 1.318 + } 1.319 + 1.320 + if ("visibilityState" in expected) { 1.321 + is(event.originalTarget.visibilityState, expected.visibilityState, 1.322 + "The visibilityState property of the document on page " + 1.323 + event.originalTarget.location + " had an unexpected value"); 1.324 + } 1.325 + 1.326 + if ("hidden" in expected) { 1.327 + is(event.originalTarget.hidden, expected.hidden, 1.328 + "The hidden property of the document on page " + 1.329 + event.originalTarget.location + " had an unexpected value"); 1.330 + } 1.331 + 1.332 + // If we're out of expected events, let doPageNavigation() return. 1.333 + if (gExpectedEvents.length == 0) 1.334 + setTimeout(function() { gFinalEvent = true; }, 0); 1.335 +} 1.336 + 1.337 +/** 1.338 + * End a test. 1.339 + */ 1.340 +function finish() { 1.341 + // Work around bug 467960. 1.342 + var history = TestWindow.getBrowser().webNavigation.sessionHistory; 1.343 + history.PurgeHistory(history.count); 1.344 + 1.345 + // If the test changed the value of max_total_viewers via a call to 1.346 + // enableBFCache(), then restore it now. 1.347 + if (typeof(gOrigMaxTotalViewers) != "undefined") { 1.348 + var prefs = Components.classes["@mozilla.org/preferences-service;1"] 1.349 + .getService(Components.interfaces.nsIPrefBranch); 1.350 + prefs.setIntPref("browser.sessionhistory.max_total_viewers", 1.351 + gOrigMaxTotalViewers); 1.352 + } 1.353 + 1.354 + // Close the test window and signal the framework that the test is done. 1.355 + let opener = window.opener; 1.356 + let SimpleTest = opener.wrappedJSObject.SimpleTest; 1.357 + 1.358 + // Wait for the window to be closed before finishing the test 1.359 + let ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] 1.360 + .getService(Components.interfaces.nsIWindowWatcher); 1.361 + ww.registerNotification(function(subject, topic, data) { 1.362 + if (topic == "domwindowclosed") { 1.363 + ww.unregisterNotification(arguments.callee); 1.364 + SimpleTest.waitForFocus(function() { 1.365 + SimpleTest.finish(); 1.366 + }, opener); 1.367 + } 1.368 + }); 1.369 + 1.370 + window.close(); 1.371 +} 1.372 + 1.373 +/** 1.374 + * Helper function which waits until another function returns true, or until a 1.375 + * timeout occurs, and then notifies a callback. 1.376 + * 1.377 + * Parameters: 1.378 + * 1.379 + * fn: a function which is evaluated repeatedly, and when it turns true, 1.380 + * the onWaitComplete callback is notified. 1.381 + * 1.382 + * onWaitComplete: a callback which will be notified when fn() returns 1.383 + * true, or when a timeout occurs. 1.384 + * 1.385 + * timeout: a timeout, in seconds or ms, after which waitForTrue() will 1.386 + * fail an assertion and then return, even if the fn function never 1.387 + * returns true. If timeout is undefined, waitForTrue() will never 1.388 + * time out. 1.389 + */ 1.390 +function waitForTrue(fn, onWaitComplete, timeout) { 1.391 + var start = new Date().valueOf(); 1.392 + if (typeof(timeout) != "undefined") { 1.393 + // If timeoutWait is less than 500, assume it represents seconds, and 1.394 + // convert to ms. 1.395 + if (timeout < 500) 1.396 + timeout *= 1000; 1.397 + } 1.398 + 1.399 + // Loop until the test function returns true, or until a timeout occurs, 1.400 + // if a timeout is defined. 1.401 + var intervalid; 1.402 + intervalid = 1.403 + setInterval( 1.404 + function() { 1.405 + var timeoutHit = false; 1.406 + if (typeof(timeout) != "undefined") { 1.407 + timeoutHit = new Date().valueOf() - start >= 1.408 + timeout ? true : false; 1.409 + if (timeoutHit) { 1.410 + ok(false, "Timed out waiting for condition"); 1.411 + } 1.412 + } 1.413 + if (timeoutHit || fn.call()) { 1.414 + // Stop calling the test function and notify the callback. 1.415 + clearInterval(intervalid); 1.416 + onWaitComplete.call(); 1.417 + } 1.418 + }, 20); 1.419 +} 1.420 + 1.421 +/** 1.422 + * Enable or disable the bfcache. 1.423 + * 1.424 + * Parameters: 1.425 + * 1.426 + * enable: if true, set max_total_viewers to -1 (the default); if false, set 1.427 + * to 0 (disabled), if a number, set it to that specific number 1.428 + */ 1.429 +function enableBFCache(enable) { 1.430 + var prefs = Components.classes["@mozilla.org/preferences-service;1"] 1.431 + .getService(Components.interfaces.nsIPrefBranch); 1.432 + 1.433 + // If this is the first time the test called enableBFCache(), 1.434 + // store the original value of max_total_viewers, so it can 1.435 + // be restored at the end of the test. 1.436 + if (typeof(gOrigMaxTotalViewers) == "undefined") { 1.437 + gOrigMaxTotalViewers = 1.438 + prefs.getIntPref("browser.sessionhistory.max_total_viewers"); 1.439 + } 1.440 + 1.441 + if (typeof(enable) == "boolean") { 1.442 + if (enable) 1.443 + prefs.setIntPref("browser.sessionhistory.max_total_viewers", -1); 1.444 + else 1.445 + prefs.setIntPref("browser.sessionhistory.max_total_viewers", 0); 1.446 + } 1.447 + else if (typeof(enable) == "number") { 1.448 + prefs.setIntPref("browser.sessionhistory.max_total_viewers", enable); 1.449 + } 1.450 +} 1.451 + 1.452 +/* 1.453 + * get http root for local tests. Use a single extractJarToTmp instead of 1.454 + * extracting for each test. 1.455 + * Returns a file://path if we have a .jar file 1.456 + */ 1.457 +function getHttpRoot() { 1.458 + var location = window.location.href; 1.459 + location = getRootDirectory(location); 1.460 + var jar = getJar(location); 1.461 + if (jar != null) { 1.462 + if (gExtractedPath == null) { 1.463 + var resolved = extractJarToTmp(jar); 1.464 + gExtractedPath = resolved.path; 1.465 + } 1.466 + } else { 1.467 + return null; 1.468 + } 1.469 + return "file://" + gExtractedPath + '/'; 1.470 +} 1.471 + 1.472 +/** 1.473 + * Returns the full HTTP url for a file in the mochitest docshell test 1.474 + * directory. 1.475 + */ 1.476 +function getHttpUrl(filename) { 1.477 + var root = getHttpRoot(); 1.478 + if (root == null) { 1.479 + root = "http://mochi.test:8888/chrome/docshell/test/chrome/"; 1.480 + } 1.481 + return root + filename; 1.482 +} 1.483 + 1.484 +/** 1.485 + * A convenience object with methods that return the current test window, 1.486 + * browser, and document. 1.487 + */ 1.488 +var TestWindow = {}; 1.489 +TestWindow.getWindow = function () { 1.490 + return document.getElementById("content").contentWindow; 1.491 +} 1.492 +TestWindow.getBrowser = function () { 1.493 + return document.getElementById("content"); 1.494 +} 1.495 +TestWindow.getDocument = function () { 1.496 + return document.getElementById("content").contentDocument; 1.497 +}