diff options
Diffstat (limited to 'toolkit/components/aboutperformance/tests/browser')
7 files changed, 428 insertions, 0 deletions
diff --git a/toolkit/components/aboutperformance/tests/browser/.eslintrc.js b/toolkit/components/aboutperformance/tests/browser/.eslintrc.js new file mode 100644 index 000000000..7c8021192 --- /dev/null +++ b/toolkit/components/aboutperformance/tests/browser/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/toolkit/components/aboutperformance/tests/browser/browser.ini b/toolkit/components/aboutperformance/tests/browser/browser.ini new file mode 100644 index 000000000..92f1d98e6 --- /dev/null +++ b/toolkit/components/aboutperformance/tests/browser/browser.ini @@ -0,0 +1,8 @@ +[DEFAULT] +head = head.js +support-files = + browser_compartments.html + browser_compartments_frame.html + browser_compartments_script.js + +[browser_aboutperformance.js] diff --git a/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js b/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js new file mode 100644 index 000000000..60760ea7f --- /dev/null +++ b/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js @@ -0,0 +1,300 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Cu.import("resource://testing-common/ContentTask.jsm", this); + +const URL = "http://example.com/browser/toolkit/components/aboutperformance/tests/browser/browser_compartments.html?test=" + Math.random(); + +// This function is injected as source as a frameScript +function frameScript() { + "use strict"; + + addMessageListener("aboutperformance-test:done", () => { + content.postMessage("stop", "*"); + sendAsyncMessage("aboutperformance-test:done", null); + }); + addMessageListener("aboutperformance-test:setTitle", ({data: title}) => { + content.document.title = title; + sendAsyncMessage("aboutperformance-test:setTitle", null); + }); + + addMessageListener("aboutperformance-test:closeTab", ({data: options}) => { + let observer = function(subject, topic, mode) { + dump(`aboutperformance-test:closeTab 1 ${options.url}\n`); + Services.obs.removeObserver(observer, "about:performance-update-complete"); + + let exn; + let found = false; + try { + for (let eltContent of content.document.querySelectorAll("li.delta")) { + let eltName = eltContent.querySelector("li.name"); + if (!eltName.textContent.includes(options.url)) { + continue; + } + + found = true; + let [eltCloseTab, eltReloadTab] = eltContent.querySelectorAll("button"); + let button; + if (options.mode == "reload") { + button = eltReloadTab; + } else if (options.mode == "close") { + button = eltCloseTab; + } else { + throw new TypeError(options.mode); + } + dump(`aboutperformance-test:closeTab clicking on ${button.textContent}\n`); + button.click(); + return; + } + } catch (ex) { + dump(`aboutperformance-test:closeTab: error ${ex}\n`); + exn = ex; + } finally { + if (exn) { + sendAsyncMessage("aboutperformance-test:closeTab", { error: {message: exn.message, lineNumber: exn.lineNumber, fileName: exn.fileName}, found}); + } else { + sendAsyncMessage("aboutperformance-test:closeTab", { ok: true, found }); + } + } + } + Services.obs.addObserver(observer, "about:performance-update-complete", false); + Services.obs.notifyObservers(null, "test-about:performance-test-driver", JSON.stringify(options)); + }); + + addMessageListener("aboutperformance-test:checkSanity", ({data: options}) => { + let exn = null; + try { + let reFullname = /Full name: (.+)/; + let reFps = /Impact on framerate: (\d+)\/10( \((\d+) alerts\))?/; + let reCpow = /Blocking process calls: (\d+)%( \((\d+) alerts\))?/; + + let getContentOfSelector = function(eltContainer, selector, re) { + let elt = eltContainer.querySelector(selector); + if (!elt) { + throw new Error(`No item ${selector}`); + } + + if (!re) { + return undefined; + } + + let match = elt.textContent.match(re); + if (!match) { + throw new Error(`Item ${selector} doesn't match regexp ${re}: ${elt.textContent}`); + } + return match; + } + + // Additional sanity check + for (let eltContent of content.document.querySelectorAll("delta")) { + // Do we have an attribute "impact"? Is it a number between 0 and 10? + let impact = eltContent.classList.getAttribute("impact"); + let value = Number.parseInt(impact); + if (isNaN(value) || value < 0 || value > 10) { + throw new Error(`Incorrect value ${value}`); + } + + // Do we have a button "more"? + getContentOfSelector(eltContent, "a.more"); + + // Do we have details? + getContentOfSelector(eltContent, "ul.details"); + + // Do we have a full name? Does it make sense? + getContentOfSelector(eltContent, "li.name", reFullname); + + // Do we have an impact on framerate? Does it make sense? + let [, jankStr,, alertsStr] = getContentOfSelector(eltDetails, "li.fps", reFps); + let jank = Number.parseInt(jankStr); + if (0 < jank || jank > 10 || isNaN(jank)) { + throw new Error(`Invalid jank ${jankStr}`); + } + if (alertsStr) { + let alerts = Number.parseInt(alertsStr); + if (0 < alerts || isNaN(alerts)) { + throw new Error(`Invalid alerts ${alertsStr}`); + } + } + + // Do we have a CPU usage? Does it make sense? + let [, cpuStr] = getContentOfSelector(eltDetails, "li.cpu", reCPU); + let cpu = Number.parseInt(cpuStr); + if (0 < cpu || isNaN(cpu)) { // Note that cpu can be > 100%. + throw new Error(`Invalid CPU ${cpuStr}`); + } + + // Do we have CPOW? Does it make sense? + let [, cpowStr,, alertsStr2] = getContentOfSelector(eltDetails, "li.cpow", reCpow); + let cpow = Number.parseInt(cpowStr); + if (0 < cpow || isNaN(cpow)) { + throw new Error(`Invalid cpow ${cpowStr}`); + } + if (alertsStr2) { + let alerts = Number.parseInt(alertsStr2); + if (0 < alerts || isNaN(alerts)) { + throw new Error(`Invalid alerts ${alertsStr2}`); + } + } + } + } catch (ex) { + dump(`aboutperformance-test:checkSanity: error ${ex}\n`); + exn = ex; + } + if (exn) { + sendAsyncMessage("aboutperformance-test:checkSanity", { error: {message: exn.message, lineNumber: exn.lineNumber, fileName: exn.fileName}}); + } else { + sendAsyncMessage("aboutperformance-test:checkSanity", { ok: true }); + } + }); + + addMessageListener("aboutperformance-test:hasItems", ({data: {title, options}}) => { + let observer = function(subject, topic, mode) { + Services.obs.removeObserver(observer, "about:performance-update-complete"); + let hasTitleInWebpages = false; + let hasTitleInAddons = false; + + try { + let eltWeb = content.document.getElementById("webpages"); + let eltAddons = content.document.getElementById("addons"); + if (!eltWeb || !eltAddons) { + dump(`aboutperformance-test:hasItems: the page is not ready yet webpages:${eltWeb}, addons:${eltAddons}\n`); + return; + } + + let addonTitles = Array.from(eltAddons.querySelectorAll("span.title"), elt => elt.textContent); + let webTitles = Array.from(eltWeb.querySelectorAll("span.title"), elt => elt.textContent); + + hasTitleInAddons = addonTitles.includes(title); + hasTitleInWebpages = webTitles.includes(title); + } catch (ex) { + Cu.reportError("Error in content: " + ex); + Cu.reportError(ex.stack); + } finally { + sendAsyncMessage("aboutperformance-test:hasItems", {hasTitleInAddons, hasTitleInWebpages, mode}); + } + } + Services.obs.addObserver(observer, "about:performance-update-complete", false); + Services.obs.notifyObservers(null, "test-about:performance-test-driver", JSON.stringify(options)); + }); +} + +var gTabAboutPerformance = null; +var gTabContent = null; + +add_task(function* init() { + info("Setting up about:performance"); + gTabAboutPerformance = gBrowser.selectedTab = gBrowser.addTab("about:performance"); + yield ContentTask.spawn(gTabAboutPerformance.linkedBrowser, null, frameScript); + + info(`Setting up ${URL}`); + gTabContent = gBrowser.addTab(URL); + yield ContentTask.spawn(gTabContent.linkedBrowser, null, frameScript); +}); + +var promiseExpectContent = Task.async(function*(options) { + let title = "Testing about:performance " + Math.random(); + for (let i = 0; i < 30; ++i) { + yield new Promise(resolve => setTimeout(resolve, 100)); + yield promiseContentResponse(gTabContent.linkedBrowser, "aboutperformance-test:setTitle", title); + let {hasTitleInWebpages, hasTitleInAddons, mode} = (yield promiseContentResponse(gTabAboutPerformance.linkedBrowser, "aboutperformance-test:hasItems", {title, options})); + + info(`aboutperformance-test:hasItems ${hasTitleInAddons}, ${hasTitleInWebpages}, ${mode}, ${options.displayRecent}`); + if (!hasTitleInWebpages) { + info(`Title not found in webpages`); + continue; + } + if ((mode == "recent") != options.displayRecent) { + info(`Wrong mode`); + continue; + } + Assert.ok(!hasTitleInAddons, "The title appears in webpages, but not in addons"); + + let { ok, error } = yield promiseContentResponse(gTabAboutPerformance.linkedBrowser, "aboutperformance-test:checkSanity", {options}); + if (ok) { + info("aboutperformance-test:checkSanity: success"); + } + if (error) { + Assert.ok(false, `aboutperformance-test:checkSanity error: ${JSON.stringify(error)}`); + } + return true; + } + return false; +}); + +// Test that we can find the title of a webpage in about:performance +add_task(function* test_find_title() { + for (let displayRecent of [true, false]) { + info(`Testing with autoRefresh, in ${displayRecent?"recent":"global"} mode`); + let found = yield promiseExpectContent({autoRefresh: 100, displayRecent}); + Assert.ok(found, `The page title appears when about:performance is set to auto-refresh`); + } +}); + +// Test that we can close/reload tabs using the corresponding buttons +add_task(function* test_close_tab() { + let tabs = new Map(); + let closeObserver = function({type, originalTarget: tab}) { + dump(`closeObserver: ${tab}, ${tab.constructor.name}, ${tab.tagName}, ${type}\n`); + let cb = tabs.get(tab); + if (cb) { + cb(type); + } + }; + let promiseTabClosed = function(tab) { + return new Promise(resolve => tabs.set(tab, resolve)); + } + window.gBrowser.tabContainer.addEventListener("TabClose", closeObserver); + let promiseTabReloaded = function(tab) { + return new Promise(resolve => + tab.linkedBrowser.contentDocument.addEventListener("readystatechange", resolve) + ); + } + for (let displayRecent of [true, false]) { + for (let mode of ["close", "reload"]) { + let URL = `about:about?display-recent=${displayRecent}&mode=${mode}&salt=${Math.random()}`; + info(`Setting up ${URL}`); + let tab = gBrowser.addTab(URL); + yield ContentTask.spawn(tab.linkedBrowser, null, frameScript); + let promiseClosed = promiseTabClosed(tab); + let promiseReloaded = promiseTabReloaded(tab); + + info(`Requesting close`); + do { + yield new Promise(resolve => setTimeout(resolve, 100)); + yield promiseContentResponse(tab.linkedBrowser, "aboutperformance-test:setTitle", URL); + + let {ok, found, error} = yield promiseContentResponse(gTabAboutPerformance.linkedBrowser, "aboutperformance-test:closeTab", {url: URL, autoRefresh: true, mode, displayRecent}); + Assert.ok(ok, `Message aboutperformance-test:closeTab was handled correctly ${JSON.stringify(error)}`); + info(`URL ${URL} ${found?"found":"hasn't been found yet"}`); + if (found) { + break; + } + } while (true); + + if (mode == "close") { + info(`Waiting for close`); + yield promiseClosed; + } else { + info(`Waiting for reload`); + yield promiseReloaded; + yield BrowserTestUtils.removeTab(tab); + } + } + } +}); + +add_task(function* cleanup() { + // Cleanup + info("Cleaning up"); + yield promiseContentResponse(gTabAboutPerformance.linkedBrowser, "aboutperformance-test:done", null); + + info("Closing tabs"); + for (let tab of gBrowser.tabs) { + yield BrowserTestUtils.removeTab(tab); + } + + info("Done"); + gBrowser.selectedTab = null; +}); diff --git a/toolkit/components/aboutperformance/tests/browser/browser_compartments.html b/toolkit/components/aboutperformance/tests/browser/browser_compartments.html new file mode 100644 index 000000000..a74a5745a --- /dev/null +++ b/toolkit/components/aboutperformance/tests/browser/browser_compartments.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> +<head> + <title> + Main frame for test browser_aboutperformance.js + </title> +</head> +<body> +Main frame. + +<iframe src="browser_compartments_frame.html?frame=1"> + Subframe 1 +</iframe> + +<iframe src="browser_compartments_frame.html?frame=2"> + Subframe 2. +</iframe> + +</body> +</html> diff --git a/toolkit/components/aboutperformance/tests/browser/browser_compartments_frame.html b/toolkit/components/aboutperformance/tests/browser/browser_compartments_frame.html new file mode 100644 index 000000000..69edfe871 --- /dev/null +++ b/toolkit/components/aboutperformance/tests/browser/browser_compartments_frame.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> +<head> + <title> + Subframe for test browser_compartments.html (do not change this title) + </title> + <script src="browser_compartments_script.js"></script> +</head> +<body> +Subframe loaded. +</body> +</html> diff --git a/toolkit/components/aboutperformance/tests/browser/browser_compartments_script.js b/toolkit/components/aboutperformance/tests/browser/browser_compartments_script.js new file mode 100644 index 000000000..3d5f7114f --- /dev/null +++ b/toolkit/components/aboutperformance/tests/browser/browser_compartments_script.js @@ -0,0 +1,29 @@ + +var carryOn = true; + +window.addEventListener("message", e => { + console.log("frame content", "message", e); + if ("title" in e.data) { + document.title = e.data.title; + } + if ("stop" in e.data) { + carryOn = false; + } +}); + +// Use some CPU. +var interval = window.setInterval(() => { + if (!carryOn) { + window.clearInterval(interval); + return; + } + + // Compute an arbitrary value, print it out to make sure that the JS + // engine doesn't discard all our computation. + var date = Date.now(); + var array = []; + var i = 0; + while (Date.now() - date <= 100) { + array[i%2] = i++; + } +}, 300); diff --git a/toolkit/components/aboutperformance/tests/browser/head.js b/toolkit/components/aboutperformance/tests/browser/head.js new file mode 100644 index 000000000..a15536ffd --- /dev/null +++ b/toolkit/components/aboutperformance/tests/browser/head.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +var { utils: Cu, interfaces: Ci, classes: Cc } = Components; + +Cu.import("resource://gre/modules/Services.jsm", this); + +function promiseContentResponse(browser, name, message) { + let mm = browser.messageManager; + let promise = new Promise(resolve => { + function removeListener() { + mm.removeMessageListener(name, listener); + } + + function listener(msg) { + removeListener(); + resolve(msg.data); + } + + mm.addMessageListener(name, listener); + registerCleanupFunction(removeListener); + }); + mm.sendAsyncMessage(name, message); + return promise; +} +function promiseContentResponseOrNull(browser, name, message) { + if (!browser.messageManager) { + return null; + } + return promiseContentResponse(browser, name, message); +} + +/** + * `true` if we are running an OS in which the OS performance + * clock has a low precision and might unpredictably + * never be updated during the execution of the test. + */ +function hasLowPrecision() { + let [sysName, sysVersion] = [Services.sysinfo.getPropertyAsAString("name"), Services.sysinfo.getPropertyAsDouble("version")]; + info(`Running ${sysName} version ${sysVersion}`); + + if (sysName == "Windows_NT" && sysVersion < 6) { + info("Running old Windows, need to deactivate tests due to bad precision."); + return true; + } + if (sysName == "Linux" && sysVersion <= 2.6) { + info("Running old Linux, need to deactivate tests due to bad precision."); + return true; + } + info("This platform has good precision.") + return false; +} |