fune/layout/tools/layout-debug/ui/content/layoutdebug.js
Cristina Horotan 5f4356e527 Backed out 9 changesets (bug 1810141) for several test failures on a CLOSED TREE
Backed out changeset 8781a0d1254d (bug 1810141)
Backed out changeset 131037295784 (bug 1810141)
Backed out changeset 3852fbe290f4 (bug 1810141)
Backed out changeset 118f131a524a (bug 1810141)
Backed out changeset ab5d76846e10 (bug 1810141)
Backed out changeset dce3aa683445 (bug 1810141)
Backed out changeset 4dc41d90dbb3 (bug 1810141)
Backed out changeset 50b57ba1a061 (bug 1810141)
Backed out changeset 569de94781e4 (bug 1810141)
2023-02-13 16:05:30 +02:00

503 lines
14 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/. */
var gArgs;
var gBrowser;
var gURLBar;
var gDebugger;
var gMultiProcessBrowser = window.docShell.QueryInterface(Ci.nsILoadContext)
.useRemoteTabs;
var gFissionBrowser = window.docShell.QueryInterface(Ci.nsILoadContext)
.useRemoteSubframes;
var gWritingProfile = false;
var gWrittenProfile = false;
const { E10SUtils } = ChromeUtils.importESModule(
"resource://gre/modules/E10SUtils.sys.mjs"
);
const { Preferences } = ChromeUtils.importESModule(
"resource://gre/modules/Preferences.sys.mjs"
);
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
BrowserToolboxLauncher:
"resource://devtools/client/framework/browser-toolbox/Launcher.sys.mjs",
});
const FEATURES = {
paintDumping: "nglayout.debug.paint_dumping",
invalidateDumping: "nglayout.debug.invalidate_dumping",
eventDumping: "nglayout.debug.event_dumping",
motionEventDumping: "nglayout.debug.motion_event_dumping",
crossingEventDumping: "nglayout.debug.crossing_event_dumping",
reflowCounts: "layout.reflow.showframecounts",
};
const COMMANDS = [
"dumpContent",
"dumpFrames",
"dumpFramesInCSSPixels",
"dumpTextRuns",
"dumpViews",
"dumpCounterManager",
"dumpStyleSheets",
"dumpMatchedRules",
"dumpComputedStyles",
"dumpReflowStats",
];
class Debugger {
constructor() {
this._flags = new Map();
this._pagedMode = false;
this._attached = false;
for (let [name, pref] of Object.entries(FEATURES)) {
this._flags.set(name, !!Preferences.get(pref, false));
}
this.attachBrowser();
}
detachBrowser() {
if (!this._attached) {
return;
}
gBrowser.removeProgressListener(this._progressListener);
this._progressListener = null;
this._attached = false;
}
attachBrowser() {
if (this._attached) {
throw "already attached";
}
this._progressListener = new nsLDBBrowserContentListener();
gBrowser.addProgressListener(this._progressListener);
this._attached = true;
}
dumpProcessIDs() {
let parentPid = Services.appinfo.processID;
let [contentPid, ...framePids] = E10SUtils.getBrowserPids(
gBrowser,
gFissionBrowser
);
dump(`Parent pid: ${parentPid}\n`);
dump(`Content pid: ${contentPid || "-"}\n`);
if (gFissionBrowser) {
dump(`Subframe pids: ${framePids.length ? framePids.join(", ") : "-"}\n`);
}
}
get pagedMode() {
return this._pagedMode;
}
set pagedMode(v) {
v = !!v;
this._pagedMode = v;
this.setPagedMode(this._pagedMode);
}
setPagedMode(v) {
this._sendMessage("setPagedMode", v);
}
openDevTools() {
lazy.BrowserToolboxLauncher.init();
}
async _sendMessage(name, arg) {
await this._sendMessageTo(gBrowser.browsingContext, name, arg);
}
async _sendMessageTo(context, name, arg) {
let global = context.currentWindowGlobal;
if (global) {
await global
.getActor("LayoutDebug")
.sendQuery("LayoutDebug:Call", { name, arg });
}
for (let c of context.children) {
await this._sendMessageTo(c, name, arg);
}
}
}
for (let [name, pref] of Object.entries(FEATURES)) {
Object.defineProperty(Debugger.prototype, name, {
get: function() {
return this._flags.get(name);
},
set: function(v) {
v = !!v;
Preferences.set(pref, v);
this._flags.set(name, v);
// XXX PresShell should watch for this pref change itself.
if (name == "reflowCounts") {
this._sendMessage("setReflowCounts", v);
}
this._sendMessage("forceRefresh");
},
});
}
for (let name of COMMANDS) {
Debugger.prototype[name] = function() {
this._sendMessage(name);
};
}
function autoCloseIfNeeded(aCrash) {
if (!gArgs.autoclose) {
return;
}
setTimeout(function() {
if (aCrash) {
let browser = document.createXULElement("browser");
// FIXME(emilio): we could use gBrowser if we bothered get the process switches right.
//
// Doesn't seem worth for this particular case.
document.documentElement.appendChild(browser);
browser.loadURI("about:crashparent", {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
});
return;
}
if (gArgs.profile && Services.profiler) {
dumpProfile();
} else {
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
}
}, gArgs.delay * 1000);
}
function nsLDBBrowserContentListener() {
this.init();
}
nsLDBBrowserContentListener.prototype = {
init: function() {
this.mStatusText = document.getElementById("status-text");
this.mForwardButton = document.getElementById("forward-button");
this.mBackButton = document.getElementById("back-button");
this.mStopButton = document.getElementById("stop-button");
},
QueryInterface: ChromeUtils.generateQI([
"nsIWebProgressListener",
"nsISupportsWeakReference",
]),
// nsIWebProgressListener implementation
onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) {
return;
}
if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
this.setButtonEnabled(this.mStopButton, true);
this.setButtonEnabled(this.mForwardButton, gBrowser.canGoForward);
this.setButtonEnabled(this.mBackButton, gBrowser.canGoBack);
this.mStatusText.value = "loading...";
this.mLoading = true;
} else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
this.setButtonEnabled(this.mStopButton, false);
this.mStatusText.value = gURLBar.value + " loaded";
this.mLoading = false;
if (gDebugger.pagedMode) {
// Change to paged mode after the page is loaded.
gDebugger.setPagedMode(true);
}
if (gBrowser.currentURI.spec != "about:blank") {
// We check for about:blank just to avoid one or two STATE_STOP
// notifications that occur before the loadURI() call completes.
// This does mean that --autoclose doesn't work when the URL on
// the command line is about:blank (or not specified), but that's
// not a big deal.
autoCloseIfNeeded(false);
}
}
},
onProgressChange: function(
aWebProgress,
aRequest,
aCurSelfProgress,
aMaxSelfProgress,
aCurTotalProgress,
aMaxTotalProgress
) {},
onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
gURLBar.value = aLocation.spec;
this.setButtonEnabled(this.mForwardButton, gBrowser.canGoForward);
this.setButtonEnabled(this.mBackButton, gBrowser.canGoBack);
},
onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage) {
this.mStatusText.value = aMessage;
},
onSecurityChange: function(aWebProgress, aRequest, aState) {},
onContentBlockingEvent: function(aWebProgress, aRequest, aEvent) {},
// non-interface methods
setButtonEnabled: function(aButtonElement, aEnabled) {
if (aEnabled) {
aButtonElement.removeAttribute("disabled");
} else {
aButtonElement.setAttribute("disabled", "true");
}
},
mStatusText: null,
mForwardButton: null,
mBackButton: null,
mStopButton: null,
mLoading: false,
};
function parseArguments() {
let args = {
url: null,
autoclose: false,
delay: 0,
paged: false,
};
if (window.arguments) {
args.url = window.arguments[0];
for (let i = 1; i < window.arguments.length; ++i) {
let arg = window.arguments[i];
if (/^autoclose=(.*)$/.test(arg)) {
args.autoclose = true;
args.delay = +RegExp.$1;
} else if (/^profile=(.*)$/.test(arg)) {
args.profile = true;
args.profileFilename = RegExp.$1;
} else if (/^paged$/.test(arg)) {
args.paged = true;
} else {
throw `Unknown option ${arg}`;
}
}
}
return args;
}
const TabCrashedObserver = {
observe(subject, topic, data) {
switch (topic) {
case "ipc:content-shutdown":
subject.QueryInterface(Ci.nsIPropertyBag2);
if (!subject.get("abnormal")) {
return;
}
break;
case "oop-frameloader-crashed":
break;
}
autoCloseIfNeeded(true);
},
};
function OnLDBLoad() {
gBrowser = document.getElementById("browser");
gURLBar = document.getElementById("urlbar");
try {
ChromeUtils.registerWindowActor("LayoutDebug", {
child: {
moduleURI: "resource://gre/actors/LayoutDebugChild.jsm",
},
allFrames: true,
});
} catch (ex) {
// Only register the actor once.
}
gDebugger = new Debugger();
Services.obs.addObserver(TabCrashedObserver, "ipc:content-shutdown");
Services.obs.addObserver(TabCrashedObserver, "oop-frameloader-crashed");
// Pretend slightly to be like a normal browser, so that SessionStore.sys.mjs
// doesn't get too confused. The effect is that we'll never switch process
// type when navigating, and for layout debugging purposes we don't bother
// about getting that right.
gBrowser.getTabForBrowser = function() {
return null;
};
gArgs = parseArguments();
if (gArgs.profile) {
if (Services.profiler) {
if (!Services.env.exists("MOZ_PROFILER_SYMBOLICATE")) {
dump(
"Warning: MOZ_PROFILER_SYMBOLICATE environment variable not set; " +
"profile will not be symbolicated.\n"
);
}
Services.profiler.StartProfiler(
1 << 20,
1,
["default"],
["GeckoMain", "Compositor", "Renderer", "RenderBackend", "StyleThread"]
);
if (gArgs.url) {
// Switch to the right kind of content process, and wait a bit so that
// the profiler has had a chance to attach to it.
updateBrowserRemotenessByURL(gArgs.url);
setTimeout(() => loadURI(gArgs.url), 3000);
return;
}
} else {
dump("Cannot profile Layout Debugger; profiler was not compiled in.\n");
}
}
// The URI is not loaded yet. Just set the internal variable.
gDebugger._pagedMode = gArgs.paged;
if (gArgs.url) {
loadURI(gArgs.url);
}
// Some command line arguments may toggle menu items. Call this after
// processing all the arguments.
checkPersistentMenus();
}
function checkPersistentMenu(item) {
var menuitem = document.getElementById("menu_" + item);
menuitem.setAttribute("checked", gDebugger[item]);
}
function checkPersistentMenus() {
// Restore the toggles that are stored in prefs.
checkPersistentMenu("paintDumping");
checkPersistentMenu("invalidateDumping");
checkPersistentMenu("eventDumping");
checkPersistentMenu("motionEventDumping");
checkPersistentMenu("crossingEventDumping");
checkPersistentMenu("reflowCounts");
checkPersistentMenu("pagedMode");
}
function dumpProfile() {
gWritingProfile = true;
let cwd = Services.dirsvc.get("CurWorkD", Ci.nsIFile).path;
let filename = PathUtils.join(cwd, gArgs.profileFilename);
dump(`Writing profile to ${filename}...\n`);
Services.profiler.dumpProfileToFileAsync(filename).then(function() {
gWritingProfile = false;
gWrittenProfile = true;
dump(`done\n`);
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
});
}
function OnLDBBeforeUnload(event) {
if (gArgs.profile && Services.profiler) {
if (gWrittenProfile) {
// We've finished writing the profile. Allow the window to close.
return;
}
event.preventDefault();
if (gWritingProfile) {
// Wait for the profile to finish being written out.
return;
}
// The dumpProfileToFileAsync call can block for a while, so run it off a
// timeout to avoid annoying the window manager if we're doing this in
// response to clicking the window's close button.
setTimeout(dumpProfile, 0);
}
}
function OnLDBUnload() {
gDebugger.detachBrowser();
Services.obs.removeObserver(TabCrashedObserver, "ipc:content-shutdown");
Services.obs.removeObserver(TabCrashedObserver, "oop-frameloader-crashed");
}
function toggle(menuitem) {
// trim the initial "menu_"
var feature = menuitem.id.substring(5);
gDebugger[feature] = menuitem.getAttribute("checked") == "true";
}
function openFile() {
var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, "Select a File", Ci.nsIFilePicker.modeOpen);
fp.appendFilters(Ci.nsIFilePicker.filterHTML | Ci.nsIFilePicker.filterAll);
fp.open(rv => {
if (
rv == Ci.nsIFilePicker.returnOK &&
fp.fileURL.spec &&
fp.fileURL.spec.length > 0
) {
loadURI(fp.fileURL.spec);
}
});
}
// A simplified version of the function with the same name in tabbrowser.js.
function updateBrowserRemotenessByURL(aURL) {
let oa = E10SUtils.predictOriginAttributes({ browser: gBrowser });
let remoteType = E10SUtils.getRemoteTypeForURI(
aURL,
gMultiProcessBrowser,
gFissionBrowser,
gBrowser.remoteType,
gBrowser.currentURI,
oa
);
if (gBrowser.remoteType != remoteType) {
gDebugger.detachBrowser();
if (remoteType == E10SUtils.NOT_REMOTE) {
gBrowser.removeAttribute("remote");
gBrowser.removeAttribute("remoteType");
} else {
gBrowser.setAttribute("remote", "true");
gBrowser.setAttribute("remoteType", remoteType);
}
gBrowser.changeRemoteness({ remoteType });
gBrowser.construct();
gDebugger.attachBrowser();
}
}
function loadURI(aURL) {
// We don't bother trying to handle navigations within the browser to new URLs
// that should be loaded in a different process.
updateBrowserRemotenessByURL(aURL);
gBrowser.loadURI(aURL, {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
});
}
function focusURLBar() {
gURLBar.focus();
gURLBar.select();
}
function go() {
loadURI(gURLBar.value);
gBrowser.focus();
}