diff options
Diffstat (limited to 'testing/talos/talos/tests/tabpaint')
-rw-r--r-- | testing/talos/talos/tests/tabpaint/api.js | 217 | ||||
-rw-r--r-- | testing/talos/talos/tests/tabpaint/framescript.js | 102 | ||||
-rw-r--r-- | testing/talos/talos/tests/tabpaint/manifest.json | 23 | ||||
-rw-r--r-- | testing/talos/talos/tests/tabpaint/schema.json | 1 | ||||
-rw-r--r-- | testing/talos/talos/tests/tabpaint/tabpaint.html | 57 | ||||
-rw-r--r-- | testing/talos/talos/tests/tabpaint/tabpaint.manifest | 2 | ||||
-rw-r--r-- | testing/talos/talos/tests/tabpaint/target.html | 9 |
7 files changed, 411 insertions, 0 deletions
diff --git a/testing/talos/talos/tests/tabpaint/api.js b/testing/talos/talos/tests/tabpaint/api.js new file mode 100644 index 0000000000..6c184636ac --- /dev/null +++ b/testing/talos/talos/tests/tabpaint/api.js @@ -0,0 +1,217 @@ +/* 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/. */ + +/** + * This test will measure the performance of opening a tab and displaying + * the simplest content (a blank page). This test is concerned primarily + * with the time it takes to show the content area to the user, rather than + * the performance of the tab opening or closing animations (which are + * measured in the TART test). This is why this test disables tab animations. + * + * When opening tabs and showing content, there are two cases to consider: + * + * 1) The tab has been opened via a command from the parent process. For example, + * the user has clicked on a link in a separate application which is opening + * in this browser, so the message to open the tab and display the content + * comes from the operating system, to the parent. + * + * 2) The tab has been opened by clicking on a link in content. It is possible + * for certain types of links (_blank links for example) to open new tabs. + */ + +ChromeUtils.defineESModuleGetters(this, { + TalosParentProfiler: "resource://talos-powers/TalosParentProfiler.sys.mjs", +}); +ChromeUtils.defineModuleGetter( + this, + "BrowserWindowTracker", + "resource:///modules/BrowserWindowTracker.jsm" +); + +const REDUCE_MOTION_PREF = "ui.prefersReducedMotion"; +const MULTI_OPT_OUT_PREF = "dom.ipc.multiOptOut"; + +const MESSAGES = ["TabPaint:Go", "TabPaint:Painted"]; + +const BROWSER_FLUSH_TOPIC = "sessionstore-browser-shutdown-flush"; + +/* globals ExtensionAPI */ +this.tabpaint = class extends ExtensionAPI { + onStartup() { + // We don't have a window in this scope, and in fact, there might + // not be any browser windows around. Since pageloader is loading + // this add-on, along with the tabpaint.html content, what we'll do + // is wait for the tabpaint.html content to send us a message to + // get us moving. + for (let msgName of MESSAGES) { + Services.mm.addMessageListener(msgName, this); + } + + this.framescriptURL = this.extension.baseURI.resolve("/framescript.js"); + Services.mm.loadFrameScript(this.framescriptURL, true); + + Services.prefs.setIntPref(REDUCE_MOTION_PREF, 1); + Services.prefs.setIntPref( + MULTI_OPT_OUT_PREF, + Services.appinfo.E10S_MULTI_EXPERIMENT + ); + + /** + * We'll store a callback here to be fired once the target page + * reports that it has painted. + */ + this.paintCallback = null; + } + + onShutdown() { + for (let msgName of MESSAGES) { + Services.mm.removeMessageListener(msgName, this); + } + + Services.mm.removeDelayedFrameScript(this.framescriptURL); + + Services.prefs.clearUserPref(REDUCE_MOTION_PREF); + Services.prefs.clearUserPref(MULTI_OPT_OUT_PREF); + } + + receiveMessage(msg) { + let browser = msg.target; + + let gBrowser = browser.ownerGlobal.gBrowser; + + switch (msg.name) { + case "TabPaint:Go": { + // Our document has loaded, and we're off to the races! + this.go(browser.messageManager, gBrowser, msg.data.target); + break; + } + + case "TabPaint:Painted": { + // Content has reported that it has painted. + if (!this.paintCallback) { + throw new Error("TabPaint:Painted fired without a paintCallback set"); + } + + let tab = gBrowser.getTabForBrowser(browser); + let delta = msg.data.delta; + this.paintCallback({ tab, delta }); + break; + } + } + } + + /** + * Start a single run of the test. This will measure the time + * to open a new tab from the parent, and then measure the time + * to open a new tab from content. + * + * @param gBrowser (<xul:tabbrowser>) + * The tabbrowser of the window to use. + */ + async go(mm, gBrowser, target) { + let fromParent = await this.openTabFromParent(gBrowser, target); + let fromContent = await this.openTabFromContent(gBrowser); + + mm.sendAsyncMessage("TabPaint:FinalResults", { fromParent, fromContent }); + } + + /** + * Opens a tab from the parent, waits until it is displayed, then + * removes the tab. + * + * @param gBrowser (<xul:tabbrowser>) + * The tabbrowser of the window to use. + * + * @return Promise + * Resolves once the tab has been fully removed. Resolves + * with the time (in ms) it took to open the tab from the parent. + */ + async openTabFromParent(gBrowser, target) { + let win = BrowserWindowTracker.getTopWindow(); + + TalosParentProfiler.resume("TabPaint Parent Start"); + let startTime = Cu.now(); + + gBrowser.selectedTab = gBrowser.addTab( + //win.performance.now() + win.performance.timing.navigationStart gives the UNIX timestamp. + `${target}?${ + win.performance.now() + win.performance.timing.navigationStart + }`, + { + triggeringPrincipal: + Services.scriptSecurityManager.getSystemPrincipal(), + } + ); + + let { tab, delta } = await this.whenTabShown(); + TalosParentProfiler.pause( + "Talos - Tabpaint: Open Tab from Parent", + startTime + ); + await this.removeTab(tab); + return delta; + } + + /** + * Opens a tab from content, waits until it is displayed, then + * removes the tab. + * + * @param gBrowser (<xul:tabbrowser>) + * The tabbrowser of the window to use. + * + * @return Promise + * Resolves once the tab has been fully removed. Resolves + * with the time (in ms) it took to open the tab from content. + */ + async openTabFromContent(gBrowser) { + TalosParentProfiler.resume("TabPaint Content Start"); + let start_time = Cu.now(); + + Services.mm.broadcastAsyncMessage("TabPaint:OpenFromContent"); + + let { tab, delta } = await this.whenTabShown(); + TalosParentProfiler.pause( + "Talos - Tabpaint: Open Tab from Content", + start_time + ); + await this.removeTab(tab); + return delta; + } + + /** + * Returns a Promise that will resolve once the next tab reports + * it has shown. + * + * @return Promise + */ + whenTabShown() { + return new Promise(resolve => { + this.paintCallback = resolve; + }); + } + + /** + * Returns a Promise that removes a tab, and resolves once the final + * message from that tab has come up. + * + * @param tab (<xul:tab>) + * The tab to remove. + * + * @return Promise + */ + removeTab(tab) { + TalosParentProfiler.mark("Tabpaint: Remove Tab"); + return new Promise(resolve => { + let browser = tab.linkedBrowser; + let observer = (subject, topic, data) => { + if (subject === browser) { + Services.obs.removeObserver(observer, BROWSER_FLUSH_TOPIC); + resolve(); + } + }; + Services.obs.addObserver(observer, BROWSER_FLUSH_TOPIC); + tab.ownerGlobal.gBrowser.removeTab(tab); + }); + } +}; diff --git a/testing/talos/talos/tests/tabpaint/framescript.js b/testing/talos/talos/tests/tabpaint/framescript.js new file mode 100644 index 0000000000..2d4d7149fc --- /dev/null +++ b/testing/talos/talos/tests/tabpaint/framescript.js @@ -0,0 +1,102 @@ +/* eslint-env mozilla/frame-script */ + +(function () { + addEventListener( + "load", + loadevt => { + if (!content.location.pathname.endsWith("target.html")) { + return; + } + + /** + * When a page is loaded, we expect a search string to be + * appended with the "starting time" (in ms) of when the tab + * was opened. + * + * Example: target.html?1457063506846 + */ + let opened = parseInt(content.location.search.substring(1), 10); + + addEventListener("MozAfterPaint", function onPaint(e) { + // Bug 1371332 - sometimes, MozAfterPaint events fire + // for "empty" paints, where nothing has actually been + // painted. We can detect that by looking at the rect + // for the region that has painted. + let rect = e.boundingClientRect; + if (!rect.width && !rect.height) { + return; + } + + removeEventListener("MozAfterPaint", onPaint); + + // The MozAfterPaint event comes with a paintTimeStamp + // which tells us when in this content's lifetime the + // paint actually occurred. Note that this is not a + // measurement of when this paint occurred from + // the UNIX epoch. This makes it a little tricky to + // calculate when the paint actually occurred relative + // to the starting time that's been appended to the + // page's URL. + // + // Thankfully, the PerformanceTiming API gives us a + // sense of when this page's lifetime started, relative + // to the UNIX epoch - the "fetchStart". Taking that + // time and adding the paintTimeStamp should give us + // a pretty decent approximation of when since the + // UNIX epoch the paint actually occurred for this + // content. + // + // We can then subtract the starting time to get the + // delta, which should now represent the time it took + // from requesting that the tab be opened, to the + // paint occurring within the tab. + let fetchStart = content.performance.timing.fetchStart; + let presented = fetchStart + e.paintTimeStamp; + let delta = presented - opened; + + sendAsyncMessage("TabPaint:Painted", { delta }); + }); + }, + true + ); + + addEventListener( + "TabPaint:Ping", + e => { + let evt = new content.CustomEvent("TabPaint:Pong", { bubbles: true }); + content.dispatchEvent(evt); + }, + false, + true + ); + + addEventListener( + "TabPaint:Go", + e => { + sendAsyncMessage("TabPaint:Go", { target: e.detail.target }); + }, + false, + true + ); + + addMessageListener("TabPaint:OpenFromContent", msg => { + let evt = new content.CustomEvent("TabPaint:OpenFromContent", { + bubbles: true, + }); + content.dispatchEvent(evt); + }); + + addMessageListener("TabPaint:FinalResults", msg => { + let evt = Cu.cloneInto( + { + bubbles: true, + detail: msg.data, + }, + content + ); + + content.dispatchEvent( + new content.CustomEvent("TabPaint:FinalResults", evt) + ); + }); +})(); diff --git a/testing/talos/talos/tests/tabpaint/manifest.json b/testing/talos/talos/tests/tabpaint/manifest.json new file mode 100644 index 0000000000..105e04719a --- /dev/null +++ b/testing/talos/talos/tests/tabpaint/manifest.json @@ -0,0 +1,23 @@ +{ + "manifest_version": 2, + "name": "tabpaint test", + "version": "1.1", + "description": "Measures the performance of opening tabs", + + "browser_specific_settings": { + "gecko": { + "id": "tabpaint-test@mozilla.org" + } + }, + + "experiment_apis": { + "tabpaint": { + "schema": "schema.json", + "parent": { + "scopes": ["addon_parent"], + "script": "api.js", + "events": ["startup"] + } + } + } +} diff --git a/testing/talos/talos/tests/tabpaint/schema.json b/testing/talos/talos/tests/tabpaint/schema.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/testing/talos/talos/tests/tabpaint/schema.json @@ -0,0 +1 @@ +[] diff --git a/testing/talos/talos/tests/tabpaint/tabpaint.html b/testing/talos/talos/tests/tabpaint/tabpaint.html new file mode 100644 index 0000000000..0ab7242304 --- /dev/null +++ b/testing/talos/talos/tests/tabpaint/tabpaint.html @@ -0,0 +1,57 @@ +<html> +<head> +<script> + +function init() { + let targetURL = new URL(window.location.href); + let i = targetURL.pathname.lastIndexOf("/"); + targetURL.pathname = `${targetURL.pathname.substring(0, i)}/target.html`; + + window.addEventListener("TabPaint:FinalResults", (event) => { + let { fromParent, fromContent } = event.detail; + + tpRecordTime([fromParent, fromContent].join(","), 0, "tabpaint-from-parent, tabpaint-from-content"); + }, {once: true}); + + window.addEventListener("TabPaint:OpenFromContent", (event) => { + let target = document.getElementById("target"); + + //win.performance.now() + win.performance.timing.navigationStart gives the UNIX timestamp. + let now = window.performance.now() + window.performance.timing.navigationStart; + + target.href = `${targetURL.href}?${now}`; + target.click(); + }); + + async function tryPing() { + let pingPromise = new Promise(resolve => { + window.addEventListener("TabPaint:Pong", resolve, {once: true}); + dispatchEvent(new CustomEvent("TabPaint:Ping", {bubbles: true})); + }); + let timeoutPromise = new Promise((resolve, reject) => setTimeout(reject, 500)); + + try { + await Promise.race([pingPromise, timeoutPromise]); + } catch (e) { + return tryPing(); + } + return null; + } + + tryPing().then(() => { + dispatchEvent(new CustomEvent("TabPaint:Go", { + bubbles: true, + detail: { + target: targetURL.href, + }, + })); + }); +} +</script> +</head> +<body onload="init();"> + Hello, Talos! + + <a href="#" id="target" target="_blank">I'll open a new tab</a> +</body> +</html> diff --git a/testing/talos/talos/tests/tabpaint/tabpaint.manifest b/testing/talos/talos/tests/tabpaint/tabpaint.manifest new file mode 100644 index 0000000000..68116b6dca --- /dev/null +++ b/testing/talos/talos/tests/tabpaint/tabpaint.manifest @@ -0,0 +1,2 @@ +% http://localhost/tests/tabpaint/tabpaint.html + diff --git a/testing/talos/talos/tests/tabpaint/target.html b/testing/talos/talos/tests/tabpaint/target.html new file mode 100644 index 0000000000..79b661596e --- /dev/null +++ b/testing/talos/talos/tests/tabpaint/target.html @@ -0,0 +1,9 @@ +<html> +<head> +<title>TABPAINT</title> +<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta> +</head> +<body> +<p>TABPAINT</p> +</body> +</html> |