1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 const Cm = Components.manager;
6 Cm.QueryInterface(Ci.nsIServiceManager);
8 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
10 let firstPaintNotification = "widget-first-paint";
11 // widget-first-paint fires much later than expected on Linux.
13 AppConstants.platform == "linux" ||
14 Services.prefs.getBoolPref("browser.startup.preXulSkeletonUI", false)
16 firstPaintNotification = "xul-window-visible";
21 let afterPaintListener = () => {
23 canvas.width = width = win.innerWidth;
24 canvas.height = height = win.innerHeight;
25 if (width < 1 || height < 1) {
28 let ctx = canvas.getContext("2d", { alpha: false, willReadFrequently: true });
37 ctx.DRAWWINDOW_DO_NOT_FLUSH |
38 ctx.DRAWWINDOW_DRAW_VIEW |
39 ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES |
40 ctx.DRAWWINDOW_USE_WIDGET_LAYERS
43 data: ctx.getImageData(0, 0, width, height).data,
50 * The StartupRecorder component observes notifications at various stages of
51 * startup and records the set of JS modules that were already loaded at
52 * each of these points.
53 * The records are meant to be used by startup tests in
54 * browser/base/content/test/performance
55 * This component only exists in nightly and debug builds, it doesn't ship in
58 export function StartupRecorder() {
59 this.wrappedJSObject = this;
62 "image-drawing": new Set(),
63 "image-loading": new Set(),
69 this.done = new Promise(resolve => {
70 this._resolve = resolve;
74 StartupRecorder.prototype = {
75 QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
78 ChromeUtils.addProfilerMarker("startupRecorder:" + name);
79 this.data.code[name] = {
80 modules: Cu.loadedJSModules.concat(Cu.loadedESModules),
81 services: Object.keys(Cc).filter(c => {
83 return Cm.isServiceInstantiatedByContractID(c, Ci.nsISupports);
89 this.data.extras[name] = {
90 hiddenWindowLoaded: Services.appShell.hasHiddenWindow,
94 observe(subject, topic, data) {
95 if (topic == "app-startup" || topic == "content-process-ready-for-script") {
96 // Don't do anything in xpcshell.
97 if (Services.appinfo.ID != "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") {
102 !Services.prefs.getBoolPref("browser.startup.record", false) &&
103 !Services.prefs.getBoolPref("browser.startup.recordImages", false)
106 this._resolve = null;
110 // We can't ensure our observer will be called first or last, so the list of
111 // topics we observe here should avoid the topics used to trigger things
112 // during startup (eg. the topics observed by BrowserGlue.sys.mjs).
114 "profile-do-change", // This catches stuff loaded during app-startup
115 "toplevel-window-ready", // Catches stuff from final-ui-startup
116 firstPaintNotification,
117 "sessionstore-windows-restored",
118 "browser-startup-idle-tasks-finished",
121 if (Services.prefs.getBoolPref("browser.startup.recordImages", false)) {
122 // For code simplicify, recording images excludes the other startup
123 // recorder behaviors, so we can observe only the image topics.
127 "browser-startup-idle-tasks-finished",
130 for (let t of topics) {
131 Services.obs.addObserver(this, t);
136 // We only care about the first paint notification for browser windows, and
137 // not other types (for example, the gfx sanity test window)
138 if (topic == firstPaintNotification) {
139 // In the case we're handling xul-window-visible, we'll have been handed
140 // an nsIAppWindow instead of an nsIDOMWindow.
141 if (subject instanceof Ci.nsIAppWindow) {
143 .QueryInterface(Ci.nsIInterfaceRequestor)
144 .getInterface(Ci.nsIDOMWindow);
148 subject.document.documentElement.getAttribute("windowtype") !=
155 if (topic == "image-drawing" || topic == "image-loading") {
156 this.data.images[topic].add(data);
160 Services.obs.removeObserver(this, topic);
162 if (topic == firstPaintNotification) {
163 // Because of the check for navigator:browser we made earlier, we know
164 // that if we got here, then the subject must be the first browser window.
166 canvas = win.document.createElementNS(
167 "http://www.w3.org/1999/xhtml",
170 canvas.mozOpaque = true;
171 afterPaintListener();
172 win.addEventListener("MozAfterPaint", afterPaintListener);
175 if (topic == "sessionstore-windows-restored") {
176 // We use idleDispatchToMainThread here to record the set of
177 // loaded scripts after we are fully done with startup and ready
178 // to react to user events.
179 Services.tm.dispatchToMainThread(
180 this.record.bind(this, "before handling user events")
182 } else if (topic == "browser-startup-idle-tasks-finished") {
183 if (Services.prefs.getBoolPref("browser.startup.recordImages", false)) {
184 Services.obs.removeObserver(this, "image-drawing");
185 Services.obs.removeObserver(this, "image-loading");
187 this._resolve = null;
191 this.record("before becoming idle");
192 win.removeEventListener("MozAfterPaint", afterPaintListener);
194 this.data.frames = paints;
195 this.data.prefStats = {};
196 if (AppConstants.DEBUG) {
197 Services.prefs.readStats(
198 (key, value) => (this.data.prefStats[key] = value)
203 if (!Services.env.exists("MOZ_PROFILER_STARTUP_PERFORMANCE_TEST")) {
205 this._resolve = null;
209 Services.profiler.getProfileDataAsync().then(profileData => {
210 this.data.profile = profileData;
211 // There's no equivalent StartProfiler call in this file because the
212 // profiler is started using the MOZ_PROFILER_STARTUP environment
213 // variable in browser/base/content/test/performance/browser.ini
214 Services.profiler.StopProfiler();
217 this._resolve = null;
220 const topicsToNames = {
221 "profile-do-change": "before profile selection",
222 "toplevel-window-ready": "before opening first browser window",
224 topicsToNames[firstPaintNotification] = "before first paint";
225 this.record(topicsToNames[topic]);