summaryrefslogtreecommitdiffstats
path: root/testing/talos/talos/tests/tabpaint
diff options
context:
space:
mode:
Diffstat (limited to 'testing/talos/talos/tests/tabpaint')
-rw-r--r--testing/talos/talos/tests/tabpaint/api.js217
-rw-r--r--testing/talos/talos/tests/tabpaint/framescript.js102
-rw-r--r--testing/talos/talos/tests/tabpaint/manifest.json23
-rw-r--r--testing/talos/talos/tests/tabpaint/schema.json1
-rw-r--r--testing/talos/talos/tests/tabpaint/tabpaint.html57
-rw-r--r--testing/talos/talos/tests/tabpaint/tabpaint.manifest2
-rw-r--r--testing/talos/talos/tests/tabpaint/target.html9
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>