docshell/test/chrome/docshell_helpers.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:5de4b00ad551
1 /**
2 * Import common SimpleTest methods so that they're usable in this window.
3 */
4 var imports = [ "SimpleTest", "is", "isnot", "ise", "ok", "onerror", "todo",
5 "todo_is", "todo_isnot" ];
6 for each (var name in imports) {
7 window[name] = window.opener.wrappedJSObject[name];
8 }
9
10 /**
11 * Define global constants and variables.
12 */
13 const NAV_NONE = 0;
14 const NAV_BACK = 1;
15 const NAV_FORWARD = 2;
16 const NAV_URI = 3;
17 const NAV_RELOAD = 4;
18
19 var gExpectedEvents; // an array of events which are expected to
20 // be triggered by this navigation
21 var gUnexpectedEvents; // an array of event names which are NOT expected
22 // to be triggered by this navigation
23 var gFinalEvent; // true if the last expected event has fired
24 var gUrisNotInBFCache = []; // an array of uri's which shouldn't be stored
25 // in the bfcache
26 var gNavType = NAV_NONE; // defines the most recent navigation type
27 // executed by doPageNavigation
28 var gOrigMaxTotalViewers = // original value of max_total_viewers,
29 undefined; // to be restored at end of test
30
31 var gExtractedPath = null; //used to cache file path for extracting files from a .jar file
32
33 /**
34 * The doPageNavigation() function performs page navigations asynchronously,
35 * listens for specified events, and compares actual events with a list of
36 * expected events. When all expected events have occurred, an optional
37 * callback can be notified. The parameter passed to this function is an
38 * object with the following properties:
39 *
40 * uri: if !undefined, the browser will navigate to this uri
41 *
42 * back: if true, the browser will execute goBack()
43 *
44 * forward: if true, the browser will execute goForward()
45 *
46 * reload: if true, the browser will execute reload()
47 *
48 * eventsToListenFor: an array containing one or more of the following event
49 * types to listen for: "pageshow", "pagehide", "onload",
50 * "onunload". If this property is undefined, only a
51 * single "pageshow" events will be listened for. If this
52 * property is explicitly empty, [], then no events will
53 * be listened for.
54 *
55 * expectedEvents: an array of one or more expectedEvent objects,
56 * corresponding to the events which are expected to be
57 * fired for this navigation. Each object has the
58 * following properties:
59 *
60 * type: one of the event type strings
61 * title (optional): the title of the window the
62 * event belongs to
63 * persisted (optional): the event's expected
64 * .persisted attribute
65 *
66 * This function will verify that events with the
67 * specified properties are fired in the same order as
68 * specified in the array. If .title or .persisted
69 * properties for an expectedEvent are undefined, those
70 * properties will not be verified for that particular
71 * event.
72 *
73 * This property is ignored if eventsToListenFor is
74 * undefined or [].
75 *
76 * preventBFCache: if true, an unload handler will be added to the loaded
77 * page to prevent it from being bfcached. This property
78 * has no effect when eventsToListenFor is [].
79 *
80 * onNavComplete: a callback which is notified after all expected events
81 * have occurred, or after a timeout has elapsed. This
82 * callback is not notified if eventsToListenFor is [].
83 *
84 * There must be an expectedEvent object for each event of the types in
85 * eventsToListenFor which is triggered by this navigation. For example, if
86 * eventsToListenFor = [ "pagehide", "pageshow" ], then expectedEvents
87 * must contain an object for each pagehide and pageshow event which occurs as
88 * a result of this navigation.
89 */
90 function doPageNavigation(params) {
91 // Parse the parameters.
92 let back = params.back ? params.back : false;
93 let forward = params.forward ? params.forward : false;
94 let reload = params.reload ? params.reload : false;
95 let uri = params.uri ? params.uri : false;
96 let eventsToListenFor = typeof(params.eventsToListenFor) != "undefined" ?
97 params.eventsToListenFor : ["pageshow"];
98 gExpectedEvents = typeof(params.eventsToListenFor) == "undefined" ||
99 eventsToListenFor.length == 0 ? undefined : params.expectedEvents;
100 gUnexpectedEvents = typeof(params.eventsToListenFor) == "undefined" ||
101 eventsToListenFor.length == 0 ? undefined : params.unexpectedEvents;
102 let preventBFCache = (typeof[params.preventBFCache] == "undefined") ?
103 false : params.preventBFCache;
104 let waitOnly = (typeof(params.waitForEventsOnly) == "boolean"
105 && params.waitForEventsOnly);
106
107 // Do some sanity checking on arguments.
108 if (back && forward)
109 throw "Can't specify both back and forward";
110 if (back && uri)
111 throw "Can't specify both back and a uri";
112 if (forward && uri)
113 throw "Can't specify both forward and a uri";
114 if (reload && (forward || back || uri))
115 throw "Can't specify reload and another navigation type";
116 if (!back && !forward && !uri && !reload && !waitOnly)
117 throw "Must specify back or foward or reload or uri";
118 if (params.onNavComplete && eventsToListenFor.length == 0)
119 throw "Can't use onNavComplete when eventsToListenFor == []";
120 if (params.preventBFCache && eventsToListenFor.length == 0)
121 throw "Can't use preventBFCache when eventsToListenFor == []";
122 if (params.preventBFCache && waitOnly)
123 throw "Can't prevent bfcaching when only waiting for events";
124 if (waitOnly && typeof(params.onNavComplete) == "undefined")
125 throw "Must specify onNavComplete when specifying waitForEventsOnly";
126 if (waitOnly && (back || forward || reload || uri))
127 throw "Can't specify a navigation type when using waitForEventsOnly";
128 for each (let anEventType in eventsToListenFor) {
129 let eventFound = false;
130 if ( (anEventType == "pageshow") && (!gExpectedEvents) )
131 eventFound = true;
132 for each (let anExpectedEvent in gExpectedEvents) {
133 if (anExpectedEvent.type == anEventType)
134 eventFound = true;
135 }
136 for each (let anExpectedEventType in gUnexpectedEvents) {
137 if (anExpectedEventType == anEventType)
138 eventFound = true;
139 }
140 if (!eventFound)
141 throw "Event type " + anEventType + " is specified in " +
142 "eventsToListenFor, but not in expectedEvents";
143 }
144
145 // If the test explicitly sets .eventsToListenFor to [], don't wait for any
146 // events.
147 gFinalEvent = eventsToListenFor.length == 0 ? true : false;
148
149 // Add an event listener for each type of event in the .eventsToListenFor
150 // property of the input parameters.
151 for each (let eventType in eventsToListenFor) {
152 dump("TEST: registering a listener for " + eventType + " events\n");
153 TestWindow.getBrowser().addEventListener(eventType, pageEventListener,
154 true);
155 }
156
157 // Perform the specified navigation.
158 if (back) {
159 gNavType = NAV_BACK;
160 TestWindow.getBrowser().goBack();
161 }
162 else if (forward) {
163 gNavType = NAV_FORWARD;
164 TestWindow.getBrowser().goForward();
165 }
166 else if (uri) {
167 gNavType = NAV_URI;
168 TestWindow.getBrowser().loadURI(uri);
169 }
170 else if (reload) {
171 gNavType = NAV_RELOAD;
172 TestWindow.getBrowser().reload();
173 }
174 else if (waitOnly) {
175 gNavType = NAV_NONE;
176 }
177 else {
178 throw "No valid navigation type passed to doPageNavigation!";
179 }
180
181 // If we're listening for events and there is an .onNavComplete callback,
182 // wait for all events to occur, and then call doPageNavigation_complete().
183 if (eventsToListenFor.length > 0 && params.onNavComplete)
184 {
185 waitForTrue(
186 function() { return gFinalEvent; },
187 function() {
188 doPageNavigation_complete(eventsToListenFor, params.onNavComplete,
189 preventBFCache);
190 } );
191 }
192 }
193
194 /**
195 * Finish doPageNavigation(), by removing event listeners, adding an unload
196 * handler if appropriate, and calling the onNavComplete callback. This
197 * function is called after all the expected events for this navigation have
198 * occurred.
199 */
200 function doPageNavigation_complete(eventsToListenFor, onNavComplete,
201 preventBFCache) {
202 // Unregister our event listeners.
203 dump("TEST: removing event listeners\n");
204 for each (let eventType in eventsToListenFor) {
205 TestWindow.getBrowser().removeEventListener(eventType, pageEventListener,
206 true);
207 }
208
209 // If the .preventBFCache property was set, add an empty unload handler to
210 // prevent the page from being bfcached.
211 let uri = TestWindow.getBrowser().currentURI.spec;
212 if (preventBFCache) {
213 TestWindow.getWindow().addEventListener("unload", function() {
214 dump("TEST: Called dummy unload function to prevent page from " +
215 "being bfcached.\n");
216 }, true);
217
218 // Save the current uri in an array of uri's which shouldn't be
219 // stored in the bfcache, for later verification.
220 if (!(uri in gUrisNotInBFCache)) {
221 gUrisNotInBFCache.push(uri);
222 }
223 } else if (gNavType == NAV_URI) {
224 // If we're navigating to a uri and .preventBFCache was not
225 // specified, splice it out of gUrisNotInBFCache if it's there.
226 gUrisNotInBFCache.forEach(
227 function(element, index, array) {
228 if (element == uri) {
229 array.splice(index, 1);
230 }
231 }, this);
232 }
233
234 // Notify the callback now that we're done.
235 onNavComplete.call();
236 }
237
238 /**
239 * Allows a test to wait for page navigation events, and notify a
240 * callback when they've all been received. This works exactly the
241 * same as doPageNavigation(), except that no navigation is initiated.
242 */
243 function waitForPageEvents(params) {
244 params.waitForEventsOnly = true;
245 doPageNavigation(params);
246 }
247
248 /**
249 * The event listener which listens for expectedEvents.
250 */
251 function pageEventListener(event) {
252 try {
253 dump("TEST: eventListener received a " + event.type + " event for page " +
254 event.originalTarget.title + ", persisted=" + event.persisted + "\n");
255 } catch(e) {
256 // Ignore any exception.
257 }
258
259 // If this page shouldn't be in the bfcache because it was previously
260 // loaded with .preventBFCache, make sure that its pageshow event
261 // has .persisted = false, even if the test doesn't explicitly test
262 // for .persisted.
263 if ( (event.type == "pageshow") &&
264 (gNavType == NAV_BACK || gNavType == NAV_FORWARD) ) {
265 let uri = TestWindow.getBrowser().currentURI.spec;
266 if (uri in gUrisNotInBFCache) {
267 ok(!event.persisted, "pageshow event has .persisted = false, even " +
268 "though it was loaded with .preventBFCache previously\n");
269 }
270 }
271
272 if (typeof(gUnexpectedEvents) != "undefined") {
273 is(gUnexpectedEvents.indexOf(event.type), -1,
274 "Should not get unexpected event " + event.type);
275 }
276
277 // If no expected events were specified, mark the final event as having been
278 // triggered when a pageshow event is fired; this will allow
279 // doPageNavigation() to return.
280 if ((typeof(gExpectedEvents) == "undefined") && event.type == "pageshow")
281 {
282 setTimeout(function() { gFinalEvent = true; }, 0);
283 return;
284 }
285
286 // If there are explicitly no expected events, but we receive one, it's an
287 // error.
288 if (gExpectedEvents.length == 0) {
289 ok(false, "Unexpected event (" + event.type + ") occurred");
290 return;
291 }
292
293 // Grab the next expected event, and compare its attributes against the
294 // actual event.
295 let expected = gExpectedEvents.shift();
296
297 is(event.type, expected.type,
298 "A " + expected.type + " event was expected, but a " +
299 event.type + " event occurred");
300
301 if (typeof(expected.title) != "undefined") {
302 ok(event.originalTarget instanceof HTMLDocument,
303 "originalTarget for last " + event.type +
304 " event not an HTMLDocument");
305 is(event.originalTarget.title, expected.title,
306 "A " + event.type + " event was expected for page " +
307 expected.title + ", but was fired for page " +
308 event.originalTarget.title);
309 }
310
311 if (typeof(expected.persisted) != "undefined") {
312 is(event.persisted, expected.persisted,
313 "The persisted property of the " + event.type + " event on page " +
314 event.originalTarget.location + " had an unexpected value");
315 }
316
317 if ("visibilityState" in expected) {
318 is(event.originalTarget.visibilityState, expected.visibilityState,
319 "The visibilityState property of the document on page " +
320 event.originalTarget.location + " had an unexpected value");
321 }
322
323 if ("hidden" in expected) {
324 is(event.originalTarget.hidden, expected.hidden,
325 "The hidden property of the document on page " +
326 event.originalTarget.location + " had an unexpected value");
327 }
328
329 // If we're out of expected events, let doPageNavigation() return.
330 if (gExpectedEvents.length == 0)
331 setTimeout(function() { gFinalEvent = true; }, 0);
332 }
333
334 /**
335 * End a test.
336 */
337 function finish() {
338 // Work around bug 467960.
339 var history = TestWindow.getBrowser().webNavigation.sessionHistory;
340 history.PurgeHistory(history.count);
341
342 // If the test changed the value of max_total_viewers via a call to
343 // enableBFCache(), then restore it now.
344 if (typeof(gOrigMaxTotalViewers) != "undefined") {
345 var prefs = Components.classes["@mozilla.org/preferences-service;1"]
346 .getService(Components.interfaces.nsIPrefBranch);
347 prefs.setIntPref("browser.sessionhistory.max_total_viewers",
348 gOrigMaxTotalViewers);
349 }
350
351 // Close the test window and signal the framework that the test is done.
352 let opener = window.opener;
353 let SimpleTest = opener.wrappedJSObject.SimpleTest;
354
355 // Wait for the window to be closed before finishing the test
356 let ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
357 .getService(Components.interfaces.nsIWindowWatcher);
358 ww.registerNotification(function(subject, topic, data) {
359 if (topic == "domwindowclosed") {
360 ww.unregisterNotification(arguments.callee);
361 SimpleTest.waitForFocus(function() {
362 SimpleTest.finish();
363 }, opener);
364 }
365 });
366
367 window.close();
368 }
369
370 /**
371 * Helper function which waits until another function returns true, or until a
372 * timeout occurs, and then notifies a callback.
373 *
374 * Parameters:
375 *
376 * fn: a function which is evaluated repeatedly, and when it turns true,
377 * the onWaitComplete callback is notified.
378 *
379 * onWaitComplete: a callback which will be notified when fn() returns
380 * true, or when a timeout occurs.
381 *
382 * timeout: a timeout, in seconds or ms, after which waitForTrue() will
383 * fail an assertion and then return, even if the fn function never
384 * returns true. If timeout is undefined, waitForTrue() will never
385 * time out.
386 */
387 function waitForTrue(fn, onWaitComplete, timeout) {
388 var start = new Date().valueOf();
389 if (typeof(timeout) != "undefined") {
390 // If timeoutWait is less than 500, assume it represents seconds, and
391 // convert to ms.
392 if (timeout < 500)
393 timeout *= 1000;
394 }
395
396 // Loop until the test function returns true, or until a timeout occurs,
397 // if a timeout is defined.
398 var intervalid;
399 intervalid =
400 setInterval(
401 function() {
402 var timeoutHit = false;
403 if (typeof(timeout) != "undefined") {
404 timeoutHit = new Date().valueOf() - start >=
405 timeout ? true : false;
406 if (timeoutHit) {
407 ok(false, "Timed out waiting for condition");
408 }
409 }
410 if (timeoutHit || fn.call()) {
411 // Stop calling the test function and notify the callback.
412 clearInterval(intervalid);
413 onWaitComplete.call();
414 }
415 }, 20);
416 }
417
418 /**
419 * Enable or disable the bfcache.
420 *
421 * Parameters:
422 *
423 * enable: if true, set max_total_viewers to -1 (the default); if false, set
424 * to 0 (disabled), if a number, set it to that specific number
425 */
426 function enableBFCache(enable) {
427 var prefs = Components.classes["@mozilla.org/preferences-service;1"]
428 .getService(Components.interfaces.nsIPrefBranch);
429
430 // If this is the first time the test called enableBFCache(),
431 // store the original value of max_total_viewers, so it can
432 // be restored at the end of the test.
433 if (typeof(gOrigMaxTotalViewers) == "undefined") {
434 gOrigMaxTotalViewers =
435 prefs.getIntPref("browser.sessionhistory.max_total_viewers");
436 }
437
438 if (typeof(enable) == "boolean") {
439 if (enable)
440 prefs.setIntPref("browser.sessionhistory.max_total_viewers", -1);
441 else
442 prefs.setIntPref("browser.sessionhistory.max_total_viewers", 0);
443 }
444 else if (typeof(enable) == "number") {
445 prefs.setIntPref("browser.sessionhistory.max_total_viewers", enable);
446 }
447 }
448
449 /*
450 * get http root for local tests. Use a single extractJarToTmp instead of
451 * extracting for each test.
452 * Returns a file://path if we have a .jar file
453 */
454 function getHttpRoot() {
455 var location = window.location.href;
456 location = getRootDirectory(location);
457 var jar = getJar(location);
458 if (jar != null) {
459 if (gExtractedPath == null) {
460 var resolved = extractJarToTmp(jar);
461 gExtractedPath = resolved.path;
462 }
463 } else {
464 return null;
465 }
466 return "file://" + gExtractedPath + '/';
467 }
468
469 /**
470 * Returns the full HTTP url for a file in the mochitest docshell test
471 * directory.
472 */
473 function getHttpUrl(filename) {
474 var root = getHttpRoot();
475 if (root == null) {
476 root = "http://mochi.test:8888/chrome/docshell/test/chrome/";
477 }
478 return root + filename;
479 }
480
481 /**
482 * A convenience object with methods that return the current test window,
483 * browser, and document.
484 */
485 var TestWindow = {};
486 TestWindow.getWindow = function () {
487 return document.getElementById("content").contentWindow;
488 }
489 TestWindow.getBrowser = function () {
490 return document.getElementById("content");
491 }
492 TestWindow.getDocument = function () {
493 return document.getElementById("content").contentDocument;
494 }

mercurial