зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset 86cbcf2195f9 (bug 1356036) for browser-chrome failures in browser_all_files_referenced.js CLOSED TREE
This commit is contained in:
Родитель
e490d97127
Коммит
d3e491db1d
|
@ -154,6 +154,9 @@ var whitelist = [
|
||||||
{file: "resource://gre/modules/PerfMeasurement.jsm"},
|
{file: "resource://gre/modules/PerfMeasurement.jsm"},
|
||||||
// Bug 1356045
|
// Bug 1356045
|
||||||
{file: "chrome://global/content/test-ipc.xul"},
|
{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)
|
// Bug 1378173 (warning: still used by devtools)
|
||||||
{file: "resource://gre/modules/Promise.jsm"},
|
{file: "resource://gre/modules/Promise.jsm"},
|
||||||
// Still used by WebIDE, which is going away but not entirely gone.
|
// Still used by WebIDE, which is going away but not entirely gone.
|
||||||
|
|
|
@ -17,6 +17,9 @@ var EXPORTED_SYMBOLS = ["PerformanceStats"];
|
||||||
*
|
*
|
||||||
* Data is collected by "Performance Group". Typically, a Performance Group
|
* Data is collected by "Performance Group". Typically, a Performance Group
|
||||||
* is a frame, or the internals of the application.
|
* 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);
|
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||||
|
|
|
@ -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)}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
},
|
||||||
|
};
|
|
@ -14,6 +14,8 @@ XPIDL_MODULE = 'toolkit_perfmonitoring'
|
||||||
EXTRA_JS_MODULES += [
|
EXTRA_JS_MODULES += [
|
||||||
'PerformanceStats-content.js',
|
'PerformanceStats-content.js',
|
||||||
'PerformanceStats.jsm',
|
'PerformanceStats.jsm',
|
||||||
|
'PerformanceWatcher-content.js',
|
||||||
|
'PerformanceWatcher.jsm',
|
||||||
]
|
]
|
||||||
|
|
||||||
XPIDL_SOURCES += [
|
XPIDL_SOURCES += [
|
||||||
|
|
|
@ -8,3 +8,4 @@ support-files =
|
||||||
|
|
||||||
[browser_compartments.js]
|
[browser_compartments.js]
|
||||||
skip-if = (os == "linux" && !debug && e10s) || (os == "win" && os_version == "10.0") # Bug 1230018, Bug 1409631
|
skip-if = (os == "linux" && !debug && e10s) || (os == "win" && os_version == "10.0") # Bug 1230018, Bug 1409631
|
||||||
|
[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);
|
||||||
|
});
|
|
@ -4,9 +4,115 @@
|
||||||
/* eslint-env mozilla/frame-script */
|
/* eslint-env mozilla/frame-script */
|
||||||
|
|
||||||
ChromeUtils.import("resource://gre/modules/AddonManager.jsm", this);
|
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://gre/modules/Services.jsm", this);
|
||||||
ChromeUtils.import("resource://testing-common/ContentTaskUtils.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
|
* 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) {
|
function promiseContentResponse(browser, name, message) {
|
||||||
let mm = browser.messageManager;
|
let mm = browser.messageManager;
|
||||||
let promise = new Promise(resolve => {
|
let promise = new Promise(resolve => {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче