diff --git a/mozilla/testing/tools/pageloader/Makefile b/mozilla/testing/tools/pageloader/Makefile
new file mode 100644
index 00000000000..19975c554c6
--- /dev/null
+++ b/mozilla/testing/tools/pageloader/Makefile
@@ -0,0 +1,6 @@
+all:
+ zip -r pageloader.xpi chrome chrome.manifest install.rdf
+
+check:
+ ./test/chrome/content/report.js
+ ./test/chrome/content/pageloader.js
diff --git a/mozilla/testing/tools/pageloader/README b/mozilla/testing/tools/pageloader/README
new file mode 100644
index 00000000000..48c14678c2c
--- /dev/null
+++ b/mozilla/testing/tools/pageloader/README
@@ -0,0 +1,13 @@
+This is a pageloader extension for Firefox. It cycles through a list of URLs
+specified in a text file by the user (one URL per line), and reports statistics
+and raw data about the time each page took to load.
+
+This data is dumped to the console, so the browser.dom.window.dump.enabled pref
+(boolean) must be set to "true", and on Windows Firefox must be run with the
+"-console" command line switch.
+
+This test is intended to be run standalone from a chrome URL, e.g.:
+firefox -chrome chrome://pageloader/content/pageloader.xul
+
+The window will close and the data will be dumped to the console when the
+test has completed.
diff --git a/mozilla/testing/tools/pageloader/chrome.manifest b/mozilla/testing/tools/pageloader/chrome.manifest
new file mode 100644
index 00000000000..8cff5392a2f
--- /dev/null
+++ b/mozilla/testing/tools/pageloader/chrome.manifest
@@ -0,0 +1 @@
+content pageloader chrome/content/
diff --git a/mozilla/testing/tools/pageloader/chrome/content/pageloader.css b/mozilla/testing/tools/pageloader/chrome/content/pageloader.css
new file mode 100644
index 00000000000..8d395562d10
--- /dev/null
+++ b/mozilla/testing/tools/pageloader/chrome/content/pageloader.css
@@ -0,0 +1,3 @@
+window {
+ background-color: #0088CC;
+}
diff --git a/mozilla/testing/tools/pageloader/chrome/content/pageloader.js b/mozilla/testing/tools/pageloader/chrome/content/pageloader.js
new file mode 100644
index 00000000000..534ffa470b2
--- /dev/null
+++ b/mozilla/testing/tools/pageloader/chrome/content/pageloader.js
@@ -0,0 +1,148 @@
+var NUM_CYCLES = 5;
+
+var pages;
+var pageIndex;
+var results;
+var start_time;
+var end_time;
+var cycle;
+var report;
+
+function plInit() {
+ try {
+ pageIndex = 0;
+ cycle = 0;
+ results = new Object();
+ if (! pages) {
+ pages = plLoadURLsFromFile();
+ }
+ if (pages.length == 0) {
+ alert('no pages to test');
+ plStop(true);
+ }
+ report = new Report(pages);
+ plLoadPage();
+ } catch(e) {
+ alert(e);
+ plStop(true);
+ }
+}
+
+function plLoadPage() {
+ try {
+ start_time = new Date();
+ p = pages[pageIndex];
+ var startButton = document.getElementById('plStartButton');
+ startButton.setAttribute('disabled', 'true');
+ this.content = document.getElementById('contentPageloader');
+ this.content.addEventListener('load', plLoadHandler, true);
+ this.content.loadURI(p);
+ } catch (e) {
+ alert(e);
+ plStop(true);
+ }
+}
+
+function plLoadHandler(evt) {
+ if (evt.type == 'load') {
+ window.setTimeout('reallyHandle()', 500);
+ } else {
+ alert('Unknown event type: '+evt.type);
+ plStop(true);
+ }
+}
+
+function reallyHandle() {
+ if (pageIndex < pages.length) {
+ try {
+ end_time = new Date();
+ var pageName = pages[pageIndex];
+ results[pageName] = (end_time - start_time);
+ start_time = new Date();
+ dump(pageName+" took "+results[pageName]+"\n");
+ plReport();
+ pageIndex++;
+ plLoadPage();
+ } catch(e) {
+ alert(e);
+ plStop(true);
+ }
+ } else {
+ plStop(false);
+ }
+}
+
+function plReport() {
+ try {
+ var reportNode = document.getElementById('report');
+ var pageName = pages[pageIndex];
+ var time = results[pageName];
+ report.recordTime(pageIndex, time);
+ } catch(e) {
+ alert(e);
+ plStop(false);
+ }
+}
+
+function plStop(force) {
+ try {
+ pageIndex = 0;
+ results = new Object;
+ if (force == false) {
+ if (cycle < NUM_CYCLES) {
+ cycle++;
+ plLoadPage();
+ return;
+ } else {
+ dump(report.getReport()+"\n");
+ }
+ }
+ var startButton = document.getElementById('plStartButton');
+ startButton.setAttribute('disabled', 'false');
+ this.content.removeEventListener('load', plLoadHandler, true);
+ //goQuitApplication();
+ } catch(e) {
+ alert(e);
+ }
+}
+
+/* Returns array */
+function plLoadURLsFromFile() {
+ try {
+ const nsIFilePicker = Components.interfaces.nsIFilePicker;
+
+ var fp = Components.classes["@mozilla.org/filepicker;1"]
+ .createInstance(nsIFilePicker);
+ fp.init(window, "Dialog Title", nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText);
+
+ var rv = fp.show();
+ if (rv == nsIFilePicker.returnOK)
+ {
+ var file = fp.file;
+ var data = "";
+ var fstream =
+ Components.classes["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Components.interfaces.nsIFileInputStream);
+ var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Components.interfaces.nsIScriptableInputStream);
+ fstream.init(file, -1, 0, 0);
+ sstream.init(fstream);
+
+ var str = sstream.read(4096);
+ while (str.length > 0) {
+ data += str;
+ str = sstream.read(4096);
+ }
+
+ sstream.close();
+ fstream.close();
+ var p = data.split("\n");
+ // discard result of final split (EOF)
+ p.pop()
+ return p;
+ }
+ } catch (e) {
+ alert(e);
+ }
+}
diff --git a/mozilla/testing/tools/pageloader/chrome/content/pageloader.xul b/mozilla/testing/tools/pageloader/chrome/content/pageloader.xul
new file mode 100755
index 00000000000..7c3f366fc83
--- /dev/null
+++ b/mozilla/testing/tools/pageloader/chrome/content/pageloader.xul
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mozilla/testing/tools/pageloader/chrome/content/report.js b/mozilla/testing/tools/pageloader/chrome/content/report.js
new file mode 100644
index 00000000000..d87fdde9733
--- /dev/null
+++ b/mozilla/testing/tools/pageloader/chrome/content/report.js
@@ -0,0 +1,112 @@
+// Constructor
+function Report(pages) {
+ this.pages = pages;
+ this.timeVals = new Array(pages.length); // matrix of times
+ for (var i = 0; i < this.timeVals.length; ++i) {
+ this.timeVals[i] = new Array;
+ }
+}
+
+// returns an object with the following properties:
+// min : min value of array elements
+// max : max value of array elements
+// mean : mean value of array elements
+// vari : variance computation
+// stdd : standard deviation, sqrt(vari)
+// indexOfMax : index of max element (the element that is
+// removed from the mean computation)
+Report.prototype.getArrayStats = function(ary) {
+ var r = {};
+ r.min = ary[0];
+ r.max = ary[0];
+ r.indexOfMax = 0;
+ var sum = 0;
+ for (var i = 0; i < ary.length; ++i) {
+ if (ary[i] < r.min) {
+ r.min = ary[i];
+ } else if (ary[i] > r.max) {
+ r.max = ary[i];
+ r.indexOfMax = i;
+ }
+ sum = sum + ary[i];
+ }
+
+ // median
+ sorted_ary = ary.concat();
+ sorted_ary.sort();
+ // remove longest run
+ sorted_ary.pop();
+ if (sorted_ary.length%2) {
+ r.median = sorted_ary[(sorted_ary.length-1)/2];
+ }else{
+ var n = Math.floor(sorted_ary.length / 2);
+ r.median = (sorted_ary[n] + sorted_ary[n + 1]) / 2;
+ }
+
+ // ignore max value when computing mean and stddev
+ r.mean = (sum - r.max) / (ary.length - 1);
+
+ r.vari = 0;
+ for (var i = 0; i < ary.length; ++i) {
+ if (i == r.indexOfMax)
+ continue;
+ var d = r.mean - ary[i];
+ r.vari = r.vari + d * d;
+ }
+
+ r.vari = r.vari / (ary.length - 1);
+ r.stdd = Math.sqrt(r.vari);
+ return r;
+}
+
+Report.prototype.getReport = function() {
+ var all = new Array();
+ var counter = 0;
+
+ for (var i = 0; i < this.timeVals.length; ++i) {
+ for (var j = 0; j < this.timeVals[i].length; ++j) {
+ all[counter] = this.timeVals[i][j];
+ ++counter;
+ }
+ }
+
+ // avg and avg median are cumulative for all the pages
+ var avgs = new Array();
+ var medians = new Array();
+ for (var i = 0; i < this.timeVals.length; ++i) {
+ avgs[i] = this.getArrayStats(this.timeVals[i]).mean;
+ medians[i] = this.getArrayStats(this.timeVals[i]).median;
+ }
+ var avg = this.getArrayStats(avgs).mean;
+ var avgmed = this.getArrayStats(medians).mean;
+
+ var r = this.getArrayStats(all);
+
+ var report = '';
+
+ report +=
+ "(tinderbox dropping follows)\n"+
+ "_x_x_mozilla_page_load,"+avgmed+","+r.max+","+r.min+"\n"+
+ "_x_x_mozilla_page_load_details,avgmedian|"+avgmed+"|average|"+avg.toFixed(2)+"|minimum|"+r.min+"|maximum|"+r.max+"|stddev|"+r.stdd.toFixed(2)+":"
+
+ for (var i = 0; i < this.timeVals.length; ++i) {
+ r = this.getArrayStats(this.timeVals[i]);
+ report +=
+ '|'+
+ i+';'+
+ pages[i]+';'+
+ r.median+';'+
+ r.mean+';'+
+ r.min+';'+
+ r.max
+ for (var j = 0; j < this.timeVals[i].length; ++j) {
+ report +=
+ ';'+this.timeVals[i][j]
+ }
+ }
+ return report;
+}
+
+Report.prototype.recordTime = function(pageIndex, ms) {
+ this.timeVals[pageIndex].push(ms);
+}
diff --git a/mozilla/testing/tools/pageloader/install.rdf b/mozilla/testing/tools/pageloader/install.rdf
new file mode 100644
index 00000000000..70babd0c2ba
--- /dev/null
+++ b/mozilla/testing/tools/pageloader/install.rdf
@@ -0,0 +1,27 @@
+
+
+
+
+
+ pageloader@mozilla.org
+ 0.5
+ 2
+
+
+
+
+ {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+ 1.0+
+ 4.0
+
+
+
+
+ PageLoader
+ A pageloader extension.
+ Robert Helmer
+ http://www.roberthelmer.com/
+
+
diff --git a/mozilla/testing/tools/pageloader/test/chrome/content/pageloader.js b/mozilla/testing/tools/pageloader/test/chrome/content/pageloader.js
new file mode 100755
index 00000000000..897a958bd64
--- /dev/null
+++ b/mozilla/testing/tools/pageloader/test/chrome/content/pageloader.js
@@ -0,0 +1,46 @@
+#!/usr/bin/js
+
+// mock objects
+function alert(str) {
+ print(str);
+}
+function dump(str) {
+ print(str);
+}
+window = new Object();
+document = new Object();
+document.createEvent = function(str) {
+ obj = new Object();
+ obj.initMouseEvent = function() {}
+ return obj;
+}
+document.getElementById = function(str) {
+ obj = new Object();
+ if (str == 'contentPageloader') {
+ obj.content = new Object();
+ obj.content.addEventListener = function() {}
+ obj.content.removeEventListener = function() {}
+ obj.content.loadURI = function() {}
+ return obj.content;
+ } else if (str == 'plStartButton') {
+ obj.startButton = new Object();
+ obj.startButton.setAttribute = function(key, value) {}
+ return obj.startButton;
+ }
+}
+evt = new Object();
+evt.type = 'load';
+window.setTimeout = function() {}
+this.content = document.getElementById('content');
+
+// dummy data
+pages = ['http://google.com'];
+
+load(['chrome/content/pageloader.js']);
+load(['chrome/content/report.js']);
+
+plInit(true);
+plInit(false);
+for (cycle = 0; cycle < NUM_CYCLES*2; cycle++) {
+ plLoadHandler(evt);
+}
diff --git a/mozilla/testing/tools/pageloader/test/chrome/content/report.js b/mozilla/testing/tools/pageloader/test/chrome/content/report.js
new file mode 100755
index 00000000000..1658f19db71
--- /dev/null
+++ b/mozilla/testing/tools/pageloader/test/chrome/content/report.js
@@ -0,0 +1,19 @@
+#!/usr/bin/js
+load(['chrome/content/report.js']);
+
+pages = [
+ 'http://www.google.com',
+ 'http://www.yahoo.com',
+ 'http://www.msn.com',
+];
+
+cycle_time = 5;
+report = new Report(pages);
+
+for (var c=0; c < cycle_time; c++) {
+ for (var p=0; p < pages.length; p++) {
+ report.recordTime(p, c+1);
+ }
+}
+
+print(report.getReport());