docshell/test/chrome/docshell_helpers.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rwxr-xr-x

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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 }
    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;
    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
    31 var gExtractedPath = null;    //used to cache file path for extracting files from a .jar file
    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);
   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   }
   145   // If the test explicitly sets .eventsToListenFor to [], don't wait for any 
   146   // events.
   147   gFinalEvent = eventsToListenFor.length == 0 ? true : false;
   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   }
   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   }
   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 }
   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   }
   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);
   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   }
   234   // Notify the callback now that we're done.
   235   onNavComplete.call();
   236 }
   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 }
   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   }
   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   }
   272   if (typeof(gUnexpectedEvents) != "undefined") {
   273     is(gUnexpectedEvents.indexOf(event.type), -1,
   274        "Should not get unexpected event " + event.type);
   275   }  
   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   }
   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   }
   293   // Grab the next expected event, and compare its attributes against the 
   294   // actual event.
   295   let expected = gExpectedEvents.shift();
   297   is(event.type, expected.type, 
   298     "A " + expected.type + " event was expected, but a " +
   299     event.type + " event occurred");
   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   }  
   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   }
   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   }
   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   }
   329   // If we're out of expected events, let doPageNavigation() return.
   330   if (gExpectedEvents.length == 0)
   331     setTimeout(function() { gFinalEvent = true; }, 0);
   332 }
   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);
   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   }
   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;
   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   });
   367   window.close();
   368 }
   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   }
   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 }
   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);
   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   }
   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 }
   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 }
   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 }
   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