summaryrefslogtreecommitdiffstats
path: root/testing/talos/talos/tests/tabswitch
diff options
context:
space:
mode:
Diffstat (limited to 'testing/talos/talos/tests/tabswitch')
-rw-r--r--testing/talos/talos/tests/tabswitch/actors/TalosTabSwitchChild.sys.mjs31
-rw-r--r--testing/talos/talos/tests/tabswitch/actors/TalosTabSwitchParent.sys.mjs344
-rw-r--r--testing/talos/talos/tests/tabswitch/api.js62
-rw-r--r--testing/talos/talos/tests/tabswitch/background.js11
-rw-r--r--testing/talos/talos/tests/tabswitch/content/tabswitch-content-process.js69
-rw-r--r--testing/talos/talos/tests/tabswitch/content/test.html17
-rw-r--r--testing/talos/talos/tests/tabswitch/manifest.json24
-rw-r--r--testing/talos/talos/tests/tabswitch/schema.json14
-rw-r--r--testing/talos/talos/tests/tabswitch/tabswitch.manifest1
9 files changed, 573 insertions, 0 deletions
diff --git a/testing/talos/talos/tests/tabswitch/actors/TalosTabSwitchChild.sys.mjs b/testing/talos/talos/tests/tabswitch/actors/TalosTabSwitchChild.sys.mjs
new file mode 100644
index 0000000000..5b27d677e3
--- /dev/null
+++ b/testing/talos/talos/tests/tabswitch/actors/TalosTabSwitchChild.sys.mjs
@@ -0,0 +1,31 @@
+/* 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 { RemotePageChild } from "resource://gre/actors/RemotePageChild.sys.mjs";
+
+export class TalosTabSwitchChild extends RemotePageChild {
+ actorCreated() {
+ // Ignore about:blank pages that can get here.
+ if (!String(this.document.location).startsWith("about:tabswitch")) {
+ return;
+ }
+
+ // If an error occurs, it was probably already added by an earlier test run.
+ try {
+ this.addPage("about:tabswitch", {
+ RPMSendQuery: ["tabswitch-do-test"],
+ });
+ } catch {}
+
+ super.actorCreated();
+ }
+
+ handleEvent(event) {}
+
+ receiveMessage(message) {
+ if (message.name == "GarbageCollect") {
+ this.contentWindow.windowUtils.garbageCollect();
+ }
+ }
+}
diff --git a/testing/talos/talos/tests/tabswitch/actors/TalosTabSwitchParent.sys.mjs b/testing/talos/talos/tests/tabswitch/actors/TalosTabSwitchParent.sys.mjs
new file mode 100644
index 0000000000..3b609af0a7
--- /dev/null
+++ b/testing/talos/talos/tests/tabswitch/actors/TalosTabSwitchParent.sys.mjs
@@ -0,0 +1,344 @@
+/* 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/. */
+
+let TalosParentProfiler;
+
+export class TalosTabSwitchParent extends JSWindowActorParent {
+ receiveMessage(message) {
+ if (message.name == "tabswitch-do-test") {
+ let browser = this.browsingContext.top.embedderElement;
+ return this.test(browser.ownerGlobal);
+ }
+
+ return undefined;
+ }
+
+ /**
+ * Returns a Promise that resolves when browser-delayed-startup-finished
+ * fires for a given window
+ *
+ * @param win
+ * The window that we're waiting for the notification for.
+ * @returns Promise
+ */
+ waitForDelayedStartup(win) {
+ return new Promise(resolve => {
+ const topic = "browser-delayed-startup-finished";
+ Services.obs.addObserver(function onStartup(subject) {
+ if (win == subject) {
+ Services.obs.removeObserver(onStartup, topic);
+ resolve();
+ }
+ }, topic);
+ });
+ }
+
+ /**
+ * For some <xul:tabbrowser>, loads a collection of URLs as new tabs
+ * in that browser.
+ *
+ * @param gBrowser (<xul:tabbrowser>)
+ * The <xul:tabbrowser> in which to load the new tabs.
+ * @param urls (Array)
+ * An array of URL strings to be loaded as new tabs.
+ * @returns Promise
+ * Resolves once all tabs have finished loading.
+ */
+ loadTabs(gBrowser, urls) {
+ return new Promise(resolve => {
+ gBrowser.loadTabs(urls, {
+ inBackground: true,
+ triggeringPrincipal:
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+
+ let waitingToLoad = new Set(urls);
+
+ let listener = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ let loadedState =
+ Ci.nsIWebProgressListener.STATE_STOP |
+ Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+ if (
+ (aStateFlags & loadedState) == loadedState &&
+ !aWebProgress.isLoadingDocument &&
+ aWebProgress.isTopLevel &&
+ Components.isSuccessCode(aStatus)
+ ) {
+ dump(`Loaded: ${aBrowser.currentURI.spec}\n`);
+ waitingToLoad.delete(aBrowser.currentURI.spec);
+
+ if (!waitingToLoad.size) {
+ gBrowser.removeTabsProgressListener(listener);
+ dump("Loads complete - starting tab switches\n");
+ resolve();
+ }
+ }
+ },
+ };
+
+ gBrowser.addTabsProgressListener(listener);
+ });
+ }
+
+ /**
+ * For some <xul:tab> in a browser window, have that window switch
+ * to that tab. Returns a Promise that resolves ones the tab content
+ * has been presented to the user.
+ */
+ async switchToTab(tab) {
+ let browser = tab.linkedBrowser;
+ let gBrowser = tab.ownerGlobal.gBrowser;
+
+ let start = Cu.now();
+
+ // We need to wait for the TabSwitchDone event to make sure
+ // that the async tab switcher has shut itself down.
+ let switchDone = this.waitForTabSwitchDone(browser);
+ // Set up our promise that will wait for the content to be
+ // presented.
+ let finishPromise = this.waitForContentPresented(browser);
+ // Finally, do the tab switch.
+ gBrowser.selectedTab = tab;
+
+ await switchDone;
+ let finish = await finishPromise;
+
+ return finish - start;
+ }
+
+ /**
+ * For some <xul:browser>, find the <xul:tabbrowser> associated with it,
+ * and wait until that tabbrowser has finished a tab switch. This function
+ * assumes a tab switch has started, or is about to start.
+ *
+ * @param browser (<xul:browser>)
+ * The browser whose tabbrowser we expect to be involved in a tab
+ * switch.
+ * @returns Promise
+ * Resolves once the TabSwitchDone event is fired.
+ */
+ waitForTabSwitchDone(browser) {
+ return new Promise(resolve => {
+ let gBrowser = browser.ownerGlobal.gBrowser;
+ gBrowser.addEventListener(
+ "TabSwitchDone",
+ function onTabSwitchDone() {
+ resolve();
+ },
+ { once: true }
+ );
+ });
+ }
+
+ /**
+ * For some <xul:browser>, returns a Promise that resolves once its
+ * content has been presented to the user.
+ *
+ * @param browser (<xul:browser>)
+ * The browser we expect to be presented.
+ *
+ * @returns Promise
+ * Resolves once the content has been presented. Resolves to
+ * the system time that the presentation occurred at, in
+ * milliseconds since midnight 01 January, 1970 UTC.
+ */
+ waitForContentPresented(browser) {
+ return new Promise(resolve => {
+ function onLayersReady() {
+ let now = Cu.now();
+ TalosParentProfiler.mark("Browser layers seen by tabswitch");
+ resolve(now);
+ }
+ if (browser.hasLayers) {
+ onLayersReady();
+ return;
+ }
+ browser.addEventListener("MozLayerTreeReady", onLayersReady, {
+ once: true,
+ });
+ });
+ }
+
+ /**
+ * Do a garbage collect in the parent, and then a garbage
+ * collection in the content process that the actor is
+ * running in.
+ *
+ * @returns Promise
+ * Resolves once garbage collection has been completed in the
+ * parent, and the content process for the actor.
+ */
+ forceGC(win) {
+ win.windowUtils.garbageCollect();
+ return this.sendQuery("GarbageCollect");
+ }
+
+ /**
+ * Given some host window, open a new window, browser its initial tab to
+ * about:blank, then load up our set of testing URLs. Once they've all finished
+ * loading, switch through each tab, recording their tab switch times. Finally,
+ * report the results.
+ *
+ * @param window
+ * A host window. Primarily, we just use this for the OpenBrowserWindow
+ * function defined in that window.
+ * @returns Promise
+ */
+ async test(window) {
+ if (!window.gMultiProcessBrowser) {
+ dump(
+ "** The tabswitch Talos test does not support running in non-e10s mode " +
+ "anymore! Bailing out!\n"
+ );
+ return null;
+ }
+
+ TalosParentProfiler = ChromeUtils.importESModule(
+ "resource://talos-powers/TalosParentProfiler.sys.mjs"
+ ).TalosParentProfiler;
+
+ let testURLs = [];
+
+ let win = window.OpenBrowserWindow();
+ try {
+ let prefFile = Services.prefs.getCharPref("addon.test.tabswitch.urlfile");
+ if (prefFile) {
+ testURLs = handleFile(win, prefFile);
+ }
+ } catch (ex) {
+ /* error condition handled below */
+ }
+ if (!testURLs || !testURLs.length) {
+ dump(
+ "no tabs to test, 'addon.test.tabswitch.urlfile' pref isn't set to page set path\n"
+ );
+ return null;
+ }
+
+ await this.waitForDelayedStartup(win);
+
+ let gBrowser = win.gBrowser;
+
+ // We don't want to catch scrolling the tabstrip in our tests
+ gBrowser.tabContainer.style.opacity = "0";
+
+ let initialTab = gBrowser.selectedTab;
+ await this.loadTabs(gBrowser, testURLs);
+
+ // We'll switch back to about:blank after each tab switch
+ // in an attempt to put the graphics layer into a "steady"
+ // state before switching to the next tab.
+ initialTab.linkedBrowser.loadURI(Services.io.newURI("about:blank"), {
+ triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
+ {}
+ ),
+ });
+
+ let tabs = gBrowser.getTabsToTheEndFrom(initialTab);
+ let times = [];
+
+ for (let tab of tabs) {
+ // Let's do an initial run to warm up any paint related caches
+ // (like glyph caches for text). In the next loop we will start with
+ // a GC before each switch so we don't need here.
+ dump(`${tab.linkedBrowser.currentURI.spec}: warm up begin\n`);
+ await this.switchToTab(tab);
+ dump(`${tab.linkedBrowser.currentURI.spec}: warm up end\n`);
+
+ await this.switchToTab(initialTab);
+ }
+
+ for (let tab of tabs) {
+ // Moving a tab causes expensive style/layout computations on the tab bar
+ // that are delayed using requestAnimationFrame, so wait for an animation
+ // frame callback + one tick to ensure we aren't measuring the time it
+ // takes to move a tab.
+ gBrowser.moveTabTo(tab, 1);
+ await new Promise(resolve => win.requestAnimationFrame(resolve));
+ await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
+
+ await this.forceGC(win);
+ TalosParentProfiler.subtestStart();
+ let time = await this.switchToTab(tab);
+ TalosParentProfiler.subtestEnd(
+ "TabSwitch Test: " + tab.linkedBrowser.currentURI.spec
+ );
+ dump(`${tab.linkedBrowser.currentURI.spec}: ${time}ms\n`);
+ times.push(time);
+ await this.switchToTab(initialTab);
+ }
+
+ let output =
+ "<!DOCTYPE html>" +
+ '<html lang="en">' +
+ "<head><title>Tab Switch Results</title></head>" +
+ "<body><h1>Tab switch times</h1>" +
+ "<table>";
+ let time = 0;
+ for (let i in times) {
+ time += times[i];
+ output +=
+ "<tr><td>" + testURLs[i] + "</td><td>" + times[i] + "ms</td></tr>";
+ }
+ output += "</table></body></html>";
+ dump("total tab switch time:" + time + "\n");
+
+ let resultsTab = win.gBrowser.addTab(
+ "data:text/html;charset=utf-8," + encodeURIComponent(output),
+ {
+ triggeringPrincipal:
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ }
+ );
+
+ win.gBrowser.selectedTab = resultsTab;
+
+ TalosParentProfiler.afterProfileGathered().then(() => {
+ win.close();
+ });
+
+ return {
+ times,
+ urls: testURLs,
+ };
+ }
+}
+
+// This just has to match up with the make_talos_domain function in talos.py
+function makeTalosDomain(host) {
+ return host + "-talos";
+}
+
+function handleFile(win, file) {
+ let localFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ localFile.initWithPath(file);
+ let localURI = Services.io.newFileURI(localFile);
+ let req = new win.XMLHttpRequest();
+ req.open("get", localURI.spec, false);
+ req.send(null);
+
+ let testURLs = [];
+ let maxurls = Services.prefs.getIntPref("addon.test.tabswitch.maxurls");
+ let lines = req.responseText.split('<a href="');
+ testURLs = [];
+ if (maxurls && maxurls > 0) {
+ lines.splice(maxurls, lines.length);
+ }
+ lines.forEach(function (a) {
+ let url = a.split('"')[0];
+ if (url != "") {
+ let domain = url.split("/")[0];
+ if (domain != "") {
+ testURLs.push(`http://${makeTalosDomain(domain)}/fis/tp5n/${url}`);
+ }
+ }
+ });
+
+ return testURLs;
+}
diff --git a/testing/talos/talos/tests/tabswitch/api.js b/testing/talos/talos/tests/tabswitch/api.js
new file mode 100644
index 0000000000..8ebccdf411
--- /dev/null
+++ b/testing/talos/talos/tests/tabswitch/api.js
@@ -0,0 +1,62 @@
+// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+
+/* globals ExtensionAPI, Services */
+
+ChromeUtils.defineESModuleGetters(this, {
+ AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
+});
+
+this.tabswitch = class extends ExtensionAPI {
+ getAPI(context) {
+ return {
+ tabswitch: {
+ setup() {
+ AboutNewTab.newTabURL = "about:blank";
+
+ let uri = Services.io.newURI(
+ "actors/",
+ null,
+ context.extension.rootURI
+ );
+ let resProto = Services.io
+ .getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+ resProto.setSubstitution("talos-tabswitch", uri);
+
+ const processScriptURL = context.extension.baseURI.resolve(
+ "content/tabswitch-content-process.js"
+ );
+ Services.ppmm.loadProcessScript(processScriptURL, true);
+
+ let tabSwitchTalosActors = {
+ parent: {
+ esModuleURI:
+ "resource://talos-tabswitch/TalosTabSwitchParent.sys.mjs",
+ },
+ child: {
+ esModuleURI:
+ "resource://talos-tabswitch/TalosTabSwitchChild.sys.mjs",
+ events: {
+ DOMDocElementInserted: { capture: true },
+ },
+ },
+ };
+ ChromeUtils.registerWindowActor(
+ "TalosTabSwitch",
+ tabSwitchTalosActors
+ );
+
+ return () => {
+ Services.ppmm.sendAsyncMessage("Tabswitch:Teardown");
+ ChromeUtils.unregisterWindowActor(
+ "TalosTabSwitch",
+ tabSwitchTalosActors
+ );
+ AboutNewTab.resetNewTabURL();
+ resProto.setSubstitution("talos-tabswitch", null);
+ };
+ },
+ },
+ };
+ }
+};
diff --git a/testing/talos/talos/tests/tabswitch/background.js b/testing/talos/talos/tests/tabswitch/background.js
new file mode 100644
index 0000000000..6727db475f
--- /dev/null
+++ b/testing/talos/talos/tests/tabswitch/background.js
@@ -0,0 +1,11 @@
+/* globals browser */
+
+/**
+ * The tabswitch test is a Pageloader test, meaning that the tabswitch.manifest file
+ * tells Talos to load a particular page. The loading of that page signals
+ * the start of the test. It's also where results need to go, as the
+ * Talos gunk augments the loaded page with a special tpRecordTime
+ * function that is used to report results.
+ */
+
+browser.tabswitch.setup();
diff --git a/testing/talos/talos/tests/tabswitch/content/tabswitch-content-process.js b/testing/talos/talos/tests/tabswitch/content/tabswitch-content-process.js
new file mode 100644
index 0000000000..53c4935953
--- /dev/null
+++ b/testing/talos/talos/tests/tabswitch/content/tabswitch-content-process.js
@@ -0,0 +1,69 @@
+/* eslint-env mozilla/process-script */
+
+const { ComponentUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/ComponentUtils.sys.mjs"
+);
+
+const WEBEXTENSION_ID = "tabswitch-talos@mozilla.org";
+const ABOUT_PAGE_NAME = "tabswitch";
+const Registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+const UUID = "0f459ab4-b4ba-4741-ac89-ee47dea07adb";
+const ABOUT_PATH_PATH = "content/test.html";
+
+const { WebExtensionPolicy } = Cu.getGlobalForObject(Services);
+
+const TPSProcessScript = {
+ init() {
+ let extensionPolicy = WebExtensionPolicy.getByID(WEBEXTENSION_ID);
+ let aboutPageURI = extensionPolicy.getURL(ABOUT_PATH_PATH);
+
+ class TabSwitchAboutModule {
+ constructor() {
+ this.QueryInterface = ChromeUtils.generateQI(["nsIAboutModule"]);
+ }
+ newChannel(aURI, aLoadInfo) {
+ let uri = Services.io.newURI(aboutPageURI);
+ let chan = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
+ chan.originalURI = aURI;
+ return chan;
+ }
+ getURIFlags(aURI) {
+ return (
+ Ci.nsIAboutModule.ALLOW_SCRIPT |
+ Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD |
+ Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT
+ );
+ }
+ }
+
+ let factory = ComponentUtils.generateSingletonFactory(TabSwitchAboutModule);
+ this._factory = factory;
+
+ Registrar.registerFactory(
+ Components.ID(UUID),
+ "",
+ `@mozilla.org/network/protocol/about;1?what=${ABOUT_PAGE_NAME}`,
+ factory
+ );
+
+ this._hasSetup = true;
+ },
+
+ teardown() {
+ if (!this._hasSetup) {
+ return;
+ }
+
+ Registrar.unregisterFactory(Components.ID(UUID), this._factory);
+ this._hasSetup = false;
+ this._factory = null;
+ },
+
+ receiveMessage(msg) {
+ if (msg.name == "Tabswitch:Teardown") {
+ this.teardown();
+ }
+ },
+};
+
+TPSProcessScript.init();
diff --git a/testing/talos/talos/tests/tabswitch/content/test.html b/testing/talos/talos/tests/tabswitch/content/test.html
new file mode 100644
index 0000000000..3cdc7a673c
--- /dev/null
+++ b/testing/talos/talos/tests/tabswitch/content/test.html
@@ -0,0 +1,17 @@
+<html>
+ <head>
+ <script>
+ /* global RPMSendQuery */
+ function do_test(override) {
+ if (override || document.location.hash.indexOf("#auto") == 0) {
+ RPMSendQuery("tabswitch-do-test", {}).then(results => {
+ tpRecordTime(results.times.join(","), 0, results.urls.join(","));
+ });
+ }
+ }
+ </script>
+ </head>
+ <body onload="do_test(false)">
+ Hello Talos!
+ </body>
+</html>
diff --git a/testing/talos/talos/tests/tabswitch/manifest.json b/testing/talos/talos/tests/tabswitch/manifest.json
new file mode 100644
index 0000000000..d8f4451c4e
--- /dev/null
+++ b/testing/talos/talos/tests/tabswitch/manifest.json
@@ -0,0 +1,24 @@
+{
+ "browser_specific_settings": {
+ "gecko": {
+ "id": "tabswitch-talos@mozilla.org"
+ }
+ },
+ "manifest_version": 2,
+ "name": "Tabswitch Talos Test",
+ "version": "0.1",
+ "permissions": [],
+ "background": {
+ "scripts": ["background.js"]
+ },
+ "experiment_apis": {
+ "tabswitch": {
+ "schema": "schema.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "script": "api.js",
+ "paths": [["tabswitch"]]
+ }
+ }
+ }
+}
diff --git a/testing/talos/talos/tests/tabswitch/schema.json b/testing/talos/talos/tests/tabswitch/schema.json
new file mode 100644
index 0000000000..6a8e54177e
--- /dev/null
+++ b/testing/talos/talos/tests/tabswitch/schema.json
@@ -0,0 +1,14 @@
+[
+ {
+ "namespace": "tabswitch",
+ "description": "Special powers for the tabswitch Talos test",
+ "functions": [
+ {
+ "name": "setup",
+ "type": "function",
+ "description": "Prepares the tabswitch test to be run by the Talos framework.",
+ "parameters": []
+ }
+ ]
+ }
+]
diff --git a/testing/talos/talos/tests/tabswitch/tabswitch.manifest b/testing/talos/talos/tests/tabswitch/tabswitch.manifest
new file mode 100644
index 0000000000..cabe207e5d
--- /dev/null
+++ b/testing/talos/talos/tests/tabswitch/tabswitch.manifest
@@ -0,0 +1 @@
+% about:tabswitch#auto