summaryrefslogtreecommitdiffstats
path: root/testing/talos/talos/pageloader/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'testing/talos/talos/pageloader/chrome')
-rw-r--r--testing/talos/talos/pageloader/chrome/MozillaFileLogger.js95
-rw-r--r--testing/talos/talos/pageloader/chrome/Profiler.js206
-rw-r--r--testing/talos/talos/pageloader/chrome/a11y.js66
-rw-r--r--testing/talos/talos/pageloader/chrome/lh_dummy.js11
-rw-r--r--testing/talos/talos/pageloader/chrome/lh_fnbpaint.js45
-rw-r--r--testing/talos/talos/pageloader/chrome/lh_hero.js49
-rw-r--r--testing/talos/talos/pageloader/chrome/lh_moz.js28
-rw-r--r--testing/talos/talos/pageloader/chrome/lh_pdfpaint.js26
-rw-r--r--testing/talos/talos/pageloader/chrome/pageloader.js1111
-rw-r--r--testing/talos/talos/pageloader/chrome/pageloader.xhtml56
-rw-r--r--testing/talos/talos/pageloader/chrome/quit.js72
-rw-r--r--testing/talos/talos/pageloader/chrome/report.js195
-rw-r--r--testing/talos/talos/pageloader/chrome/talos-content.js24
-rw-r--r--testing/talos/talos/pageloader/chrome/tscroll.js324
-rw-r--r--testing/talos/talos/pageloader/chrome/utils.js34
15 files changed, 2342 insertions, 0 deletions
diff --git a/testing/talos/talos/pageloader/chrome/MozillaFileLogger.js b/testing/talos/talos/pageloader/chrome/MozillaFileLogger.js
new file mode 100644
index 0000000000..65338ee19e
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/MozillaFileLogger.js
@@ -0,0 +1,95 @@
+/* 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/. */
+
+/**
+ * MozillaFileLogger, a log listener that can write to a local file.
+ */
+
+// double logging to account for normal mode and ipc mode (mobile_profile only)
+// Ideally we would remove the dump() and just do ipc logging
+function dumpLog(msg) {
+ dump(msg);
+ MozillaFileLogger.log(msg);
+}
+
+const FOSTREAM_CID = "@mozilla.org/network/file-output-stream;1";
+const LF_CID = "@mozilla.org/file/local;1";
+
+// File status flags. It is a bitwise OR of the following bit flags.
+// Only one of the first three flags below may be used.
+const PR_READ_ONLY = 0x01; // Open for reading only.
+const PR_WRITE_ONLY = 0x02; // Open for writing only.
+const PR_READ_WRITE = 0x04; // Open for reading and writing.
+
+// If the file does not exist, the file is created.
+// If the file exists, this flag has no effect.
+const PR_CREATE_FILE = 0x08;
+
+// The file pointer is set to the end of the file prior to each write.
+const PR_APPEND = 0x10;
+
+// If the file exists, its length is truncated to 0.
+const PR_TRUNCATE = 0x20;
+
+// If set, each write will wait for both the file data
+// and file status to be physically updated.
+const PR_SYNC = 0x40;
+
+// If the file does not exist, the file is created. If the file already
+// exists, no action and NULL is returned.
+const PR_EXCL = 0x80;
+
+/** Init the file logger with the absolute path to the file.
+ It will create and append if the file already exists **/
+var MozillaFileLogger = {};
+
+MozillaFileLogger.init = function(path) {
+ MozillaFileLogger._file = Cc[LF_CID].createInstance(Ci.nsIFile);
+ MozillaFileLogger._file.initWithPath(path);
+ MozillaFileLogger._foStream = Cc[FOSTREAM_CID].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ MozillaFileLogger._foStream.init(
+ this._file,
+ PR_WRITE_ONLY | PR_CREATE_FILE | PR_APPEND,
+ 0o664,
+ 0
+ );
+};
+
+MozillaFileLogger.getLogCallback = function() {
+ return function(msg) {
+ var data = msg.num + " " + msg.level + " " + msg.info.join(" ") + "\n";
+ if (MozillaFileLogger._foStream) {
+ MozillaFileLogger._foStream.write(data, data.length);
+ }
+
+ if (data.includes("SimpleTest FINISH")) {
+ MozillaFileLogger.close();
+ }
+ };
+};
+
+// This is only used from chrome space by the reftest harness
+MozillaFileLogger.log = function(msg) {
+ try {
+ if (MozillaFileLogger._foStream) {
+ MozillaFileLogger._foStream.write(msg, msg.length);
+ }
+ } catch (ex) {}
+};
+
+MozillaFileLogger.close = function() {
+ if (MozillaFileLogger._foStream) {
+ MozillaFileLogger._foStream.close();
+ }
+
+ MozillaFileLogger._foStream = null;
+ MozillaFileLogger._file = null;
+};
+
+try {
+ var filename = Services.prefs.getCharPref("talos.logfile");
+ MozillaFileLogger.init(filename);
+} catch (ex) {} // pref does not exist, return empty string
diff --git a/testing/talos/talos/pageloader/chrome/Profiler.js b/testing/talos/talos/pageloader/chrome/Profiler.js
new file mode 100644
index 0000000000..a652c10c04
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/Profiler.js
@@ -0,0 +1,206 @@
+/* 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/. */
+
+/* eslint-env mozilla/frame-script */
+
+// - NOTE: This file is duplicated verbatim at:
+// - talos/pageloader/chrome/Profiler.js
+// - talos/tests/tart/addon/content/Profiler.js
+// - talos/startup_test/tresize/addon/content/Profiler.js
+//
+// - Please keep these copies in sync.
+// - Please make sure your changes apply cleanly to all use cases.
+
+// Finer grained profiler control
+//
+// Use this object to pause and resume the profiler so that it only profiles the
+// relevant parts of our tests.
+var Profiler;
+
+(function() {
+ var _profiler;
+
+ // If this script is loaded in a framescript context, there won't be a
+ // document object, so just use a fallback value in that case.
+ var test_name = this.document ? this.document.location.pathname : "unknown";
+
+ // Whether Profiler has been initialized. Until that happens, most calls
+ // will be ignored.
+ var enabled = false;
+
+ // The subtest name that beginTest() was called with.
+ var currentTest = "";
+
+ // Profiling settings.
+ var profiler_interval,
+ profiler_entries,
+ profiler_threadsArray,
+ profiler_featuresArray,
+ profiler_dir;
+
+ try {
+ // eslint-disable-next-line mozilla/use-services
+ _profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+ } catch (ex) {
+ (typeof dumpLog == "undefined" ? dump : dumpLog)(ex + "\n");
+ }
+
+ // Parses an url query string into a JS object.
+ function searchToObject(locationSearch) {
+ var pairs = locationSearch.substring(1).split("&");
+ var result = {};
+
+ for (var i in pairs) {
+ if (pairs[i] !== "") {
+ var pair = pairs[i].split("=");
+ result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || "");
+ }
+ }
+
+ return result;
+ }
+
+ Profiler = {
+ /**
+ * Initialize the profiler using profiler settings supplied in a JS object.
+ * The following properties on the object are respected:
+ * - gecko_profile_interval
+ * - gecko_profile_entries
+ * - gecko_profile_features
+ * - gecko_profile_threads
+ * - gecko_profile_dir
+ */
+ initFromObject: function Profiler__initFromObject(obj) {
+ if (
+ obj &&
+ "gecko_profile_dir" in obj &&
+ typeof obj.gecko_profile_dir == "string" &&
+ "gecko_profile_interval" in obj &&
+ Number.isFinite(obj.gecko_profile_interval * 1) &&
+ "gecko_profile_entries" in obj &&
+ Number.isFinite(obj.gecko_profile_entries * 1) &&
+ "gecko_profile_features" in obj &&
+ typeof obj.gecko_profile_features == "string" &&
+ "gecko_profile_threads" in obj &&
+ typeof obj.gecko_profile_threads == "string"
+ ) {
+ profiler_interval = obj.gecko_profile_interval;
+ profiler_entries = obj.gecko_profile_entries;
+ profiler_featuresArray = obj.gecko_profile_features.split(",");
+ profiler_threadsArray = obj.gecko_profile_threads.split(",");
+ profiler_dir = obj.gecko_profile_dir;
+ enabled = true;
+ }
+ },
+ initFromURLQueryParams: function Profiler__initFromURLQueryParams(
+ locationSearch
+ ) {
+ this.initFromObject(searchToObject(locationSearch));
+ },
+ beginTest: function Profiler__beginTest(testName) {
+ currentTest = testName;
+ if (_profiler && enabled) {
+ _profiler.StartProfiler(
+ profiler_entries,
+ profiler_interval,
+ profiler_featuresArray,
+ profiler_threadsArray
+ );
+ _profiler.PauseSampling();
+ }
+ },
+ finishTest: function Profiler__finishTest() {
+ if (_profiler && enabled) {
+ _profiler.Pause();
+ _profiler.dumpProfileToFile(
+ profiler_dir + "/" + currentTest + ".profile"
+ );
+ _profiler.StopProfiler();
+ }
+ },
+ finishTestAsync: function Profiler__finishTest() {
+ if (!(_profiler && enabled)) {
+ return undefined;
+ }
+ return new Promise((resolve, reject) => {
+ Services.profiler.getProfileDataAsync().then(
+ profile => {
+ let profileFile = profiler_dir + "/" + currentTest + ".profile";
+
+ const { NetUtil } = ChromeUtils.import(
+ "resource://gre/modules/NetUtil.jsm"
+ );
+ const { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+ );
+
+ var file = Cc["@mozilla.org/file/local;1"].createInstance(
+ Ci.nsIFile
+ );
+ file.initWithPath(profileFile);
+
+ var ostream = FileUtils.openSafeFileOutputStream(file);
+
+ var converter = Cc[
+ "@mozilla.org/intl/scriptableunicodeconverter"
+ ].createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ var istream = converter.convertToInputStream(
+ JSON.stringify(profile)
+ );
+
+ // The last argument (the callback) is optional.
+ NetUtil.asyncCopy(istream, ostream, function(status) {
+ if (!Components.isSuccessCode(status)) {
+ reject();
+ return;
+ }
+
+ resolve();
+ });
+ },
+ error => {
+ console.error("Failed to gather profile: " + error);
+ reject();
+ }
+ );
+ });
+ },
+ finishStartupProfiling: function Profiler__finishStartupProfiling() {
+ if (_profiler && enabled) {
+ _profiler.Pause();
+ _profiler.dumpProfileToFile(profiler_dir + "/startup.profile");
+ _profiler.StopProfiler();
+ }
+ },
+ resume: function Profiler__resume(name, explicit) {
+ if (_profiler) {
+ if (_profiler.ResumeSampling) {
+ _profiler.ResumeSampling();
+ }
+ ChromeUtils.addProfilerMarker(
+ explicit ? name : 'Start of test "' + (name || test_name) + '"',
+ { category: "Test" }
+ );
+ }
+ },
+ pause: function Profiler__pause(name, explicit) {
+ if (_profiler) {
+ ChromeUtils.addProfilerMarker(
+ explicit ? name : 'End of test "' + (name || test_name) + '"',
+ { category: "Test" }
+ );
+ _profiler.PauseSampling();
+ }
+ },
+ mark: function Profiler__mark(marker, explicit) {
+ if (_profiler) {
+ ChromeUtils.addProfilerMarker(
+ explicit ? marker : 'Profiler: "' + (marker || test_name) + '"',
+ { category: "Test" }
+ );
+ }
+ },
+ };
+})();
diff --git a/testing/talos/talos/pageloader/chrome/a11y.js b/testing/talos/talos/pageloader/chrome/a11y.js
new file mode 100644
index 0000000000..302ca0ed1f
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/a11y.js
@@ -0,0 +1,66 @@
+/* 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/. */
+
+/* eslint-env mozilla/frame-script */
+
+(function() {
+ let gAccService = 0;
+
+ function initAccessibility() {
+ if (!gAccService) {
+ var service = Cc["@mozilla.org/accessibilityService;1"];
+ if (service) {
+ // fails if build lacks accessibility module
+ gAccService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ }
+ }
+ return !!gAccService;
+ }
+
+ function getAccessible(aNode) {
+ try {
+ return gAccService.getAccessibleFor(aNode);
+ } catch (e) {}
+
+ return null;
+ }
+
+ function ensureAccessibleTreeForNode(aNode) {
+ var acc = getAccessible(aNode);
+
+ ensureAccessibleTreeForAccessible(acc);
+ }
+
+ function ensureAccessibleTreeForAccessible(aAccessible) {
+ var child = aAccessible.firstChild;
+ while (child) {
+ ensureAccessibleTreeForAccessible(child);
+ try {
+ child = child.nextSibling;
+ } catch (e) {
+ child = null;
+ }
+ }
+ }
+
+ // Walk accessible tree of the given identifier to ensure tree creation
+ function ensureAccessibleTreeForId(aID) {
+ var node = content.document.getElementById(aID);
+ if (!node) {
+ return;
+ }
+ ensureAccessibleTreeForNode(node);
+ }
+
+ addEventListener("DOMContentLoaded", e => {
+ Cu.exportFunction(initAccessibility, content, {
+ defineAs: "initAccessibility",
+ });
+ Cu.exportFunction(ensureAccessibleTreeForId, content, {
+ defineAs: "ensureAccessibleTreeForId",
+ });
+ });
+})();
diff --git a/testing/talos/talos/pageloader/chrome/lh_dummy.js b/testing/talos/talos/pageloader/chrome/lh_dummy.js
new file mode 100644
index 0000000000..134efd4ef1
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/lh_dummy.js
@@ -0,0 +1,11 @@
+/* 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/. */
+
+/* eslint-env mozilla/frame-script */
+
+function _dummy() {
+ sendAsyncMessage("PageLoader:LoadEvent", {});
+}
+
+addEventListener("load", contentLoadHandlerCallback(_dummy), true); // eslint-disable-line no-undef
diff --git a/testing/talos/talos/pageloader/chrome/lh_fnbpaint.js b/testing/talos/talos/pageloader/chrome/lh_fnbpaint.js
new file mode 100644
index 0000000000..5ef0066ad2
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/lh_fnbpaint.js
@@ -0,0 +1,45 @@
+/* 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/. */
+
+/* eslint-env mozilla/frame-script */
+
+var gErr =
+ "Abort: firstNonBlankPaint value is not available after loading the page";
+var gRetryCounter = 0;
+
+function _contentFNBPaintHandler() {
+ var x = content.window.performance.timing.timeToNonBlankPaint;
+ if (typeof x == "undefined") {
+ sendAsyncMessage("PageLoader:Error", { msg: gErr });
+ }
+ if (x > 0) {
+ dump("received fnbpaint value\n");
+ sendAsyncMessage("PageLoader:LoadEvent", { time: x, name: "fnbpaint" });
+ gRetryCounter = 0;
+ } else {
+ gRetryCounter += 1;
+ if (gRetryCounter <= 10) {
+ dump(
+ "\nfnbpaint is not yet available (0), retry number " +
+ gRetryCounter +
+ "...\n"
+ );
+ content.setTimeout(_contentFNBPaintHandler, 100);
+ } else {
+ dump(
+ "\nunable to get a value for fnbpaint after " +
+ gRetryCounter +
+ " retries\n"
+ );
+ sendAsyncMessage("PageLoader:Error", { msg: gErr });
+ }
+ }
+}
+
+addEventListener(
+ "load",
+ // eslint-disable-next-line no-undef
+ contentLoadHandlerCallback(_contentFNBPaintHandler),
+ true
+);
diff --git a/testing/talos/talos/pageloader/chrome/lh_hero.js b/testing/talos/talos/pageloader/chrome/lh_hero.js
new file mode 100644
index 0000000000..caef53f374
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/lh_hero.js
@@ -0,0 +1,49 @@
+/* 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/. */
+
+/* eslint-env mozilla/frame-script */
+
+function _contentHeroHandler(isload) {
+ var obs = null;
+ var el = content.window.document.querySelector("[elementtiming]");
+ if (el) {
+ function callback(entries, observer) {
+ entries.forEach(entry => {
+ sendAsyncMessage("PageLoader:LoadEvent", {
+ time: content.window.performance.now(),
+ name: "tphero",
+ });
+ obs.disconnect();
+ });
+ }
+ // we want the element 100% visible on the viewport
+ var options = { root: null, rootMargin: "0px", threshold: [1] };
+ try {
+ obs = new content.window.IntersectionObserver(callback, options);
+ obs.observe(el);
+ } catch (err) {
+ sendAsyncMessage("PageLoader:Error", { msg: err.message });
+ }
+ } else if (isload) {
+ // If the hero element is added from a settimeout handler, it might not run before 'load'
+ content.setTimeout(function() {
+ _contentHeroHandler(false);
+ }, 5000);
+ } else {
+ var err = "Could not find a tag with an elmenttiming attr on the page";
+ sendAsyncMessage("PageLoader:Error", { msg: err });
+ }
+ return obs;
+}
+
+function _contentHeroLoadHandler() {
+ _contentHeroHandler(true);
+}
+
+addEventListener(
+ "load",
+ // eslint-disable-next-line no-undef
+ contentLoadHandlerCallback(_contentHeroLoadHandler),
+ true
+);
diff --git a/testing/talos/talos/pageloader/chrome/lh_moz.js b/testing/talos/talos/pageloader/chrome/lh_moz.js
new file mode 100644
index 0000000000..46d4a74a7c
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/lh_moz.js
@@ -0,0 +1,28 @@
+/* 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/. */
+
+/* eslint-env mozilla/frame-script */
+
+function _contentPaintHandler() {
+ var utils = content.windowUtils;
+ if (utils.isMozAfterPaintPending) {
+ addEventListener(
+ "MozAfterPaint",
+ function afterpaint(e) {
+ removeEventListener("MozAfterPaint", afterpaint, true);
+ sendAsyncMessage("PageLoader:LoadEvent", {});
+ },
+ true
+ );
+ } else {
+ sendAsyncMessage("PageLoader:LoadEvent", {});
+ }
+}
+
+addEventListener(
+ "load",
+ // eslint-disable-next-line no-undef
+ contentLoadHandlerCallback(_contentPaintHandler),
+ true
+);
diff --git a/testing/talos/talos/pageloader/chrome/lh_pdfpaint.js b/testing/talos/talos/pageloader/chrome/lh_pdfpaint.js
new file mode 100644
index 0000000000..0f1eff6da9
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/lh_pdfpaint.js
@@ -0,0 +1,26 @@
+/* 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/. */
+
+/* eslint-env mozilla/frame-script */
+
+function _pdfPaintHandler() {
+ content.window.addEventListener(
+ "pagerendered",
+ e => {
+ if (e.detail.pageNumber !== 1) {
+ sendAsyncMessage("PageLoader:Error", {
+ msg: `Error: Expected page 1 got ${e.detail.pageNumber}`,
+ });
+ return;
+ }
+ sendAsyncMessage("PageLoader:LoadEvent", {
+ time: e.detail.timestamp,
+ name: "pdfpaint",
+ });
+ },
+ { once: true }
+ );
+}
+
+addEventListener("DOMContentLoaded", _pdfPaintHandler, true);
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");
+}
diff --git a/testing/talos/talos/pageloader/chrome/pageloader.xhtml b/testing/talos/talos/pageloader/chrome/pageloader.xhtml
new file mode 100644
index 0000000000..177502a57d
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/pageloader.xhtml
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+ - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ -
+ - The contents of this file are subject to the Mozilla Public License Version
+ - 1.1 (the "License"); you may not use this file except in compliance with
+ - the License. You may obtain a copy of the License at
+ - http://www.mozilla.org/MPL/
+ -
+ - Software distributed under the License is distributed on an "AS IS" basis,
+ - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ - for the specific language governing rights and limitations under the
+ - License.
+ -
+ - The Original Code is tp.
+ -
+ - The Initial Developer of the Original Code is the Mozilla Corporation.
+ - Portions created by the Initial Developer are Copyright (C) 2007
+ - the Initial Developer. All Rights Reserved.
+ -
+ - Contributor(s):
+ - Rob Helmer <rhelmer@mozilla.com>
+ - Vladimir Vukicevic <vladimir@mozilla.com>
+ -
+ - Alternatively, the contents of this file may be used under the terms of
+ - either the GNU General Public License Version 2 or later (the "GPL"), or
+ - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ - in which case the provisions of the GPL or the LGPL are applicable instead
+ - of those above. If you wish to allow use of your version of this file only
+ - under the terms of either the GPL or the LGPL, and not to allow others to
+ - use your version of this file under the terms of the MPL, indicate your
+ - decision by deleting the provisions above and replace them with the notice
+ - and other provisions required by the LGPL or the GPL. If you do not delete
+ - the provisions above, a recipient may use your version of this file under
+ - the terms of any one of the MPL, the GPL or the LGPL.
+ -
+ - ***** END LICENSE BLOCK ***** -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="pageloader"
+ screenX="0" screenY="0"
+ onload="plInit()">
+
+ <script type="application/x-javascript"
+ src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/x-javascript" src="MozillaFileLogger.js"></script>
+ <script type="application/x-javascript" src="report.js"></script>
+ <script type="application/x-javascript" src="pageloader.js"></script>
+ <script type="application/x-javascript" src="quit.js"></script>
+
+ <browser id="contentPageloader" src="about:blank"
+ type="content" flex="1"/>
+</window>
diff --git a/testing/talos/talos/pageloader/chrome/quit.js b/testing/talos/talos/pageloader/chrome/quit.js
new file mode 100644
index 0000000000..0261295c1c
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/quit.js
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is The Original Code is Mozilla Automated Testing Code
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s): Bob Clary <bob@bclary.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ From mozilla/toolkit/content
+ These files did not have a license
+*/
+
+/* import-globals-from pageloader.js */
+
+function canQuitApplication() {
+ try {
+ var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
+ Ci.nsISupportsPRBool
+ );
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested");
+
+ // Something aborted the quit process.
+ if (cancelQuit.data) {
+ return false;
+ }
+ } catch (ex) {}
+ return true;
+}
+
+function goQuitApplication() {
+ if (!canQuitApplication()) {
+ return false;
+ }
+
+ try {
+ Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
+ } catch (ex) {
+ throw new Error("goQuitApplication: " + ex);
+ }
+
+ return true;
+}
diff --git a/testing/talos/talos/pageloader/chrome/report.js b/testing/talos/talos/pageloader/chrome/report.js
new file mode 100644
index 0000000000..1827aed36b
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/report.js
@@ -0,0 +1,195 @@
+/* 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/. */
+
+// given an array of strings, finds the longest common prefix
+function findCommonPrefixLength(strs) {
+ if (strs.length < 2) {
+ // only one page in the manifest
+ // i.e. http://localhost/tests/perf-reftest/bloom-basic.html
+ var place = strs[0].lastIndexOf("/");
+ if (place < 0) {
+ place = 0;
+ }
+ return place;
+ }
+
+ var len = 0;
+ do {
+ var newlen = len + 1;
+ var newprefix = null;
+ var failed = false;
+ for (var i = 0; i < strs.length; i++) {
+ if (newlen > strs[i].length) {
+ failed = true;
+ break;
+ }
+
+ var s = strs[i].substr(0, newlen);
+ if (newprefix == null) {
+ newprefix = s;
+ } else if (newprefix != s) {
+ failed = true;
+ break;
+ }
+ }
+
+ if (failed) {
+ break;
+ }
+
+ len++;
+ } while (true);
+ return len;
+}
+
+// Constructor
+function Report() {
+ this.timeVals = {};
+ this.totalCCTime = 0;
+ this.showTotalCCTime = false;
+}
+
+Report.prototype.pageNames = function() {
+ var retval = [];
+ for (var page in this.timeVals) {
+ retval.push(page);
+ }
+ return retval;
+};
+
+Report.prototype.getReport = function() {
+ var report;
+ var pages = this.pageNames();
+ var prefixLen = findCommonPrefixLength(pages);
+
+ report = "__start_tp_report\n";
+ report += "_x_x_mozilla_page_load\n";
+ report += "_x_x_mozilla_page_load_details\n";
+ report += "|i|pagename|runs|\n";
+
+ for (var i = 0; i < pages.length; i++) {
+ // don't report any measurements that were reported for about:blank
+ // some tests (like about-preferences) use it as a dummy test page
+ if (pages[i] == "about:blank") {
+ continue;
+ }
+ report +=
+ "|" +
+ i +
+ ";" +
+ pages[i].substr(prefixLen) +
+ ";" +
+ this.timeVals[pages[i]].join(";") +
+ "\n";
+ }
+ report += "__end_tp_report\n";
+
+ if (this.showTotalCCTime) {
+ report += "__start_cc_report\n";
+ report += "_x_x_mozilla_cycle_collect," + this.totalCCTime + "\n";
+ report += "__end_cc_report\n";
+ }
+ var now = window.performance.now();
+ report += "__startTimestamp" + now + "__endTimestamp\n"; // timestamp for determning shutdown time, used by talos
+
+ return report;
+};
+
+Report.prototype.getReportSummary = function() {
+ function average(arr) {
+ var sum = 0;
+ for (var i in arr) {
+ sum += arr[i];
+ }
+ return sum / (arr.length || 1);
+ }
+
+ function median(arr) {
+ if (!arr.length) {
+ return 0;
+ } // As good indication for "not available" as any other value.
+
+ var sorted = arr.slice(0).sort();
+ var mid = Math.floor(arr.length / 2);
+
+ if (sorted.length % 2) {
+ return sorted[mid];
+ }
+
+ return average(sorted.slice(mid, mid + 2));
+ }
+
+ // We use sample stddev and not population stddev because
+ // well.. it's a sample and we can't collect all/infinite number of values.
+ function stddev(arr) {
+ if (arr.length <= 1) {
+ return 0;
+ }
+ var avg = average(arr);
+
+ var squareDiffArr = arr.map(function(v) {
+ return Math.pow(v - avg, 2);
+ });
+ var sum = squareDiffArr.reduce(function(a, b) {
+ return a + b;
+ });
+ var rv = Math.sqrt(sum / (arr.length - 1));
+ return rv;
+ }
+
+ var report = "";
+ var pages = this.pageNames();
+ var prefixLen = findCommonPrefixLength(pages);
+
+ report += "------- Summary: start -------\n";
+ report += "Number of tests: " + pages.length + "\n";
+
+ for (var i = 0; i < pages.length; i++) {
+ var results = this.timeVals[pages[i]].map(function(v) {
+ return Number(v);
+ });
+
+ report +=
+ "\n[#" +
+ i +
+ "] " +
+ pages[i].substr(prefixLen) +
+ " Cycles:" +
+ results.length +
+ " Average:" +
+ average(results).toFixed(2) +
+ " Median:" +
+ median(results).toFixed(2) +
+ " stddev:" +
+ stddev(results).toFixed(2) +
+ " (" +
+ ((100 * stddev(results)) / median(results)).toFixed(1) +
+ "%)" +
+ (results.length < 5
+ ? ""
+ : " stddev-sans-first:" + stddev(results.slice(1)).toFixed(2)) +
+ "\nValues: " +
+ results
+ .map(function(v) {
+ return v.toFixed(1);
+ })
+ .join(" ") +
+ "\n";
+ }
+ report += "-------- Summary: end --------\n";
+
+ return report;
+};
+
+Report.prototype.recordTime = function(pageName, ms) {
+ if (this.timeVals[pageName] == undefined) {
+ this.timeVals[pageName] = [];
+ }
+ this.timeVals[pageName].push(ms);
+};
+
+Report.prototype.recordCCTime = function(ms) {
+ this.totalCCTime += ms;
+ this.showTotalCCTime = true;
+};
diff --git a/testing/talos/talos/pageloader/chrome/talos-content.js b/testing/talos/talos/pageloader/chrome/talos-content.js
new file mode 100644
index 0000000000..ca21388c2c
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/talos-content.js
@@ -0,0 +1,24 @@
+/* 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/. */
+
+/* eslint-env mozilla/frame-script */
+
+const TalosContent = {
+ init() {
+ addMessageListener("Talos:ForceGC", this);
+ },
+
+ receiveMessage(msg) {
+ if (msg.name == "Talos:ForceGC") {
+ this.forceGC();
+ }
+ },
+
+ forceGC() {
+ content.windowUtils.garbageCollect();
+ sendAsyncMessage("Talos:ForceGC:OK");
+ },
+};
+
+TalosContent.init();
diff --git a/testing/talos/talos/pageloader/chrome/tscroll.js b/testing/talos/talos/pageloader/chrome/tscroll.js
new file mode 100644
index 0000000000..03e039b583
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/tscroll.js
@@ -0,0 +1,324 @@
+/* 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/. */
+
+/* eslint-env mozilla/frame-script */
+
+// Note: This file is used at both tscrollx and tp5o_scroll. With the former as
+// unprivileged code.
+// - Please make sure that any changes apply cleanly to all use cases.
+
+function testScroll(target, stepSize, opt_reportFunc, opt_numSteps) {
+ var win;
+ if (target == "content") {
+ target = content.wrappedJSObject;
+ win = content;
+ } else {
+ win = window;
+ }
+
+ var result = {
+ names: [],
+ values: [],
+ };
+ // We report multiple results, so we base the name on the path.
+ // Everything after '/tp5n/' if exists (for tp5o_scroll), or the file name at
+ // the path if non-empty (e.g. with tscrollx), or the last dir otherwise (e.g.
+ // 'mydir' for 'http://my.domain/dir1/mydir/').
+ var href = win.location.href;
+ var testBaseName =
+ href.split("/tp5n/")[1] ||
+ href.split("/").pop() ||
+ href.split("/").splice(-2, 1)[0] ||
+ "REALLY_WEIRD_URI";
+
+ // Verbatim copy from talos-powers/content/TalosPowersContent.js
+ // If the origin changes, this copy should be updated.
+ TalosPowersParent = {
+ replyId: 1,
+
+ // dispatch an event to the framescript and register the result/callback event
+ exec(commandName, arg, callback, opt_custom_window) {
+ let win = opt_custom_window || window;
+ let replyEvent = "TalosPowers:ParentExec:ReplyEvent:" + this.replyId++;
+ if (callback) {
+ win.addEventListener(
+ replyEvent,
+ function(e) {
+ callback(e.detail);
+ },
+ { once: true }
+ );
+ }
+ win.dispatchEvent(
+ new win.CustomEvent("TalosPowers:ParentExec:QueryEvent", {
+ bubbles: true,
+ detail: {
+ command: {
+ name: commandName,
+ data: arg,
+ },
+ listeningTo: replyEvent,
+ },
+ })
+ );
+ },
+ };
+ // End of code from talos-powers
+
+ var report;
+ /**
+ * Sets up the value of 'report' as a function for reporting the test result[s].
+ * Chooses between the "usual" tpRecordTime which the pageloader addon injects
+ * to pages, or a custom function in case we're a framescript which pageloader
+ * added to the tested page, or a debug tpRecordTime from talos-debug.js if
+ * running in a plain browser.
+ *
+ * @returns Promise
+ */
+ function P_setupReportFn() {
+ return new Promise(function(resolve) {
+ report = opt_reportFunc || win.tpRecordTime;
+ if (report == "PageLoader:RecordTime") {
+ report = function(duration, start, name) {
+ var msg = { time: duration, startTime: start, testName: name };
+ sendAsyncMessage("PageLoader:RecordTime", msg);
+ };
+ resolve();
+ return;
+ }
+
+ // Not part of the test and does nothing if we're within talos.
+ // Provides an alternative tpRecordTime (with some stats display) if running in a browser.
+ if (!report && document.head) {
+ var imported = document.createElement("script");
+ imported.addEventListener("load", function() {
+ report = tpRecordTime;
+ resolve();
+ });
+
+ imported.src =
+ "../../scripts/talos-debug.js?dummy=" + win.performance.now(); // For some browsers to re-read
+ document.head.appendChild(imported);
+ return;
+ }
+
+ resolve();
+ });
+ }
+
+ function FP_wait(ms) {
+ return function() {
+ return new Promise(function(resolve) {
+ win.setTimeout(resolve, ms);
+ });
+ };
+ }
+
+ function rAF(fn) {
+ return win.requestAnimationFrame(fn);
+ }
+
+ function P_rAF() {
+ return new Promise(function(resolve) {
+ rAF(resolve);
+ });
+ }
+
+ function P_MozAfterPaint() {
+ return new Promise(function(resolve) {
+ win.addEventListener("MozAfterPaint", () => resolve(), { once: true });
+ });
+ }
+
+ var isWindow = target.self === target;
+
+ var getPos = isWindow
+ ? function() {
+ return target.pageYOffset;
+ }
+ : function() {
+ return target.scrollTop;
+ };
+
+ var gotoTop = isWindow
+ ? function() {
+ target.scroll(0, 0);
+ ensureScroll();
+ }
+ : function() {
+ target.scrollTop = 0;
+ ensureScroll();
+ };
+
+ var doScrollTick = isWindow
+ ? function() {
+ target.scrollBy(0, stepSize);
+ ensureScroll();
+ }
+ : function() {
+ target.scrollTop += stepSize;
+ ensureScroll();
+ };
+
+ var setSmooth = isWindow
+ ? function() {
+ target.document.scrollingElement.style.scrollBehavior = "smooth";
+ }
+ : function() {
+ target.style.scrollBehavior = "smooth";
+ };
+
+ var gotoBottom = isWindow
+ ? function() {
+ target.scrollTo(0, target.scrollMaxY);
+ }
+ : function() {
+ target.scrollTop = target.scrollHeight;
+ };
+
+ function ensureScroll() {
+ // Ensure scroll by reading computed values. screenY is for X11.
+ if (!this.dummyEnsureScroll) {
+ this.dummyEnsureScroll = 1;
+ }
+ this.dummyEnsureScroll += win.screenY + getPos();
+ }
+
+ // For reference, rAF should fire on vsync, but Gecko currently doesn't use vsync.
+ // Instead, it uses 1000/layout.frame_rate
+ // (with 60 as default value when layout.frame_rate == -1).
+ function P_syncScrollTest() {
+ return new Promise(function(resolve) {
+ // We should be at the top of the page now.
+ var start = win.performance.now();
+ var lastScrollPos = getPos();
+ var lastScrollTime = start;
+ var durations = [];
+
+ function tick() {
+ var now = win.performance.now();
+ var duration = now - lastScrollTime;
+ lastScrollTime = now;
+
+ durations.push(duration);
+ doScrollTick();
+
+ /* stop scrolling if we can't scroll more, or if we've reached requested number of steps */
+ if (
+ getPos() == lastScrollPos ||
+ (opt_numSteps && durations.length >= opt_numSteps + 2)
+ ) {
+ let profilerPaused = Promise.resolve();
+ if (typeof TalosContentProfiler !== "undefined") {
+ profilerPaused = TalosContentProfiler.pause(testBaseName, true);
+ }
+
+ profilerPaused.then(() => {
+ // Note: The first (1-5) intervals WILL be longer than the rest.
+ // First interval might include initial rendering and be extra slow.
+ // Also requestAnimationFrame needs to sync (optimally in 1 frame) after long frames.
+ // Suggested: Ignore the first 5 intervals.
+
+ durations.pop(); // Last step was 0.
+ durations.pop(); // and the prev one was shorter and with end-of-page logic, ignore both.
+
+ if (win.talosDebug) {
+ win.talosDebug.displayData = true;
+ } // In a browser: also display all data points.
+
+ // For analysis (otherwise, it's too many data points for talos):
+ var sum = 0;
+ for (var i = 0; i < durations.length; i++) {
+ sum += Number(durations[i]);
+ }
+
+ // Report average interval or (failsafe) 0 if no intervls were recorded
+ result.values.push(durations.length ? sum / durations.length : 0);
+ result.names.push(testBaseName);
+ resolve();
+ });
+ return;
+ }
+
+ lastScrollPos = getPos();
+ P_MozAfterPaint().then(tick);
+ }
+
+ if (typeof TalosContentProfiler !== "undefined") {
+ TalosContentProfiler.resume(testBaseName, true).then(() => {
+ rAF(tick);
+ });
+ }
+ });
+ }
+
+ function P_testAPZScroll() {
+ var APZ_MEASURE_MS = 1000;
+
+ function startFrameTimeRecording(cb) {
+ TalosPowersParent.exec("startFrameTimeRecording", null, cb, win);
+ }
+
+ function stopFrameTimeRecording(handle, cb) {
+ TalosPowersParent.exec("stopFrameTimeRecording", handle, cb, win);
+ }
+
+ return new Promise(function(resolve, reject) {
+ setSmooth();
+
+ var handle = -1;
+ startFrameTimeRecording(function(rv) {
+ handle = rv;
+ });
+
+ // Get the measurements after APZ_MEASURE_MS of scrolling
+ win.setTimeout(function() {
+ stopFrameTimeRecording(handle, function(intervals) {
+ function average(arr) {
+ var sum = 0;
+ for (var i = 0; i < arr.length; i++) {
+ sum += arr[i];
+ }
+ return arr.length ? sum / arr.length : 0;
+ }
+
+ // remove two frames on each side of the recording to get a cleaner result
+ result.values.push(average(intervals.slice(2, intervals.length - 2)));
+ result.names.push("CSSOM." + testBaseName);
+
+ resolve();
+ });
+ }, APZ_MEASURE_MS);
+
+ gotoBottom(); // trigger the APZ scroll
+ });
+ }
+
+ P_setupReportFn()
+ .then(FP_wait(260))
+ .then(gotoTop)
+ .then(P_rAF)
+ .then(P_syncScrollTest)
+ .then(gotoTop)
+ .then(FP_wait(260))
+ .then(P_testAPZScroll)
+ .then(function() {
+ report(result.values.join(","), 0, result.names.join(","));
+ });
+}
+
+// This code below here is unique to tscroll.js inside of pageloader
+try {
+ function handleMessageFromChrome(message) {
+ var payload = message.data.details;
+ testScroll(
+ payload.target,
+ payload.stepSize,
+ "PageLoader:RecordTime",
+ payload.opt_numSteps
+ );
+ }
+
+ addMessageListener("PageLoader:ScrollTest", handleMessageFromChrome);
+} catch (e) {}
diff --git a/testing/talos/talos/pageloader/chrome/utils.js b/testing/talos/talos/pageloader/chrome/utils.js
new file mode 100644
index 0000000000..e326f6c6a4
--- /dev/null
+++ b/testing/talos/talos/pageloader/chrome/utils.js
@@ -0,0 +1,34 @@
+/* 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/. */
+
+/* eslint-env mozilla/frame-script */
+
+var idleCallbackHandle;
+
+function _idleCallbackHandler() {
+ content.window.cancelIdleCallback(idleCallbackHandle);
+ sendAsyncMessage("PageLoader:IdleCallbackReceived", {});
+}
+
+function setIdleCallback() {
+ idleCallbackHandle = content.window.requestIdleCallback(_idleCallbackHandler);
+ sendAsyncMessage("PageLoader:IdleCallbackSet", {});
+}
+
+function contentLoadHandlerCallback(cb) {
+ function _handler(e) {
+ if (e.originalTarget.defaultView == content) {
+ content.wrappedJSObject.tpRecordTime = Cu.exportFunction((t, s, n) => {
+ sendAsyncMessage("PageLoader:RecordTime", {
+ time: t,
+ startTime: s,
+ testName: n,
+ });
+ }, content);
+ content.setTimeout(cb, 0);
+ content.setTimeout(setIdleCallback, 0);
+ }
+ }
+ return _handler;
+}