зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1663647, convert talos tab switching test to use actor-based RemotePageChild and remove usage of old RemotePageManager, r=mconley,perftest-reviewers,sparky
Differential Revision: https://phabricator.services.mozilla.com/D167358
This commit is contained in:
Родитель
5de446476e
Коммит
768a4ed3e6
|
@ -0,0 +1,88 @@
|
|||
/* 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/. */
|
||||
|
||||
const { ComponentUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/ComponentUtils.jsm"
|
||||
);
|
||||
|
||||
const { RemotePageChild } = ChromeUtils.import(
|
||||
"resource://gre/actors/RemotePageChild.jsm"
|
||||
);
|
||||
|
||||
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);
|
||||
|
||||
let factory;
|
||||
|
||||
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 == "Tabswitch:Teardown") {
|
||||
this.teardown();
|
||||
} else if (message.name == "GarbageCollect") {
|
||||
this.contentWindow.windowUtils.garbageCollect();
|
||||
}
|
||||
}
|
||||
|
||||
teardown() {
|
||||
Registrar.unregisterFactory(Components.ID(UUID), this._factory);
|
||||
factory = null;
|
||||
}
|
||||
}
|
||||
|
||||
function setupTabSwitch() {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
factory = ComponentUtils.generateSingletonFactory(TabSwitchAboutModule);
|
||||
|
||||
Registrar.registerFactory(
|
||||
Components.ID(UUID),
|
||||
"",
|
||||
`@mozilla.org/network/protocol/about;1?what=${ABOUT_PAGE_NAME}`,
|
||||
factory
|
||||
);
|
||||
}
|
||||
|
||||
setupTabSwitch();
|
|
@ -0,0 +1,336 @@
|
|||
/* 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 => {
|
||||
browser.addEventListener(
|
||||
"MozLayerTreeReady",
|
||||
function onLayersReady(event) {
|
||||
let now = Cu.now();
|
||||
TalosParentProfiler.mark("MozLayerTreeReady seen by tabswitch");
|
||||
resolve(now);
|
||||
},
|
||||
{ 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("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.
|
||||
await this.switchToTab(tab);
|
||||
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.resume();
|
||||
let time = await this.switchToTab(tab);
|
||||
TalosParentProfiler.pause(
|
||||
"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;
|
||||
}
|
|
@ -2,383 +2,54 @@
|
|||
|
||||
/* globals ExtensionAPI, Services */
|
||||
|
||||
const { RemotePages } = ChromeUtils.import(
|
||||
"resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm"
|
||||
);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"AboutNewTab",
|
||||
"resource:///modules/AboutNewTab.jsm"
|
||||
);
|
||||
|
||||
let TalosParentProfiler;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
function 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.
|
||||
*/
|
||||
function 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 function 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 = waitForTabSwitchDone(browser);
|
||||
// Set up our promise that will wait for the content to be
|
||||
// presented.
|
||||
let finishPromise = 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.
|
||||
*/
|
||||
function 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.
|
||||
*/
|
||||
function waitForContentPresented(browser) {
|
||||
return new Promise(resolve => {
|
||||
browser.addEventListener(
|
||||
"MozLayerTreeReady",
|
||||
function onLayersReady(event) {
|
||||
let now = Cu.now();
|
||||
TalosParentProfiler.mark("MozLayerTreeReady seen by tabswitch");
|
||||
resolve(now);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Given some browser, do a garbage collect in the parent, and then
|
||||
* a garbage collection in the content process that the browser is
|
||||
* running in.
|
||||
*
|
||||
* @param browser (<xul:browser>)
|
||||
* The browser in which to do the garbage collection.
|
||||
* @returns Promise
|
||||
* Resolves once garbage collection has been completed in the
|
||||
* parent, and the content process for the browser (if applicable).
|
||||
*/
|
||||
function forceGC(win, browser) {
|
||||
// TODO: Find a better way of letting Talos force GC in the child. We're
|
||||
// stealing a chunk of pageloader to do this, and we should probably put
|
||||
// something into TalosPowers instead.
|
||||
browser.messageManager.loadFrameScript(
|
||||
"chrome://pageloader/content/talos-content.js",
|
||||
false
|
||||
);
|
||||
|
||||
win.windowUtils.garbageCollect();
|
||||
|
||||
return new Promise(resolve => {
|
||||
let mm = browser.messageManager;
|
||||
mm.addMessageListener("Talos:ForceGC:OK", function onTalosContentForceGC(
|
||||
msg
|
||||
) {
|
||||
mm.removeMessageListener("Talos:ForceGC:OK", onTalosContentForceGC);
|
||||
resolve();
|
||||
});
|
||||
mm.sendAsyncMessage("Talos:ForceGC");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 function test(window) {
|
||||
if (!window.gMultiProcessBrowser) {
|
||||
dump(
|
||||
"** The tabswitch Talos test does not support running in non-e10s mode " +
|
||||
"anymore! Bailing out!\n"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
await 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 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("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.
|
||||
// Note: in case of multiple content processes, closing all the tabs
|
||||
// would close the related content processes, and even if we kept them
|
||||
// alive it would be unlikely that the same pages end up in the same
|
||||
// content processes, so we cannot do this at the manifest level.
|
||||
await switchToTab(tab);
|
||||
await 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 forceGC(win, tab.linkedBrowser);
|
||||
TalosParentProfiler.resume();
|
||||
let time = await switchToTab(tab);
|
||||
TalosParentProfiler.pause(
|
||||
"TabSwitch Test: " + tab.linkedBrowser.currentURI.spec
|
||||
);
|
||||
dump(`${tab.linkedBrowser.currentURI.spec}: ${time}ms\n`);
|
||||
times.push(time);
|
||||
await 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");
|
||||
|
||||
win.gBrowser.addTab(
|
||||
"data:text/html;charset=utf-8," + encodeURIComponent(output),
|
||||
{
|
||||
inBackground: false,
|
||||
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
}
|
||||
);
|
||||
|
||||
remotePage.sendAsyncMessage("tabswitch-test-results", {
|
||||
times,
|
||||
urls: testURLs,
|
||||
});
|
||||
|
||||
TalosParentProfiler.afterProfileGathered().then(() => {
|
||||
win.close();
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
var remotePage;
|
||||
|
||||
this.tabswitch = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
return {
|
||||
tabswitch: {
|
||||
setup({ processScriptPath }) {
|
||||
setup() {
|
||||
AboutNewTab.newTabURL = "about:blank";
|
||||
|
||||
const processScriptURL = context.extension.baseURI.resolve(
|
||||
processScriptPath
|
||||
let uri = Services.io.newURI(
|
||||
"actors/",
|
||||
null,
|
||||
context.extension.rootURI
|
||||
);
|
||||
let resProto = Services.io
|
||||
.getProtocolHandler("resource")
|
||||
.QueryInterface(Ci.nsIResProtocolHandler);
|
||||
resProto.setSubstitution("talos-tabswitch", uri);
|
||||
|
||||
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
|
||||
);
|
||||
Services.ppmm.loadProcessScript(processScriptURL, true);
|
||||
remotePage = new RemotePages("about:tabswitch");
|
||||
remotePage.addMessageListener("tabswitch-do-test", function doTest(
|
||||
msg
|
||||
) {
|
||||
test(msg.target.browser.ownerGlobal);
|
||||
});
|
||||
|
||||
return () => {
|
||||
Services.ppmm.sendAsyncMessage("Tabswitch:Teardown");
|
||||
remotePage.destroy();
|
||||
ChromeUtils.unregisterWindowActor(
|
||||
"TalosTabSwitch",
|
||||
tabSwitchTalosActors
|
||||
);
|
||||
AboutNewTab.resetNewTabURL();
|
||||
resProto.setSubstitution("talos-tabswitch", null);
|
||||
};
|
||||
},
|
||||
},
|
||||
|
|
|
@ -8,6 +8,4 @@
|
|||
* function that is used to report results.
|
||||
*/
|
||||
|
||||
let processScriptPath = "content/tabswitch-content-process.js";
|
||||
|
||||
browser.tabswitch.setup({ processScriptPath });
|
||||
browser.tabswitch.setup();
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
/* eslint-env mozilla/process-script */
|
||||
|
||||
const { ComponentUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/ComponentUtils.jsm"
|
||||
);
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
|
@ -1,13 +1,11 @@
|
|||
<html>
|
||||
<head>
|
||||
<script>
|
||||
/* global RPMAddMessageListener, RPMSendAsyncMessage */
|
||||
/* global RPMSendQuery */
|
||||
function do_test(override) {
|
||||
if (override || document.location.hash.indexOf("#auto") == 0) {
|
||||
RPMSendAsyncMessage("tabswitch-do-test");
|
||||
RPMAddMessageListener("tabswitch-test-results", function onMessage(msg) {
|
||||
let data = msg.data;
|
||||
tpRecordTime(data.times.join(","), 0, data.urls.join(","));
|
||||
RPMSendQuery("tabswitch-do-test", {}).then(results => {
|
||||
tpRecordTime(results.times.join(","), 0, results.urls.join(","));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,17 +7,8 @@
|
|||
"name": "setup",
|
||||
"type": "function",
|
||||
"description": "Prepares the tabswitch test to be run by the Talos framework.",
|
||||
"parameters": [{
|
||||
"type": "object",
|
||||
"name": "setupArgs",
|
||||
"properties": {
|
||||
"processScriptPath": {
|
||||
"type": "string",
|
||||
"description": "Relative path for the process script to load for the test in the initial tab."
|
||||
}
|
||||
}
|
||||
}]
|
||||
"parameters": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
@ -140,6 +140,10 @@ class RemotePageChild extends JSWindowActorChild {
|
|||
return true;
|
||||
}
|
||||
|
||||
addPage(aUrl, aFunctionMap) {
|
||||
lazy.RemotePageAccessManager.addPage(aUrl, aFunctionMap);
|
||||
}
|
||||
|
||||
// Implementation of functions that are exported into the page.
|
||||
|
||||
RPMSendAsyncMessage(aName, aData = null) {
|
||||
|
|
|
@ -340,4 +340,20 @@ export let RemotePageAccessManager = {
|
|||
// if not, deny access.
|
||||
return accessMapForURI[aFeature];
|
||||
},
|
||||
|
||||
/**
|
||||
* This function adds a new page to the access map, but can only
|
||||
* be used in a test environment.
|
||||
*/
|
||||
addPage(aUrl, aFunctionMap) {
|
||||
if (!Cu.isInAutomation) {
|
||||
throw new Error("Cannot only modify privileges during testing");
|
||||
}
|
||||
|
||||
if (aUrl in this.accessMap) {
|
||||
throw new Error("Cannot modify privileges of existing page");
|
||||
}
|
||||
|
||||
this.accessMap[aUrl] = aFunctionMap;
|
||||
},
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче