summaryrefslogtreecommitdiffstats
path: root/testing/talos/talos/pageloader/chrome/pageloader.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/talos/talos/pageloader/chrome/pageloader.js')
-rw-r--r--testing/talos/talos/pageloader/chrome/pageloader.js1111
1 files changed, 1111 insertions, 0 deletions
diff --git a/testing/talos/talos/pageloader/chrome/pageloader.js b/testing/talos/talos/pageloader/chrome/pageloader.js
new file mode 100644
index 0000000000..e424a5c2ec
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/pageloader.js
@@ -0,0 +1,1111 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from report.js */
+
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+var { E10SUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/E10SUtils.sys.mjs"
+);
+ChromeUtils.defineESModuleGetters(this, {
+ TalosParentProfiler: "resource://talos-powers/TalosParentProfiler.sys.mjs",
+});
+
+var NUM_CYCLES = 5;
+var numPageCycles = 1;
+
+var numRetries = 0;
+var maxRetries = 3;
+
+var pageFilterRegexp = null;
+var winWidth = 1024;
+var winHeight = 768;
+
+var pages;
+var pageIndex;
+var start_time;
+var cycle;
+var pageCycle;
+var report;
+var timeout = -1;
+var delay = 250;
+var running = false;
+var forceCC = true;
+
+var useMozAfterPaint = false;
+var useFNBPaint = false;
+var isFNBPaintPending = false;
+var usePDFPaint = false;
+var isPDFPaintPending = false;
+var useHero = false;
+var gPaintWindow = window;
+var gPaintListener = false;
+var loadNoCache = false;
+var scrollTest = false;
+var profilingInfo = false;
+var baseVsRef = false;
+var useBrowserChrome = false;
+var useA11y = false;
+
+var isIdleCallbackPending = false;
+
+// when TEST_DOES_OWN_TIMING, we need to store the time from the page as MozAfterPaint can be slower than pageload
+var gTime = -1;
+var gStartTime = -1;
+var gReference = -1;
+
+var content;
+
+// These are binary flags. Use 1/2/4/8/...
+var TEST_DOES_OWN_TIMING = 1;
+var EXECUTE_SCROLL_TEST = 2;
+
+var browserWindow = null;
+
+var recordedName = null;
+var pageUrls;
+
+/**
+ * SingleTimeout class. Allow to register one and only one callback using
+ * setTimeout at a time.
+ */
+var SingleTimeout = function() {
+ this.timeoutEvent = undefined;
+};
+
+/**
+ * Register a callback with the given timeout.
+ *
+ * If timeout is < 0, this is a no-op.
+ *
+ * If a callback was previously registered and has not been called yet, it is
+ * first cleared with clear().
+ */
+SingleTimeout.prototype.register = function(callback, timeout) {
+ if (timeout >= 0) {
+ if (this.timeoutEvent !== undefined) {
+ this.clear();
+ }
+ var that = this;
+ this.timeoutEvent = setTimeout(function() {
+ that.timeoutEvent = undefined;
+ callback();
+ }, timeout);
+ }
+};
+
+/**
+ * Clear a registered callback.
+ */
+SingleTimeout.prototype.clear = function() {
+ if (this.timeoutEvent !== undefined) {
+ clearTimeout(this.timeoutEvent);
+ this.timeoutEvent = undefined;
+ }
+};
+
+var failTimeout = new SingleTimeout();
+
+async function plInit() {
+ if (running) {
+ return;
+ }
+ running = true;
+
+ cycle = 0;
+ pageCycle = 1;
+
+ try {
+ /*
+ * Desktop firefox:
+ * non-chrome talos runs - tp-cmdline will create and load pageloader
+ * into the main window of the app which displays and tests content.
+ * chrome talos runs - tp-cmdline does the same however pageloader
+ * creates a new chromed browser window below for content.
+ */
+
+ var manifestURI = Services.prefs.getCharPref("talos.tpmanifest", null);
+ if (manifestURI.length == null) {
+ dumpLine("tp abort: talos.tpmanifest browser pref is not set");
+ plStop(true);
+ }
+
+ NUM_CYCLES = Services.prefs.getIntPref("talos.tpcycles", 1);
+ numPageCycles = Services.prefs.getIntPref("talos.tppagecycles", 1);
+ timeout = Services.prefs.getIntPref("talos.tptimeout", -1);
+ useMozAfterPaint = Services.prefs.getBoolPref(
+ "talos.tpmozafterpaint",
+ false
+ );
+ useHero = Services.prefs.getBoolPref("talos.tphero", false);
+ useFNBPaint = Services.prefs.getBoolPref("talos.fnbpaint", false);
+ usePDFPaint = Services.prefs.getBoolPref("talos.pdfpaint", false);
+ loadNoCache = Services.prefs.getBoolPref("talos.tploadnocache", false);
+ scrollTest = Services.prefs.getBoolPref("talos.tpscrolltest", false);
+ useBrowserChrome = Services.prefs.getBoolPref("talos.tpchrome", false);
+ useA11y = Services.prefs.getBoolPref("talos.a11y", false);
+
+ // for pageloader tests the profiling info is found in an env variable
+ // because it is not available early enough to set it as a browser pref
+ if (Services.env.exists("TPPROFILINGINFO")) {
+ profilingInfo = Services.env.get("TPPROFILINGINFO");
+ if (profilingInfo !== null) {
+ TalosParentProfiler.initFromObject(JSON.parse(profilingInfo));
+ }
+ }
+
+ if (forceCC && !window.windowUtils.garbageCollect) {
+ forceCC = false;
+ }
+
+ var fileURI = Services.io.newURI(manifestURI);
+ pages = plLoadURLsFromURI(fileURI);
+
+ if (!pages) {
+ dumpLine("tp: could not load URLs, quitting");
+ plStop(true);
+ }
+
+ if (!pages.length) {
+ dumpLine("tp: no pages to test, quitting");
+ plStop(true);
+ }
+
+ pageUrls = pages.map(function(p) {
+ return p.url.spec.toString();
+ });
+ report = new Report();
+
+ pageIndex = 0;
+ if (profilingInfo) {
+ TalosParentProfiler.beginTest(getCurrentPageShortName());
+ }
+
+ // Create a new chromed browser window for content
+ var blank = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ blank.data = "about:blank";
+
+ let toolbars = "all";
+ if (!useBrowserChrome) {
+ toolbars = "titlebar,resizable";
+ }
+
+ browserWindow = Services.ww.openWindow(
+ null,
+ AppConstants.BROWSER_CHROME_URL,
+ "_blank",
+ `chrome,${toolbars},dialog=no,width=${winWidth},height=${winHeight}`,
+ blank
+ );
+
+ gPaintWindow = browserWindow;
+ // get our window out of the way
+ window.resizeTo(10, 10);
+
+ await new Promise(resolve => {
+ browserWindow.addEventListener("load", resolve, {
+ capture: true,
+ once: true,
+ });
+ });
+
+ // do this half a second after load, because we need to be
+ // able to resize the window and not have it get clobbered
+ // by the persisted values
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ browserWindow.resizeTo(winWidth, winHeight);
+ browserWindow.moveTo(0, 0);
+ browserWindow.focus();
+ content = browserWindow.gBrowser;
+
+ // Since bug 1261842, the initial browser is remote unless it attempts
+ // to browse to a URI that should be non-remote (landed at bug 1047603).
+ //
+ // However, when it loads a URI that requires a different remote type,
+ // we lose the load listener and the injected tpRecordTime.remote,
+ //
+ // This listener will listen for when one of these process switches has
+ // happened, and re-install these listeners and injected methods into
+ // the new browser tab.
+ //
+ // It also probably means that per test (or, in fact, per pageloader browser
+ // instance which adds the load listener and injects tpRecordTime), all the
+ // pages should be able to load in the same mode as the initial page - due
+ // to this reinitialization on the switch.
+ let tab = content.selectedTab;
+ tab.addEventListener("TabRemotenessChange", function(evt) {
+ loadFrameScripts(tab.linkedBrowser);
+ });
+ loadFrameScripts(tab.linkedBrowser);
+
+ // Ensure that any webextensions that need to do setup have a chance
+ // to do so. e.g. the 'tabswitch' talos test registers a about:tabswitch
+ // handler during initialization, and if we don't wait for that, then
+ // attempting to load that URL will result in an error and hang the
+ // test.
+ for (let extension of WebExtensionPolicy.getActiveExtensions()) {
+ await extension.readyPromise;
+ }
+ plLoadPage();
+ } catch (e) {
+ dumpLine("pageloader exception: " + e);
+ plStop(true);
+ }
+}
+
+function plPageFlags() {
+ return pages[pageIndex].flags;
+}
+
+var ContentListener = {
+ receiveMessage(message) {
+ switch (message.name) {
+ case "PageLoader:LoadEvent":
+ return plLoadHandlerMessage(message);
+ case "PageLoader:Error":
+ return plErrorMessage(message);
+ case "PageLoader:RecordTime":
+ return plRecordTimeMessage(message);
+ case "PageLoader:IdleCallbackSet":
+ return plIdleCallbackSet();
+ case "PageLoader:IdleCallbackReceived":
+ return plIdleCallbackReceived();
+ }
+ return undefined;
+ },
+};
+
+// load the current page, start timing
+var removeLastAddedMsgListener = null;
+function plLoadPage() {
+ if (profilingInfo) {
+ TalosParentProfiler.beginTest(
+ getCurrentPageShortName() + "_pagecycle_" + pageCycle
+ );
+ }
+
+ var pageName = pages[pageIndex].url.spec;
+
+ if (removeLastAddedMsgListener) {
+ removeLastAddedMsgListener();
+ removeLastAddedMsgListener = null;
+ }
+
+ let tab = content.selectedTab;
+ tab.addEventListener("TabRemotenessChange", evt => {
+ addMsgListeners(tab.linkedBrowser);
+ });
+ addMsgListeners(tab.linkedBrowser);
+
+ failTimeout.register(loadFail, timeout);
+ // record which page we are about to open
+ TalosParentProfiler.mark("Opening " + pages[pageIndex].url.pathQueryRef);
+
+ if (useFNBPaint) {
+ isFNBPaintPending = true;
+ }
+
+ if (usePDFPaint) {
+ isPDFPaintPending = true;
+ }
+
+ startAndLoadURI(pageName);
+}
+
+function addMsgListeners(browser) {
+ let mm = browser.messageManager;
+ // messages to watch for page load
+ mm.addMessageListener("PageLoader:LoadEvent", ContentListener);
+ mm.addMessageListener("PageLoader:RecordTime", ContentListener);
+ mm.addMessageListener("PageLoader:IdleCallbackSet", ContentListener);
+ mm.addMessageListener("PageLoader:IdleCallbackReceived", ContentListener);
+ mm.addMessageListener("PageLoader:Error", ContentListener);
+
+ removeLastAddedMsgListener = function() {
+ mm.removeMessageListener("PageLoader:LoadEvent", ContentListener);
+ mm.removeMessageListener("PageLoader:RecordTime", ContentListener);
+ mm.removeMessageListener("PageLoader:IdleCallbackSet", ContentListener);
+ mm.removeMessageListener(
+ "PageLoader:IdleCallbackReceived",
+ ContentListener
+ );
+ mm.removeMessageListener("PageLoader:Error", ContentListener);
+ };
+}
+
+function loadFrameScripts(browser) {
+ let mm = browser.messageManager;
+
+ // Load our frame scripts.
+ mm.loadFrameScript("chrome://pageloader/content/utils.js", false, true);
+
+ // pick the right load handler
+ if (useFNBPaint) {
+ mm.loadFrameScript(
+ "chrome://pageloader/content/lh_fnbpaint.js",
+ false,
+ true
+ );
+ } else if (useMozAfterPaint) {
+ mm.loadFrameScript("chrome://pageloader/content/lh_moz.js", false, true);
+ } else if (useHero) {
+ mm.loadFrameScript("chrome://pageloader/content/lh_hero.js", false, true);
+ } else if (usePDFPaint) {
+ mm.loadFrameScript(
+ "chrome://pageloader/content/lh_pdfpaint.js",
+ false,
+ true
+ );
+ } else {
+ mm.loadFrameScript("chrome://pageloader/content/lh_dummy.js", false, true);
+ }
+ mm.loadFrameScript("chrome://pageloader/content/talos-content.js", false);
+ mm.loadFrameScript(
+ "resource://talos-powers/TalosContentProfiler.js",
+ false,
+ true
+ );
+ mm.loadFrameScript("chrome://pageloader/content/tscroll.js", false, true);
+ mm.loadFrameScript("chrome://pageloader/content/Profiler.js", false, true);
+ if (useA11y) {
+ mm.loadFrameScript("chrome://pageloader/content/a11y.js", false, true);
+ }
+}
+
+function startAndLoadURI(pageName) {
+ if (!(plPageFlags() & TEST_DOES_OWN_TIMING)) {
+ // Resume the profiler because we're really measuring page load time.
+ // If the test is doing its own timing, it'll also need to do its own
+ // profiler pausing / resuming.
+ TalosParentProfiler.resume("Starting to load URI " + pageName);
+ }
+
+ start_time = window.performance.now();
+ if (loadNoCache) {
+ content.loadURI(pageName, {
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ flags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE,
+ });
+ } else {
+ content.loadURI(pageName, {
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+ }
+}
+
+function getTestName() {
+ // returns tp5n
+ var pageName = pages[pageIndex].url.spec;
+ let parts = pageName.split("/");
+ if (parts.length > 4) {
+ return parts[4];
+ }
+ return "pageloader";
+}
+
+function getCurrentPageShortName() {
+ // this is also used by gecko profiling for the profile
+ // file name; so ensure it is valid on Windows/Linux/OSX
+ var pageName = pages[pageIndex].url.spec;
+ let parts = pageName.split("/");
+ if (parts.length > 5) {
+ // Tear off the first parts and concatenate the rest into a name.
+ let remainingParts = parts.slice(5);
+ let remainingAsString = remainingParts.join("_");
+ if (remainingAsString.includes("?")) {
+ // page name is something like 'tpaint.html?auto=1'
+ remainingAsString = remainingAsString.split("?")[0];
+ }
+ return remainingAsString;
+ }
+ return "page_" + pageIndex;
+}
+
+function loadFail() {
+ var pageName = pages[pageIndex].url.spec;
+ numRetries++;
+
+ if (numRetries >= maxRetries) {
+ dumpLine("__FAILTimeout in " + getTestName() + "__FAIL");
+ dumpLine(
+ "__FAILTimeout (" +
+ numRetries +
+ "/" +
+ maxRetries +
+ ") exceeded on " +
+ pageName +
+ "__FAIL"
+ );
+ TalosParentProfiler.finishTest().then(() => {
+ plStop(true);
+ });
+ } else {
+ dumpLine(
+ "__WARNTimeout (" +
+ numRetries +
+ "/" +
+ maxRetries +
+ ") exceeded on " +
+ pageName +
+ "__WARN"
+ );
+ // TODO: make this a cleaner cleanup
+ pageCycle--;
+ content.removeEventListener("load", plLoadHandler, true);
+ content.removeEventListener("load", plLoadHandlerCapturing, true);
+ content.removeEventListener("MozAfterPaint", plPaintedCapturing, true);
+ content.removeEventListener("MozAfterPaint", plPainted, true);
+ gPaintWindow.removeEventListener("MozAfterPaint", plPaintedCapturing, true);
+ gPaintWindow.removeEventListener("MozAfterPaint", plPainted, true);
+ removeLastAddedMsgListener = null;
+ gPaintListener = false;
+
+ // TODO: consider adding a tab and removing the old tab?!?
+ setTimeout(plLoadPage, delay);
+ }
+}
+
+var plNextPage = async function() {
+ var doNextPage = false;
+
+ // ensure we've receive idle-callback before proceeding
+ if (isIdleCallbackPending) {
+ dumpLine("Waiting for idle-callback");
+ await waitForIdleCallback();
+ }
+
+ if (useFNBPaint) {
+ // don't move to next page until we've received fnbpaint
+ if (isFNBPaintPending) {
+ dumpLine("Waiting for fnbpaint");
+ await waitForFNBPaint();
+ }
+ }
+
+ if (usePDFPaint) {
+ // don't move to next page until we've received pdfpaint
+ if (isPDFPaintPending) {
+ dumpLine("Waiting for pdfpaint");
+ await waitForPDFPaint();
+ }
+ }
+
+ if (profilingInfo) {
+ await TalosParentProfiler.finishTest();
+ }
+
+ if (pageCycle < numPageCycles) {
+ pageCycle++;
+ doNextPage = true;
+ } else if (pageIndex < pages.length - 1) {
+ pageIndex++;
+ recordedName = null;
+ pageCycle = 1;
+ doNextPage = true;
+ }
+
+ if (doNextPage) {
+ if (forceCC) {
+ var tccstart = window.performance.now();
+ window.windowUtils.garbageCollect();
+ var tccend = window.performance.now();
+ report.recordCCTime(tccend - tccstart);
+
+ // Now asynchronously trigger GC / CC in the content process
+ await forceContentGC();
+ }
+
+ setTimeout(plLoadPage, delay);
+ } else {
+ plStop(false);
+ }
+};
+
+function waitForIdleCallback() {
+ return new Promise(resolve => {
+ function checkForIdleCallback() {
+ if (!isIdleCallbackPending) {
+ resolve();
+ } else {
+ setTimeout(checkForIdleCallback, 5);
+ }
+ }
+ checkForIdleCallback();
+ });
+}
+
+function plIdleCallbackSet() {
+ if (!scrollTest) {
+ isIdleCallbackPending = true;
+ }
+}
+
+function plIdleCallbackReceived() {
+ isIdleCallbackPending = false;
+}
+
+function waitForFNBPaint() {
+ return new Promise(resolve => {
+ function checkForFNBPaint() {
+ if (!isFNBPaintPending) {
+ resolve();
+ } else {
+ setTimeout(checkForFNBPaint, 200);
+ }
+ }
+ checkForFNBPaint();
+ });
+}
+
+function waitForPDFPaint() {
+ return new Promise(resolve => {
+ function checkForPDFPaint() {
+ if (!isPDFPaintPending) {
+ resolve();
+ } else {
+ setTimeout(checkForPDFPaint, 200);
+ }
+ }
+ checkForPDFPaint();
+ });
+}
+
+function forceContentGC() {
+ return new Promise(resolve => {
+ let mm = browserWindow.gBrowser.selectedBrowser.messageManager;
+ mm.addMessageListener("Talos:ForceGC:OK", function onTalosContentForceGC(
+ msg
+ ) {
+ mm.removeMessageListener("Talos:ForceGC:OK", onTalosContentForceGC);
+ resolve();
+ });
+ mm.sendAsyncMessage("Talos:ForceGC");
+ });
+}
+
+function plRecordTime(time) {
+ var pageName = pages[pageIndex].url.spec;
+ var i = pageIndex;
+ if (i < pages.length - 1) {
+ i++;
+ } else {
+ i = 0;
+ }
+ var nextName = pages[i].url.spec;
+ if (!recordedName) {
+ // when doing base vs ref type of test, add pre 'base' or 'ref' to reported page name;
+ // this is necessary so that if multiple subtests use same reference page, results for
+ // each ref page run will be kept separate for each base vs ref run, and not grouped
+ // into just one set of results values for everytime that reference page was loaded
+ if (baseVsRef) {
+ recordedName = pages[pageIndex].pre + pageUrls[pageIndex];
+ } else {
+ recordedName = pageUrls[pageIndex];
+ }
+ }
+ if (typeof time == "string") {
+ var times = time.split(",");
+ var names = recordedName.split(",");
+ for (var t = 0; t < times.length; t++) {
+ if (names.length == 1) {
+ report.recordTime(names, times[t]);
+ } else {
+ report.recordTime(names[t], times[t]);
+ }
+ }
+ } else {
+ report.recordTime(recordedName, time);
+ }
+ dumpLine(
+ "Cycle " +
+ (cycle + 1) +
+ "(" +
+ pageCycle +
+ "): loaded " +
+ pageName +
+ " (next: " +
+ nextName +
+ ")"
+ );
+}
+
+function plLoadHandlerCapturing(evt) {
+ // make sure we pick up the right load event
+ if (evt.type != "load" || evt.originalTarget.defaultView.frameElement) {
+ return;
+ }
+
+ // set the tpRecordTime function (called from test pages we load) to store a global time.
+ content.contentWindow.wrappedJSObject.tpRecordTime = function(
+ time,
+ startTime,
+ testName
+ ) {
+ gTime = time;
+ gStartTime = startTime;
+ recordedName = testName;
+ setTimeout(plWaitForPaintingCapturing, 0);
+ };
+
+ content.contentWindow.wrappedJSObject.plGarbageCollect = function() {
+ window.windowUtils.garbageCollect();
+ };
+
+ content.removeEventListener("load", plLoadHandlerCapturing, true);
+
+ setTimeout(plWaitForPaintingCapturing, 0);
+}
+
+// Shim function this is really defined in tscroll.js
+function sendScroll() {
+ const SCROLL_TEST_STEP_PX = 10;
+ const SCROLL_TEST_NUM_STEPS = 100;
+ // The page doesn't really use tpRecordTime. Instead, we trigger the scroll test,
+ // and the scroll test will call tpRecordTime which will take us to the next page
+ let details = {
+ target: "content",
+ stepSize: SCROLL_TEST_STEP_PX,
+ opt_numSteps: SCROLL_TEST_NUM_STEPS,
+ };
+ let mm = content.selectedBrowser.messageManager;
+ mm.sendAsyncMessage("PageLoader:ScrollTest", { details });
+}
+
+function plWaitForPaintingCapturing() {
+ if (gPaintListener) {
+ return;
+ }
+
+ var utils = gPaintWindow.windowUtils;
+
+ if (utils.isMozAfterPaintPending && useMozAfterPaint) {
+ if (!gPaintListener) {
+ gPaintWindow.addEventListener("MozAfterPaint", plPaintedCapturing, true);
+ }
+ gPaintListener = true;
+ return;
+ }
+
+ _loadHandlerCapturing();
+}
+
+function plPaintedCapturing() {
+ gPaintWindow.removeEventListener("MozAfterPaint", plPaintedCapturing, true);
+ gPaintListener = false;
+ _loadHandlerCapturing();
+}
+
+function _loadHandlerCapturing() {
+ failTimeout.clear();
+
+ if (!(plPageFlags() & TEST_DOES_OWN_TIMING)) {
+ dumpLine(
+ "tp: Capturing onload handler used with page that doesn't do its own timing?"
+ );
+ plStop(true);
+ }
+
+ if (useMozAfterPaint) {
+ if (gStartTime != null && gStartTime >= 0) {
+ gTime =
+ window.performance.timing.navigationStart +
+ window.performance.now() -
+ gStartTime;
+ gStartTime = -1;
+ }
+ }
+
+ if (gTime !== -1) {
+ plRecordTime(gTime);
+ TalosParentProfiler.mark("Talos - capturing load handler fired");
+ TalosParentProfiler.pause();
+ gTime = -1;
+ recordedName = null;
+ setTimeout(plNextPage, delay);
+ }
+}
+
+// the onload handler
+function plLoadHandler(evt) {
+ // make sure we pick up the right load event
+ if (evt.type != "load" || evt.originalTarget.defaultView.frameElement) {
+ return;
+ }
+
+ content.removeEventListener("load", plLoadHandler, true);
+ setTimeout(waitForPainted, 0);
+}
+
+// This is called after we have received a load event, now we wait for painted
+function waitForPainted() {
+ var utils = gPaintWindow.windowUtils;
+
+ if (!utils.isMozAfterPaintPending || !useMozAfterPaint) {
+ _loadHandler();
+ return;
+ }
+
+ if (!gPaintListener) {
+ gPaintWindow.addEventListener("MozAfterPaint", plPainted, true);
+ }
+ gPaintListener = true;
+}
+
+function plPainted() {
+ gPaintWindow.removeEventListener("MozAfterPaint", plPainted, true);
+ gPaintListener = false;
+ _loadHandler();
+}
+
+function _loadHandler(paint_time = 0) {
+ failTimeout.clear();
+ var end_time = 0;
+
+ if (paint_time !== 0) {
+ // window.performance.timing.timeToNonBlankPaint is a timestamp
+ // this may have a value for hero element (also a timestamp)
+
+ let minDate = new Date("2001");
+
+ if (paint_time < minDate) {
+ //paint_time is a performance.now() value
+ end_time = paint_time;
+ } else {
+ //paint_time is a UNIX timestamp
+ end_time = paint_time - window.performance.timing.navigationStart;
+ }
+ } else {
+ end_time = window.performance.now();
+ }
+
+ var duration;
+ if (usePDFPaint) {
+ // PDF paint uses performance.now(), so the time does not need to be
+ // adjusted from the start time.
+ duration = end_time;
+ } else {
+ duration = end_time - start_time;
+ }
+ TalosParentProfiler.pause("Bubbling load handler fired.");
+
+ // does this page want to do its own timing?
+ // if so, we shouldn't be here
+ if (plPageFlags() & TEST_DOES_OWN_TIMING) {
+ dumpLine(
+ "tp: Bubbling onload handler used with page that does its own timing?"
+ );
+ plStop(true);
+ }
+
+ plRecordTime(duration);
+ plNextPage();
+}
+
+// the core handler
+function plLoadHandlerMessage(message) {
+ let paint_time = 0;
+ // XXX message.json.name contains the name
+ // of the load handler, so in future versions
+ // we can record several times per load.
+ if (message.json.time !== undefined) {
+ paint_time = message.json.time;
+ if (message.json.name == "fnbpaint") {
+ // we've received fnbpaint; no longer pending for this current pageload
+ isFNBPaintPending = false;
+ } else if (message.json.name == "pdfpaint") {
+ isPDFPaintPending = false;
+ }
+ }
+
+ failTimeout.clear();
+
+ if (plPageFlags() & EXECUTE_SCROLL_TEST) {
+ // Let the page settle down after its load event, then execute the scroll test.
+ setTimeout(sendScroll, 500);
+ } else if (plPageFlags() & TEST_DOES_OWN_TIMING) {
+ var time;
+
+ if (typeof gStartTime != "number") {
+ gStartTime = Date.parse(gStartTime);
+ }
+ if (gTime !== -1) {
+ if (useMozAfterPaint && gStartTime >= 0) {
+ time = window.performance.now() - gStartTime;
+ gStartTime = -1;
+ } else if (!useMozAfterPaint) {
+ time = gTime;
+ }
+ gTime = -1;
+ }
+
+ if (time !== undefined) {
+ plRecordTime(time);
+ plNextPage();
+ }
+ } else {
+ _loadHandler(paint_time);
+ }
+}
+
+// the record time handler
+function plRecordTimeMessage(message) {
+ gTime = message.json.time;
+ gStartTime = message.json.startTime;
+ recordedName = message.json.testName;
+
+ if (useMozAfterPaint) {
+ gStartTime = message.json.startTime;
+ }
+ _loadHandlerCapturing();
+}
+
+// error
+function plErrorMessage(message) {
+ if (message.json.msg) {
+ dumpLine(message.json.msg);
+ }
+ plStop(true);
+}
+
+function plStop(force) {
+ plStopAll(force);
+}
+
+function plStopAll(force) {
+ try {
+ if (!force) {
+ pageIndex = 0;
+ pageCycle = 1;
+ if (cycle < NUM_CYCLES - 1) {
+ cycle++;
+ recordedName = null;
+ setTimeout(plLoadPage, delay);
+ return;
+ }
+
+ /* output report */
+ dumpLine(report.getReport());
+ dumpLine(report.getReportSummary());
+ }
+ } catch (e) {
+ dumpLine(e);
+ }
+
+ if (content) {
+ content.removeEventListener("load", plLoadHandlerCapturing, true);
+ content.removeEventListener("load", plLoadHandler, true);
+
+ if (useMozAfterPaint) {
+ content.removeEventListener("MozAfterPaint", plPaintedCapturing, true);
+ content.removeEventListener("MozAfterPaint", plPainted, true);
+ }
+
+ let mm = content.selectedBrowser.messageManager;
+ mm.removeMessageListener("PageLoader:LoadEvent", ContentListener);
+ mm.removeMessageListener("PageLoader:RecordTime", ContentListener);
+ mm.removeMessageListener("PageLoader:Error", ContentListener);
+
+ if (isIdleCallbackPending) {
+ mm.removeMessageListener("PageLoader:IdleCallbackSet", ContentListener);
+ mm.removeMessageListener(
+ "PageLoader:IdleCallbackReceived",
+ ContentListener
+ );
+ }
+ mm.loadFrameScript(
+ "data:,removeEventListener('load', _contentLoadHandler, true);",
+ false,
+ true
+ );
+ }
+
+ if (MozillaFileLogger && MozillaFileLogger._foStream) {
+ MozillaFileLogger.close();
+ }
+
+ goQuitApplication();
+}
+
+/* Returns array */
+function plLoadURLsFromURI(manifestUri) {
+ var fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+
+ var uriFile = manifestUri.QueryInterface(Ci.nsIFileURL);
+
+ if (uriFile.file.isFile() === false) {
+ dumpLine("tp: invalid file: %s" % uriFile.file);
+ return null;
+ }
+
+ try {
+ fstream.init(uriFile.file, -1, 0, 0);
+ } catch (ex) {
+ dumpLine("tp: the file %s doesn't exist" % uriFile.file);
+ return null;
+ }
+
+ var lstream = fstream.QueryInterface(Ci.nsILineInputStream);
+
+ var url_array = [];
+
+ var lineNo = 0;
+ var line = { value: null };
+ var baseVsRefIndex = 0;
+ var more;
+ do {
+ lineNo++;
+ more = lstream.readLine(line);
+ var s = line.value;
+
+ // strip comments (only leading ones)
+ s = s.replace(/^#.*/, "");
+
+ // strip leading and trailing whitespace
+ s = s.replace(/^\s*/, "").replace(/\s*$/, "");
+
+ if (!s) {
+ continue;
+ }
+
+ var flags = 0;
+ var urlspec = s;
+ baseVsRefIndex += 1;
+
+ // split on whitespace, and figure out if we have any flags
+ var items = s.split(/\s+/);
+ if (items[0] == "include") {
+ if (items.length != 2) {
+ dumpLine(
+ "tp: Error on line " +
+ lineNo +
+ " in " +
+ manifestUri.spec +
+ ": include must be followed by the manifest to include!"
+ );
+ return null;
+ }
+
+ var subManifest = Services.io.newURI(items[1], null, manifestUri);
+ if (subManifest == null) {
+ dumpLine(
+ "tp: invalid URI on line " +
+ manifestUri.spec +
+ ":" +
+ lineNo +
+ " : '" +
+ line.value +
+ "'"
+ );
+ return null;
+ }
+
+ var subItems = plLoadURLsFromURI(subManifest);
+ if (subItems == null) {
+ return null;
+ }
+ url_array = url_array.concat(subItems);
+ } else {
+ // For scrollTest flag, we accept "normal" pages but treat them as TEST_DOES_OWN_TIMING
+ // together with EXECUTE_SCROLL_TEST which makes us run the scroll test on load.
+ // We do this by artificially "injecting" the TEST_DOES_OWN_TIMING flag ("%") to the item
+ // and then let the default flow for this flag run without further modifications
+ // (other than calling the scroll test once the page is loaded).
+ // Note that if we have the scrollTest flag but the item already has "%", then we do
+ // nothing (the scroll test will not execute, and the page will report with its
+ // own tpRecordTime and not the one from the scroll test).
+ if (scrollTest && items.length == 1) {
+ // scroll enabled and no "%"
+ items.unshift("%");
+ flags |= EXECUTE_SCROLL_TEST;
+ }
+
+ if (items.length == 2) {
+ if (items[0].includes("%")) {
+ flags |= TEST_DOES_OWN_TIMING;
+ }
+
+ urlspec = items[1];
+ } else if (items.length == 3) {
+ // base vs ref type of talos test
+ // expect each manifest line to be in the format of:
+ // & http://localhost/tests/perf-reftest/base-page.html, http://localhost/tests/perf-reftest/reference-page.html
+ // test will run with the base page, then with the reference page; and ultimately the actual test results will
+ // be the comparison values of those two pages; more than one line will result in base vs ref subtests
+ if (items[0].includes("&")) {
+ baseVsRef = true;
+ flags |= TEST_DOES_OWN_TIMING;
+ // for the base, must remove the comma on the end of the actual url
+ var urlspecBase = items[1].slice(0, -1);
+ var urlspecRef = items[2];
+ } else {
+ dumpLine(
+ "tp: Error on line " +
+ lineNo +
+ " in " +
+ manifestUri.spec +
+ ": unknown manifest format!"
+ );
+ return null;
+ }
+ } else if (items.length != 1) {
+ dumpLine(
+ "tp: Error on line " +
+ lineNo +
+ " in " +
+ manifestUri.spec +
+ ": whitespace must be %-escaped!"
+ );
+ return null;
+ }
+
+ var url;
+
+ if (!baseVsRef) {
+ url = Services.io.newURI(urlspec, null, manifestUri);
+
+ if (pageFilterRegexp && !pageFilterRegexp.test(url.spec)) {
+ continue;
+ }
+
+ url_array.push({ url, flags });
+ } else {
+ // base vs ref type of talos test
+ // we add a 'pre' prefix here indicating that this particular page is a base page or a reference
+ // page; later on this 'pre' is used when recording the actual time value/result; because in
+ // the results we use the url as the results key; but we might use the same test page as a reference
+ // page in the same test suite, so we need to add a prefix so this results key is always unique
+ url = Services.io.newURI(urlspecBase, null, manifestUri);
+ if (pageFilterRegexp && !pageFilterRegexp.test(url.spec)) {
+ continue;
+ }
+ var pre = "base_page_" + baseVsRefIndex + "_";
+ url_array.push({ url, flags, pre });
+
+ url = Services.io.newURI(urlspecRef, null, manifestUri);
+ if (pageFilterRegexp && !pageFilterRegexp.test(url.spec)) {
+ continue;
+ }
+ pre = "ref_page_" + baseVsRefIndex + "_";
+ url_array.push({ url, flags, pre });
+ }
+ }
+ } while (more);
+
+ return url_array;
+}
+
+function dumpLine(str) {
+ if (MozillaFileLogger && MozillaFileLogger._foStream) {
+ MozillaFileLogger.log(str + "\n");
+ }
+ dump(str);
+ dump("\n");
+}