From d3e491db1d5779bdc0904c21433e80e0e2dc1ff3 Mon Sep 17 00:00:00 2001 From: Noemi Erli Date: Wed, 9 Jan 2019 16:59:28 +0200 Subject: [PATCH] Backed out changeset 86cbcf2195f9 (bug 1356036) for browser-chrome failures in browser_all_files_referenced.js CLOSED TREE --- .../static/browser_all_files_referenced.js | 3 + .../perfmonitoring/PerformanceStats.jsm | 3 + .../PerformanceWatcher-content.js | 46 +++ .../perfmonitoring/PerformanceWatcher.jsm | 325 ++++++++++++++++++ toolkit/components/perfmonitoring/moz.build | 2 + .../perfmonitoring/tests/browser/browser.ini | 1 + .../browser_webpagePerformanceAlerts.js | 114 ++++++ .../perfmonitoring/tests/browser/head.js | 187 ++++++++++ 8 files changed, 681 insertions(+) create mode 100644 toolkit/components/perfmonitoring/PerformanceWatcher-content.js create mode 100644 toolkit/components/perfmonitoring/PerformanceWatcher.jsm create mode 100644 toolkit/components/perfmonitoring/tests/browser/browser_webpagePerformanceAlerts.js diff --git a/browser/base/content/test/static/browser_all_files_referenced.js b/browser/base/content/test/static/browser_all_files_referenced.js index 255b45c08d25..d72edde97594 100644 --- a/browser/base/content/test/static/browser_all_files_referenced.js +++ b/browser/base/content/test/static/browser_all_files_referenced.js @@ -154,6 +154,9 @@ var whitelist = [ {file: "resource://gre/modules/PerfMeasurement.jsm"}, // Bug 1356045 {file: "chrome://global/content/test-ipc.xul"}, + // Bug 1356036 + {file: "resource://gre/modules/PerformanceWatcher-content.js"}, + {file: "resource://gre/modules/PerformanceWatcher.jsm"}, // Bug 1378173 (warning: still used by devtools) {file: "resource://gre/modules/Promise.jsm"}, // Still used by WebIDE, which is going away but not entirely gone. diff --git a/toolkit/components/perfmonitoring/PerformanceStats.jsm b/toolkit/components/perfmonitoring/PerformanceStats.jsm index b1b37541bd19..d08160d57a08 100644 --- a/toolkit/components/perfmonitoring/PerformanceStats.jsm +++ b/toolkit/components/perfmonitoring/PerformanceStats.jsm @@ -17,6 +17,9 @@ var EXPORTED_SYMBOLS = ["PerformanceStats"]; * * Data is collected by "Performance Group". Typically, a Performance Group * is a frame, or the internals of the application. + * + * Generally, if you have the choice between PerformanceStats and PerformanceWatcher, + * you should favor PerformanceWatcher. */ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this); diff --git a/toolkit/components/perfmonitoring/PerformanceWatcher-content.js b/toolkit/components/perfmonitoring/PerformanceWatcher-content.js new file mode 100644 index 000000000000..e0cd2834e578 --- /dev/null +++ b/toolkit/components/perfmonitoring/PerformanceWatcher-content.js @@ -0,0 +1,46 @@ +/* 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/. */ + +"use strict"; + +/** + * An API for being informed of slow tabs (content process scripts). + */ + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {}); + +/** + * `true` if this is a content process, `false` otherwise. + */ +let isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT; + +if (isContent) { + +const { PerformanceWatcher } = ChromeUtils.import("resource://gre/modules/PerformanceWatcher.jsm", {}); + +let toMsg = function(alerts) { + let result = []; + for (let {source, details} of alerts) { + // Convert xpcom values to serializable data. + let serializableSource = {}; + for (let k of ["groupId", "name", "windowId", "isSystem", "processId", "isContentProcess"]) { + serializableSource[k] = source[k]; + } + + let serializableDetails = {}; + for (let k of ["reason", "highestJank", "highestCPOW"]) { + serializableDetails[k] = details[k]; + } + result.push({source: serializableSource, details: serializableDetails}); + } + return result; +}; + +PerformanceWatcher.addPerformanceListener({windowId: 0}, alerts => { + Services.cpmm.sendAsyncMessage("performancewatcher-propagate-notifications", + {windows: toMsg(alerts)} + ); +}); + +} diff --git a/toolkit/components/perfmonitoring/PerformanceWatcher.jsm b/toolkit/components/perfmonitoring/PerformanceWatcher.jsm new file mode 100644 index 000000000000..751bd2eac141 --- /dev/null +++ b/toolkit/components/perfmonitoring/PerformanceWatcher.jsm @@ -0,0 +1,325 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- +/* 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/. */ + +"use strict"; + +/** + * An API for being informed of slow tabs. + * + * Generally, this API is both more CPU-efficient and more battery-efficient + * than PerformanceStats. As PerformanceStats, this API does not provide any + * information during the startup or shutdown of Firefox. + * + * = Example = + * + * Example use: reporting whenever any webpage slows down Firefox. + * let listener = function(alerts) { + * // This listener is triggered whenever any window causes Firefox to miss + * // frames. FieldArgument `source` contains information about the source of the + * // slowdown (including the process in which it happens), while `details` + * // contains performance statistics. + * for (let {source, details} of alerts) { + * console.log(`Oops, window ${source.windowId} seems to be slowing down Firefox.`, details); + * }; + * // Special windowId 0 lets us to listen to all webpages. + * PerformanceWatcher.addPerformanceListener({windowId: 0}, listener); + * + * + * = How this works = + * + * This high-level API is based on the lower-level nsIPerformanceStatsService. + * At the end of each event (including micro-tasks), the nsIPerformanceStatsService + * updates its internal performance statistics and determines whether any + * window in the current process has exceeded the jank threshold. + * + * The PerformanceWatcher maintains low-level performance observers in each + * process and forwards alerts to the main process. Internal observers collate + * low-level main process alerts and children process alerts and notify clients + * of this API. + */ + +var EXPORTED_SYMBOLS = ["PerformanceWatcher"]; + +let { PerformanceStats, performanceStatsService } = ChromeUtils.import("resource://gre/modules/PerformanceStats.jsm", {}); +let { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {}); + +// `true` if the code is executed in content, `false` otherwise +let isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT; + +if (!isContent) { + // Initialize communication with children. + // + // To keep the protocol simple, the children inform the parent whenever a slow + // tab is detected. We do not attempt to implement thresholds. + Services.ppmm.loadProcessScript("resource://gre/modules/PerformanceWatcher-content.js", + true/* including future processes*/); + + Services.ppmm.addMessageListener("performancewatcher-propagate-notifications", + (...args) => ChildManager.notifyObservers(...args) + ); +} + +// Configure the performance stats service to inform us in case of jank. +performanceStatsService.jankAlertThreshold = 64000 /* us */; + + +/** + * Handle communications with child processes. Handle listening to + * a single window id (including the special window id 0, which is + * notified for all windows). + * + * Acquire through `ChildManager.getWindow`. + */ +function ChildManager(map, key) { + this.key = key; + this._map = map; + this._listeners = new Set(); +} +ChildManager.prototype = { + /** + * Add a listener, which will be notified whenever a child process + * reports a slow performance alert for this window. + */ + addListener(listener) { + this._listeners.add(listener); + }, + /** + * Remove a listener. + */ + removeListener(listener) { + let deleted = this._listeners.delete(listener); + if (!deleted) { + throw new Error("Unknown listener"); + } + }, + + listeners() { + return this._listeners.values(); + }, +}; + +/** + * Dispatch child alerts to observers. + * + * Triggered by messages from content processes. + */ +ChildManager.notifyObservers = function({data: {windows}}) { + if (windows && windows.length > 0) { + // Dispatch the entire list to universal listeners + this._notify(ChildManager.getWindow(0).listeners(), windows); + + // Dispatch individual alerts to individual listeners + for (let {source, details} of windows) { + this._notify(ChildManager.getWindow(source.windowId).listeners(), source, details); + } + } +}; + +ChildManager._notify = function(targets, ...args) { + for (let target of targets) { + target(...args); + } +}; + +ChildManager.getWindow = function(key) { + return this._get(this._windows, key); +}; +ChildManager._windows = new Map(); + +ChildManager._get = function(map, key) { + let result = map.get(key); + if (!result) { + result = new ChildManager(map, key); + map.set(key, result); + } + return result; +}; + +/** + * An object in charge of managing all the observables for a single + * target (window/all windows). + * + * In a content process, a target is represented by a single observable. + * The situation is more sophisticated in a parent process, as a target + * has both an in-process observable and several observables across children + * processes. + * + * This class abstracts away the difference to simplify the work of + * (un)registering observers for targets. + * + * @param {object} target The target being observed, as an object + * with one of the following fields: + * - {xul:tab} tab A single tab. It must already be initialized. + * - {number} windowId Either 0 for the universal window observer + * or the outer window id of the window. + */ +function Observable(target) { + // A mapping from `listener` (function) to `Observer`. + this._observers = new Map(); + if ("tab" in target || "windowId" in target) { + let windowId; + if ("tab" in target) { + windowId = target.tab.linkedBrowser.outerWindowID; + // By convention, outerWindowID may not be 0. + } else if ("windowId" in target) { + windowId = target.windowId; + } + if (windowId == undefined || windowId == null) { + throw new TypeError(`No outerWindowID. Perhaps the target is a tab that is not initialized yet.`); + } + this._key = `tab-windowId: ${windowId}`; + this._process = performanceStatsService.getObservableWindow(windowId); + this._children = isContent ? null : ChildManager.getWindow(windowId); + this._isBuffered = windowId == 0; + } else { + throw new TypeError("Unexpected target"); + } +} +Observable.prototype = { + addJankObserver(listener) { + if (this._observers.has(listener)) { + throw new TypeError(`Listener already registered for target ${this._key}`); + } + if (this._children) { + this._children.addListener(listener); + } + let observer = this._isBuffered ? new BufferedObserver(listener) + : new Observer(listener); + // Store the observer to be able to call `this._process.removeJankObserver`. + this._observers.set(listener, observer); + + this._process.addJankObserver(observer); + }, + removeJankObserver(listener) { + let observer = this._observers.get(listener); + if (!observer) { + throw new TypeError(`No listener for target ${this._key}`); + } + this._observers.delete(listener); + + if (this._children) { + this._children.removeListener(listener); + } + + this._process.removeJankObserver(observer); + observer.dispose(); + }, +}; + +/** + * Get a cached observable for a given target. + */ +Observable.get = function(target) { + let key; + if ("tab" in target) { + // We do not want to use a tab as a key, as this would prevent it from + // being garbage-collected. + key = target.tab.linkedBrowser.outerWindowID; + } else if ("windowId" in target) { + key = target.windowId; + } + if (key == null) { + throw new TypeError(`Could not extract a key from ${JSON.stringify(target)}. Could the target be an unitialized tab?`); + } + let observable = this._cache.get(key); + if (!observable) { + observable = new Observable(target); + this._cache.set(key, observable); + } + return observable; +}; +Observable._cache = new Map(); + +/** + * Wrap a listener callback as an unbuffered nsIPerformanceObserver. + * + * Each observation is propagated immediately to the listener. + */ +function Observer(listener) { + // Make sure that monitoring stays alive (in all processes) at least as + // long as the observer. + this._monitor = PerformanceStats.getMonitor(["jank", "cpow"]); + this._listener = listener; +} +Observer.prototype = { + observe(...args) { + this._listener(...args); + }, + dispose() { + this._monitor.dispose(); + this.observe = function poison() { + throw new Error("Internal error: I should have stopped receiving notifications"); + }; + }, +}; + +/** + * Wrap a listener callback as an buffered nsIPerformanceObserver. + * + * Observations are buffered and dispatch in the next tick to the listener. + */ +function BufferedObserver(listener) { + Observer.call(this, listener); + this._buffer = []; + this._isDispatching = false; + this._pending = null; +} +BufferedObserver.prototype = Object.create(Observer.prototype); +BufferedObserver.prototype.observe = function(source, details) { + this._buffer.push({source, details}); + if (!this._isDispatching) { + this._isDispatching = true; + Services.tm.dispatchToMainThread(() => { + // Grab buffer, in case something in the listener could modify it. + let buffer = this._buffer; + this._buffer = []; + + // As of this point, any further observations need to use the new buffer + // and a new dispatcher. + this._isDispatching = false; + + this._listener(buffer); + }); + } +}; + +var PerformanceWatcher = { + /** + * Add a listener informed whenever we receive a slow performance alert + * in the application. + * + * @param {object} target An object with one of the following fields: + * - {number} windowId Either 0 to observe all windows or an outer window ID + * to observe a single tab. + * - {xul:browser} tab To observe a single tab. + * @param {function} listener A function that will be triggered whenever + * the target causes a slow performance notification. The notification may + * have originated in any process of the application. + * + * If the listener listens to a single webpage, it is triggered with + * the following arguments: + * source: {groupId, name, windowId, isSystem, processId} + * Information on the source of the notification. + * details: {reason, highestJank, highestCPOW} Information on the + * notification. + * + * If the listener listens to all webpages, it is triggered with + * an array of {source, details}, as described above. + */ + addPerformanceListener(target, listener) { + if (typeof listener != "function") { + throw new TypeError(); + } + let observable = Observable.get(target); + observable.addJankObserver(listener); + }, + removePerformanceListener(target, listener) { + if (typeof listener != "function") { + throw new TypeError(); + } + let observable = Observable.get(target); + observable.removeJankObserver(listener); + }, +}; diff --git a/toolkit/components/perfmonitoring/moz.build b/toolkit/components/perfmonitoring/moz.build index 8505489a5d4f..3e424908a798 100644 --- a/toolkit/components/perfmonitoring/moz.build +++ b/toolkit/components/perfmonitoring/moz.build @@ -14,6 +14,8 @@ XPIDL_MODULE = 'toolkit_perfmonitoring' EXTRA_JS_MODULES += [ 'PerformanceStats-content.js', 'PerformanceStats.jsm', + 'PerformanceWatcher-content.js', + 'PerformanceWatcher.jsm', ] XPIDL_SOURCES += [ diff --git a/toolkit/components/perfmonitoring/tests/browser/browser.ini b/toolkit/components/perfmonitoring/tests/browser/browser.ini index 05c8e9c668e1..a07322408e83 100644 --- a/toolkit/components/perfmonitoring/tests/browser/browser.ini +++ b/toolkit/components/perfmonitoring/tests/browser/browser.ini @@ -8,3 +8,4 @@ support-files = [browser_compartments.js] skip-if = (os == "linux" && !debug && e10s) || (os == "win" && os_version == "10.0") # Bug 1230018, Bug 1409631 +[browser_webpagePerformanceAlerts.js] diff --git a/toolkit/components/perfmonitoring/tests/browser/browser_webpagePerformanceAlerts.js b/toolkit/components/perfmonitoring/tests/browser/browser_webpagePerformanceAlerts.js new file mode 100644 index 000000000000..318198cec979 --- /dev/null +++ b/toolkit/components/perfmonitoring/tests/browser/browser_webpagePerformanceAlerts.js @@ -0,0 +1,114 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +"use strict"; + +/** + * Tests for PerformanceWatcher watching slow web pages. + */ + + /** + * Simulate a slow webpage. + */ +function WebpageBurner() { + CPUBurner.call(this, "http://example.com/browser/toolkit/components/perfmonitoring/tests/browser/browser_compartments.html?test=" + Math.random(), 300000); +} +WebpageBurner.prototype = Object.create(CPUBurner.prototype); +WebpageBurner.prototype.promiseBurnContentCPU = function() { + return promiseContentResponse(this._browser, "test-performance-watcher:burn-content-cpu", {}); +}; + +function WebpageListener(windowId, accept) { + info(`Creating WebpageListener for ${windowId}`); + AlertListener.call(this, accept, { + register: () => PerformanceWatcher.addPerformanceListener({windowId}, this.listener), + unregister: () => PerformanceWatcher.removePerformanceListener({windowId}, this.listener), + }); +} +WebpageListener.prototype = Object.create(AlertListener.prototype); + +add_task(async function init() { + // Get rid of buffering. + let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"].getService( + Ci.nsIPerformanceStatsService); + let oldDelay = service.jankAlertBufferingDelay; + + service.jankAlertBufferingDelay = 0 /* ms */; + registerCleanupFunction(() => { + info("Cleanup"); + service.jankAlertBufferingDelay = oldDelay; + }); +}); + +add_task(async function test_open_window_then_watch_it() { + let burner = new WebpageBurner(); + await burner.promiseInitialized; + await burner.promiseBurnContentCPU(); + + info(`Check that burning CPU triggers the real listener, but not the fake listener`); + let realListener = new WebpageListener(burner.windowId, (group, details) => { + info(`test: realListener for ${burner.tab.linkedBrowser.outerWindowID}: ${group}, ${details}\n`); + Assert.equal(group.windowId, burner.windowId, "We should not receive data meant for another group"); + return details; + }); // This listener should be triggered. + + info(`Creating fake burner`); + let otherTab = BrowserTestUtils.addTab(gBrowser); + await BrowserTestUtils.browserLoaded(otherTab.linkedBrowser); + info(`Check that burning CPU triggers the real listener, but not the fake listener`); + let fakeListener = new WebpageListener(otherTab.linkedBrowser.outerWindowID, group => group.windowId == burner.windowId); // This listener should never be triggered. + let universalListener = new WebpageListener(0, alerts => + alerts.find(alert => alert.source.windowId == burner.windowId) + ); + + // Waiting a little – listeners are buffered. + await new Promise(resolve => setTimeout(resolve, 100)); + + await burner.run("promiseBurnContentCPU", 20, realListener); + Assert.ok(realListener.triggered, `1. The real listener was triggered`); + Assert.ok(universalListener.triggered, `1. The universal listener was triggered`); + Assert.ok(!fakeListener.triggered, `1. The fake listener was not triggered`); + + if (realListener.result) { + Assert.ok(realListener.result.highestJank >= 300, `1. jank is at least 300ms (${realListener.result.highestJank}ms)`); + } + + info(`Attempting to remove a performance listener incorrectly, check that this does not hurt our real listener`); + Assert.throws(() => PerformanceWatcher.removePerformanceListener({windowId: burner.windowId}, () => {}), + /No listener for target/, "should throw an error for a different listener"); + Assert.throws(() => PerformanceWatcher.removePerformanceListener({windowId: burner.windowId + "-unbound-id-" + Math.random()}, realListener.listener), + /No listener for target/, "should throw an error for a different window id"); + + // Waiting a little – listeners are buffered. + await new Promise(resolve => setTimeout(resolve, 100)); + await burner.run("promiseBurnContentCPU", 20, realListener); + // Waiting a little – listeners are buffered. + await new Promise(resolve => setTimeout(resolve, 100)); + + Assert.ok(realListener.triggered, `2. The real listener was triggered`); + Assert.ok(universalListener.triggered, `2. The universal listener was triggered`); + Assert.ok(!fakeListener.triggered, `2. The fake listener was not triggered`); + if (realListener.result) { + Assert.ok(realListener.result.highestJank >= 300, `2. jank is at least 300ms (${realListener.jank}ms)`); + } + + info(`Attempting to remove correctly, check if the listener is still triggered`); + // Waiting a little – listeners are buffered. + await new Promise(resolve => setTimeout(resolve, 100)); + realListener.unregister(); + + // Waiting a little – listeners are buffered. + await new Promise(resolve => setTimeout(resolve, 100)); + await burner.run("promiseBurnContentCPU", 3, realListener); + Assert.ok(!realListener.triggered, `3. After being unregistered, the real listener was not triggered`); + Assert.ok(universalListener.triggered, `3. The universal listener is still triggered`); + + universalListener.unregister(); + + // Waiting a little – listeners are buffered. + await new Promise(resolve => setTimeout(resolve, 100)); + await burner.run("promiseBurnContentCPU", 3, realListener); + Assert.ok(!universalListener.triggered, `4. After being unregistered, the universal listener is not triggered`); + + fakeListener.unregister(); + burner.dispose(); + gBrowser.removeTab(otherTab); +}); diff --git a/toolkit/components/perfmonitoring/tests/browser/head.js b/toolkit/components/perfmonitoring/tests/browser/head.js index 024d9db5f5db..dd02dcaa583a 100644 --- a/toolkit/components/perfmonitoring/tests/browser/head.js +++ b/toolkit/components/perfmonitoring/tests/browser/head.js @@ -4,9 +4,115 @@ /* eslint-env mozilla/frame-script */ ChromeUtils.import("resource://gre/modules/AddonManager.jsm", this); +ChromeUtils.import("resource://gre/modules/PerformanceWatcher.jsm", this); ChromeUtils.import("resource://gre/modules/Services.jsm", this); ChromeUtils.import("resource://testing-common/ContentTaskUtils.jsm", this); +/** + * Base class for simulating slow addons/webpages. + */ +function CPUBurner(url, jankThreshold) { + info(`CPUBurner: Opening tab for ${url}\n`); + this.url = url; + this.tab = BrowserTestUtils.addTab(gBrowser, url); + this.jankThreshold = jankThreshold; + let browser = this.tab.linkedBrowser; + this._browser = browser; + ContentTask.spawn(this._browser, null, CPUBurner.frameScript); + this.promiseInitialized = BrowserTestUtils.browserLoaded(browser); +} +CPUBurner.prototype = { + get windowId() { + return this._browser.outerWindowID; + }, + /** + * Burn CPU until it triggers a listener with the specified jank threshold. + */ + async run(burner, max, listener) { + listener.reset(); + for (let i = 0; i < max; ++i) { + await new Promise(resolve => setTimeout(resolve, 50)); + try { + await this[burner](); + } catch (ex) { + return false; + } + if (listener.triggered && listener.result >= this.jankThreshold) { + return true; + } + } + return false; + }, + dispose() { + info(`CPUBurner: Closing tab for ${this.url}\n`); + gBrowser.removeTab(this.tab); + }, +}; +// This function is injected in all frames +CPUBurner.frameScript = function() { + try { + "use strict"; + + let sandboxes = new Map(); + let getSandbox = function(addonId) { + let sandbox = sandboxes.get(addonId); + if (!sandbox) { + sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), { addonId }); + sandboxes.set(addonId, sandbox); + } + return sandbox; + }; + + let burnCPU = function() { + var start = Date.now(); + var ignored = []; + while (Date.now() - start < 500) { + ignored[ignored.length % 2] = ignored.length; + } + }; + let burnCPUInSandbox = function(addonId) { + let sandbox = getSandbox(addonId); + Cu.evalInSandbox(burnCPU.toSource() + "()", sandbox); + }; + + { + let topic = "test-performance-watcher:burn-content-cpu"; + addMessageListener(topic, function(msg) { + try { + if (msg.data && msg.data.addonId) { + burnCPUInSandbox(msg.data.addonId); + } else { + burnCPU(); + } + sendAsyncMessage(topic, {}); + } catch (ex) { + dump(`This is the content attempting to burn CPU: error ${ex}\n`); + dump(`${ex.stack}\n`); + } + }); + } + + // Bind the function to the global context or it might be GC'd during test + // causing failures (bug 1230027) + this.burnCPOWInSandbox = function(addonId) { + try { + burnCPUInSandbox(addonId); + } catch (ex) { + dump(`This is the addon attempting to burn CPOW: error ${ex}\n`); + dump(`${ex.stack}\n`); + } + }; + + sendAsyncMessage("test-performance-watcher:cpow-init", {}, { + burnCPOWInSandbox: this.burnCPOWInSandbox, + }); + + } catch (ex) { + Cu.reportError("This is the addon: error " + ex); + Cu.reportError(ex.stack); + } +}; + /** * Base class for listening to slow group alerts */ @@ -49,6 +155,87 @@ AlertListener.prototype = { }, }; +/** + * Simulate a slow add-on. + */ +function AddonBurner(addonId = "fake add-on id: " + Math.random()) { + this.jankThreshold = 200000; + CPUBurner.call(this, `http://example.com/?uri=${addonId}`, this.jankThreshold); + this._addonId = addonId; + this._sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), { addonId: this._addonId }); + this._CPOWBurner = null; + + this._promiseCPOWBurner = new Promise(resolve => { + this._browser.messageManager.addMessageListener("test-performance-watcher:cpow-init", msg => { + // Note that we cannot resolve Promises with CPOWs now that they + // have been outlawed in bug 1233497, so we stash it in the + // AddonBurner instance instead. + this._CPOWBurner = msg.objects.burnCPOWInSandbox; + resolve(); + }); + }); +} +AddonBurner.prototype = Object.create(CPUBurner.prototype); +Object.defineProperty(AddonBurner.prototype, "addonId", { + get() { + return this._addonId; + }, +}); + +/** + * Simulate slow code being executed by the add-on in the chrome. + */ +AddonBurner.prototype.burnCPU = function() { + Cu.evalInSandbox(AddonBurner.burnCPU.toSource() + "()", this._sandbox); +}; + +/** + * Simulate slow code being executed by the add-on in a CPOW. + */ +AddonBurner.prototype.promiseBurnCPOW = async function() { + await this._promiseCPOWBurner; + ok(this._CPOWBurner, "Got the CPOW burner"); + let burner = this._CPOWBurner; + info("Parent: Preparing to burn CPOW"); + try { + await burner(this._addonId); + info("Parent: Done burning CPOW"); + } catch (ex) { + info(`Parent: Error burning CPOW: ${ex}\n`); + info(ex.stack + "\n"); + } +}; + +/** + * Simulate slow code being executed by the add-on in the content. + */ +AddonBurner.prototype.promiseBurnContentCPU = function() { + return promiseContentResponse(this._browser, "test-performance-watcher:burn-content-cpu", {addonId: this._addonId}); +}; +AddonBurner.burnCPU = function() { + var start = Date.now(); + var ignored = []; + while (Date.now() - start < 500) { + ignored[ignored.length % 2] = ignored.length; + } +}; + + +function AddonListener(addonId, accept) { + let target = {addonId}; + AlertListener.call(this, accept, { + register: () => { + info(`AddonListener: registering ${JSON.stringify(target, null, "\t")}`); + PerformanceWatcher.addPerformanceListener({addonId}, this.listener); + }, + unregister: () => { + info(`AddonListener: unregistering ${JSON.stringify(target, null, "\t")}`); + PerformanceWatcher.removePerformanceListener({addonId}, this.listener); + }, + }); +} +AddonListener.prototype = Object.create(AlertListener.prototype); + function promiseContentResponse(browser, name, message) { let mm = browser.messageManager; let promise = new Promise(resolve => {