forked from mirrors/gecko-dev
Backed out changeset 7a0696cf87a3 (bug 1629824) Backed out changeset ed3dbaaaf6da (bug 1637603) Backed out changeset a836a04739ec (bug 1580390) Backed out changeset 0bc8e4705f31 (bug 1637603)
234 lines
7.2 KiB
JavaScript
234 lines
7.2 KiB
JavaScript
/* 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 Cm = Components.manager;
|
|
Cm.QueryInterface(Ci.nsIServiceManager);
|
|
|
|
const { XPCOMUtils } = ChromeUtils.import(
|
|
"resource://gre/modules/XPCOMUtils.jsm"
|
|
);
|
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
const { AppConstants } = ChromeUtils.import(
|
|
"resource://gre/modules/AppConstants.jsm"
|
|
);
|
|
|
|
let firstPaintNotification = "widget-first-paint";
|
|
// widget-first-paint fires much later than expected on Linux.
|
|
if (AppConstants.platform == "linux") {
|
|
firstPaintNotification = "xul-window-visible";
|
|
}
|
|
|
|
let win, canvas;
|
|
let paints = [];
|
|
let afterPaintListener = () => {
|
|
let width, height;
|
|
canvas.width = width = win.innerWidth;
|
|
canvas.height = height = win.innerHeight;
|
|
if (width < 1 || height < 1) {
|
|
return;
|
|
}
|
|
let ctx = canvas.getContext("2d", { alpha: false, willReadFrequently: true });
|
|
|
|
ctx.drawWindow(
|
|
win,
|
|
0,
|
|
0,
|
|
width,
|
|
height,
|
|
"white",
|
|
ctx.DRAWWINDOW_DO_NOT_FLUSH |
|
|
ctx.DRAWWINDOW_DRAW_VIEW |
|
|
ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES |
|
|
ctx.DRAWWINDOW_USE_WIDGET_LAYERS
|
|
);
|
|
paints.push({
|
|
data: ctx.getImageData(0, 0, width, height).data,
|
|
width,
|
|
height,
|
|
});
|
|
};
|
|
|
|
/**
|
|
* The startupRecorder component observes notifications at various stages of
|
|
* startup and records the set of JS components and modules that were already
|
|
* loaded at each of these points.
|
|
* The records are meant to be used by startup tests in
|
|
* browser/base/content/test/performance
|
|
* This component only exists in nightly and debug builds, it doesn't ship in
|
|
* our release builds.
|
|
*/
|
|
function startupRecorder() {
|
|
this.wrappedJSObject = this;
|
|
this.data = {
|
|
images: {
|
|
"image-drawing": new Set(),
|
|
"image-loading": new Set(),
|
|
},
|
|
code: {},
|
|
extras: {},
|
|
prefStats: {},
|
|
};
|
|
this.done = new Promise(resolve => {
|
|
this._resolve = resolve;
|
|
});
|
|
}
|
|
startupRecorder.prototype = {
|
|
classID: Components.ID("{11c095b2-e42e-4bdf-9dd0-aed87595f6a4}"),
|
|
|
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
|
|
|
|
record(name) {
|
|
if (!Services.prefs.getBoolPref("browser.startup.record", false)) {
|
|
return;
|
|
}
|
|
|
|
Services.profiler.AddMarker("startupRecorder:" + name);
|
|
this.data.code[name] = {
|
|
components: Cu.loadedComponents,
|
|
modules: Cu.loadedModules,
|
|
services: Object.keys(Cc).filter(c => {
|
|
try {
|
|
return Cm.isServiceInstantiatedByContractID(c, Ci.nsISupports);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}),
|
|
};
|
|
this.data.extras[name] = {
|
|
hiddenWindowLoaded: Services.appShell.hasHiddenWindow,
|
|
};
|
|
},
|
|
|
|
observe(subject, topic, data) {
|
|
if (topic == "app-startup") {
|
|
// We can't ensure our observer will be called first or last, so the list of
|
|
// topics we observe here should avoid the topics used to trigger things
|
|
// during startup (eg. the topics observed by BrowserGlue.jsm).
|
|
let topics = [
|
|
"profile-do-change", // This catches stuff loaded during app-startup
|
|
"toplevel-window-ready", // Catches stuff from final-ui-startup
|
|
"image-loading",
|
|
"image-drawing",
|
|
firstPaintNotification,
|
|
"sessionstore-windows-restored",
|
|
];
|
|
for (let t of topics) {
|
|
Services.obs.addObserver(this, t);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// We only care about the first paint notification for browser windows, and
|
|
// not other types (for example, the gfx sanity test window)
|
|
if (topic == firstPaintNotification) {
|
|
// In the case we're handling xul-window-visible, we'll have been handed
|
|
// an nsIAppWindow instead of an nsIDOMWindow.
|
|
if (subject instanceof Ci.nsIAppWindow) {
|
|
subject = subject
|
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIDOMWindow);
|
|
}
|
|
|
|
if (
|
|
subject.document.documentElement.getAttribute("windowtype") !=
|
|
"navigator:browser"
|
|
) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (topic == "image-drawing" || topic == "image-loading") {
|
|
this.data.images[topic].add(data);
|
|
return;
|
|
}
|
|
|
|
Services.obs.removeObserver(this, topic);
|
|
|
|
if (
|
|
topic == firstPaintNotification &&
|
|
Services.prefs.getBoolPref("browser.startup.record", false)
|
|
) {
|
|
// Because of the check for navigator:browser we made earlier, we know
|
|
// that if we got here, then the subject must be the first browser window.
|
|
win = subject;
|
|
canvas = win.document.createElementNS(
|
|
"http://www.w3.org/1999/xhtml",
|
|
"canvas"
|
|
);
|
|
canvas.mozOpaque = true;
|
|
afterPaintListener();
|
|
win.addEventListener("MozAfterPaint", afterPaintListener);
|
|
}
|
|
|
|
if (topic == "sessionstore-windows-restored") {
|
|
if (!Services.prefs.getBoolPref("browser.startup.record", false)) {
|
|
this._resolve();
|
|
this._resolve = null;
|
|
return;
|
|
}
|
|
|
|
// We use idleDispatchToMainThread here to record the set of
|
|
// loaded scripts after we are fully done with startup and ready
|
|
// to react to user events.
|
|
Services.tm.dispatchToMainThread(
|
|
this.record.bind(this, "before handling user events")
|
|
);
|
|
|
|
// 10 is an arbitrary value here, it needs to be at least 2 to avoid
|
|
// races with code initializing itself using idle callbacks.
|
|
(function waitForIdle(callback, count = 10) {
|
|
if (count) {
|
|
Services.tm.idleDispatchToMainThread(() =>
|
|
waitForIdle(callback, count - 1)
|
|
);
|
|
} else {
|
|
callback();
|
|
}
|
|
})(() => {
|
|
this.record("before becoming idle");
|
|
Services.obs.removeObserver(this, "image-drawing");
|
|
Services.obs.removeObserver(this, "image-loading");
|
|
win.removeEventListener("MozAfterPaint", afterPaintListener);
|
|
win = null;
|
|
this.data.frames = paints;
|
|
this.data.prefStats = {};
|
|
if (AppConstants.DEBUG) {
|
|
Services.prefs.readStats(
|
|
(key, value) => (this.data.prefStats[key] = value)
|
|
);
|
|
}
|
|
paints = null;
|
|
|
|
let env = Cc["@mozilla.org/process/environment;1"].getService(
|
|
Ci.nsIEnvironment
|
|
);
|
|
if (!env.exists("MOZ_PROFILER_STARTUP")) {
|
|
this._resolve();
|
|
this._resolve = null;
|
|
return;
|
|
}
|
|
|
|
Services.profiler.getProfileDataAsync().then(profileData => {
|
|
this.data.profile = profileData;
|
|
// There's no equivalent StartProfiler call in this file because the
|
|
// profiler is started using the MOZ_PROFILER_STARTUP environment
|
|
// variable in browser/base/content/test/performance/browser.ini
|
|
Services.profiler.StopProfiler();
|
|
|
|
this._resolve();
|
|
this._resolve = null;
|
|
});
|
|
});
|
|
} else {
|
|
const topicsToNames = {
|
|
"profile-do-change": "before profile selection",
|
|
"toplevel-window-ready": "before opening first browser window",
|
|
};
|
|
topicsToNames[firstPaintNotification] = "before first paint";
|
|
this.record(topicsToNames[topic]);
|
|
}
|
|
},
|
|
};
|
|
|
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([startupRecorder]);
|