diff options
Diffstat (limited to 'testing/talos/talos/pageloader/chrome/tscroll.js')
-rw-r--r-- | testing/talos/talos/pageloader/chrome/tscroll.js | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/testing/talos/talos/pageloader/chrome/tscroll.js b/testing/talos/talos/pageloader/chrome/tscroll.js new file mode 100644 index 0000000000..948c04cb11 --- /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) {} |