summaryrefslogtreecommitdiffstats
path: root/layout/tools/layout-debug
diff options
context:
space:
mode:
Diffstat (limited to 'layout/tools/layout-debug')
-rw-r--r--layout/tools/layout-debug/LayoutDebugChild.sys.mjs29
-rw-r--r--layout/tools/layout-debug/moz.build13
-rw-r--r--layout/tools/layout-debug/src/components.conf21
-rw-r--r--layout/tools/layout-debug/src/moz.build24
-rw-r--r--layout/tools/layout-debug/src/nsILayoutDebuggingTools.idl44
-rw-r--r--layout/tools/layout-debug/src/nsLayoutDebugCLH.cpp187
-rw-r--r--layout/tools/layout-debug/src/nsLayoutDebugCLH.h24
-rw-r--r--layout/tools/layout-debug/src/nsLayoutDebuggingTools.cpp327
-rw-r--r--layout/tools/layout-debug/src/nsLayoutDebuggingTools.h30
-rw-r--r--layout/tools/layout-debug/tests/browser/browser.toml4
-rw-r--r--layout/tools/layout-debug/tests/browser/browser_openLayoutDebug.js41
-rw-r--r--layout/tools/layout-debug/tests/unit/test_componentsRegistered.js6
-rw-r--r--layout/tools/layout-debug/tests/unit/xpcshell.toml4
-rw-r--r--layout/tools/layout-debug/ui/content/layoutdebug.ftl81
-rw-r--r--layout/tools/layout-debug/ui/content/layoutdebug.js520
-rw-r--r--layout/tools/layout-debug/ui/content/layoutdebug.xhtml280
-rw-r--r--layout/tools/layout-debug/ui/jar.mn8
-rw-r--r--layout/tools/layout-debug/ui/moz.build7
18 files changed, 1650 insertions, 0 deletions
diff --git a/layout/tools/layout-debug/LayoutDebugChild.sys.mjs b/layout/tools/layout-debug/LayoutDebugChild.sys.mjs
new file mode 100644
index 0000000000..7a15e36cae
--- /dev/null
+++ b/layout/tools/layout-debug/LayoutDebugChild.sys.mjs
@@ -0,0 +1,29 @@
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
+/* 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 NS_LAYOUT_DEBUGGINGTOOLS_CONTRACTID =
+ "@mozilla.org/layout-debug/layout-debuggingtools;1";
+
+export class LayoutDebugChild extends JSWindowActorChild {
+ receiveMessage(msg) {
+ if (!this._debuggingTools) {
+ this._debuggingTools = Cc[
+ NS_LAYOUT_DEBUGGINGTOOLS_CONTRACTID
+ ].createInstance(Ci.nsILayoutDebuggingTools);
+ this._debuggingTools.init(this.contentWindow);
+ }
+ switch (msg.name) {
+ case "LayoutDebug:Call":
+ let pid = Services.appinfo.processID;
+ dump(`[${pid} ${this.contentWindow.location}]\n`);
+ this._debuggingTools[msg.data.name](msg.data.arg);
+ dump("\n");
+ break;
+ default:
+ throw `unknown message ${msg.name} sent to LayoutDebugChild`;
+ }
+ return Promise.resolve(true);
+ }
+}
diff --git a/layout/tools/layout-debug/moz.build b/layout/tools/layout-debug/moz.build
new file mode 100644
index 0000000000..cc7cf90dd2
--- /dev/null
+++ b/layout/tools/layout-debug/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DIRS += ["src", "ui"]
+
+XPCSHELL_TESTS_MANIFESTS += ["tests/unit/xpcshell.toml"]
+
+BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.toml"]
+
+FINAL_TARGET_FILES.actors += ["LayoutDebugChild.sys.mjs"]
diff --git a/layout/tools/layout-debug/src/components.conf b/layout/tools/layout-debug/src/components.conf
new file mode 100644
index 0000000000..07ad3d155d
--- /dev/null
+++ b/layout/tools/layout-debug/src/components.conf
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{3f4c3b63-e640-4712-abbf-fff1301ceb60}',
+ 'contract_ids': ['@mozilla.org/layout-debug/layout-debuggingtools;1'],
+ 'type': 'nsLayoutDebuggingTools',
+ 'headers': ['/layout/tools/layout-debug/src/nsLayoutDebuggingTools.h'],
+ },
+ {
+ 'cid': '{a8f52633-5ecf-424a-a147-47c322f7bc2e}',
+ 'contract_ids': ['@mozilla.org/commandlinehandler/general-startup;1?type=layoutdebug'],
+ 'type': 'nsLayoutDebugCLH',
+ 'headers': ['/layout/tools/layout-debug/src/nsLayoutDebugCLH.h'],
+ 'categories': {'command-line-handler': 'm-layoutdebug'},
+ },
+]
diff --git a/layout/tools/layout-debug/src/moz.build b/layout/tools/layout-debug/src/moz.build
new file mode 100644
index 0000000000..37f9389fa3
--- /dev/null
+++ b/layout/tools/layout-debug/src/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+ "nsILayoutDebuggingTools.idl",
+]
+
+XPIDL_MODULE = "layout_debug"
+
+UNIFIED_SOURCES += [
+ "nsLayoutDebugCLH.cpp",
+ "nsLayoutDebuggingTools.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/layout/tools/layout-debug/src/nsILayoutDebuggingTools.idl b/layout/tools/layout-debug/src/nsILayoutDebuggingTools.idl
new file mode 100644
index 0000000000..f689734a46
--- /dev/null
+++ b/layout/tools/layout-debug/src/nsILayoutDebuggingTools.idl
@@ -0,0 +1,44 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// vim:cindent:tabstop=4:expandtab:shiftwidth=4:
+/* 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/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindow;
+
+/**
+ * A series of hooks into non-IDL-ized layout code to allow all the
+ * layout debugging functions to be used from chrome.
+ */
+
+[builtinclass, scriptable, uuid(f336d8d3-9721-4ad3-85d0-a7018c0a3383)]
+interface nsILayoutDebuggingTools : nsISupports
+{
+ /*
+ * Initialize debugger object to act on a docshell.
+ */
+ void init(in mozIDOMWindow win);
+
+ // Repaint the window.
+ void forceRefresh();
+
+ /* Toggle various debugging states */
+ void setReflowCounts(in boolean enabled);
+ void setPagedMode(in boolean enabled);
+
+ /* Run various tests. */
+ void dumpContent();
+ void dumpFrames();
+ void dumpFramesInCSSPixels();
+ void dumpTextRuns();
+ void dumpViews();
+ void dumpCounterManager();
+
+ void dumpStyleSheets();
+ void dumpMatchedRules();
+ void dumpComputedStyles();
+
+ void dumpReflowStats();
+};
diff --git a/layout/tools/layout-debug/src/nsLayoutDebugCLH.cpp b/layout/tools/layout-debug/src/nsLayoutDebugCLH.cpp
new file mode 100644
index 0000000000..c50f3af44a
--- /dev/null
+++ b/layout/tools/layout-debug/src/nsLayoutDebugCLH.cpp
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:tabstop=4:expandtab:shiftwidth=4:
+/* 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/. */
+
+#include "nsLayoutDebugCLH.h"
+#include "mozIDOMWindow.h"
+#include "nsArray.h"
+#include "nsString.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIWindowWatcher.h"
+#include "nsISupportsPrimitives.h"
+#include "nsICommandLine.h"
+#include "nsIURI.h"
+#include "nsServiceManagerUtils.h"
+
+nsLayoutDebugCLH::nsLayoutDebugCLH() = default;
+
+nsLayoutDebugCLH::~nsLayoutDebugCLH() = default;
+
+NS_IMPL_ISUPPORTS(nsLayoutDebugCLH, ICOMMANDLINEHANDLER)
+
+static nsresult HandleFlagWithOptionalArgument(nsICommandLine* aCmdLine,
+ const nsAString& aName,
+ const nsAString& aDefaultValue,
+ nsAString& aValue,
+ bool& aFlagPresent) {
+ aValue.Truncate();
+ aFlagPresent = false;
+
+ nsresult rv;
+ int32_t idx;
+
+ rv = aCmdLine->FindFlag(aName, false, &idx);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (idx < 0) return NS_OK;
+
+ aFlagPresent = true;
+
+ int32_t length;
+ aCmdLine->GetLength(&length);
+
+ bool argPresent = false;
+
+ if (idx + 1 < length) {
+ rv = aCmdLine->GetArgument(idx + 1, aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aValue.IsEmpty() && aValue.CharAt(0) == '-') {
+ aValue.Truncate();
+ } else {
+ argPresent = true;
+ }
+ }
+
+ if (!argPresent) {
+ aValue = aDefaultValue;
+ }
+
+ return aCmdLine->RemoveArguments(idx, idx + argPresent);
+}
+
+static nsresult HandleFlagWithOptionalArgument(nsICommandLine* aCmdLine,
+ const nsAString& aName,
+ double aDefaultValue,
+ double& aValue,
+ bool& aFlagPresent) {
+ nsresult rv;
+ nsString s;
+
+ rv =
+ HandleFlagWithOptionalArgument(aCmdLine, aName, u"0"_ns, s, aFlagPresent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aFlagPresent) {
+ aValue = 0.0;
+ return NS_OK;
+ }
+
+ aValue = s.ToDouble(&rv);
+ return rv;
+}
+
+static nsresult AppendArg(nsIMutableArray* aArray, const nsAString& aString) {
+ nsCOMPtr<nsISupportsString> s =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
+ NS_ENSURE_TRUE(s, NS_ERROR_FAILURE);
+ s->SetData(aString);
+ return aArray->AppendElement(s);
+}
+
+NS_IMETHODIMP
+nsLayoutDebugCLH::Handle(nsICommandLine* aCmdLine) {
+ nsresult rv;
+ bool flagPresent;
+
+ nsString url;
+ bool autoclose = false;
+ double delay = 0.0;
+ bool captureProfile = false;
+ nsString profileFilename;
+ bool paged = false;
+
+ rv = HandleFlagWithOptionalArgument(aCmdLine, u"layoutdebug"_ns,
+ u"about:blank"_ns, url, flagPresent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!flagPresent) {
+ return NS_OK;
+ }
+
+ rv = HandleFlagWithOptionalArgument(aCmdLine, u"autoclose"_ns, 0.0, delay,
+ autoclose);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = HandleFlagWithOptionalArgument(aCmdLine, u"capture-profile"_ns,
+ u"profile.json"_ns, profileFilename,
+ captureProfile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aCmdLine->HandleFlag(u"paged"_ns, false, &paged);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> argsArray = nsArray::Create();
+
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString resolvedSpec;
+
+ rv = aCmdLine->ResolveURI(url, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = uri->GetSpec(resolvedSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AppendArg(argsArray, NS_ConvertUTF8toUTF16(resolvedSpec));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (autoclose) {
+ nsString arg;
+ arg.AppendPrintf("autoclose=%f", delay);
+
+ rv = AppendArg(argsArray, arg);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (captureProfile) {
+ nsString arg;
+ arg.AppendLiteral("profile=");
+ arg.Append(profileFilename);
+
+ rv = AppendArg(argsArray, arg);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (paged) {
+ rv = AppendArg(argsArray, u"paged"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID);
+ NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE);
+
+ nsCOMPtr<mozIDOMWindowProxy> opened;
+ wwatch->OpenWindow(
+ nullptr, "chrome://layoutdebug/content/layoutdebug.xhtml"_ns, "_blank"_ns,
+ "chrome,dialog=no,all"_ns, argsArray, getter_AddRefs(opened));
+ aCmdLine->SetPreventDefault(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutDebugCLH::GetHelpInfo(nsACString& aResult) {
+ aResult.AssignLiteral(
+ " --layoutdebug [<url>] Start with Layout Debugger\n"
+ " --autoclose [<seconds>] Automatically close the Layout Debugger once\n"
+ " the page has loaded, after delaying the specified\n"
+ " number of seconds (which defaults to 0).\n"
+ " --capture-profile [<filename>] Capture a profile of the Layout\n"
+ " Debugger using the Gecko Profiler, and save the\n"
+ " profile to the specified file (which defaults to\n"
+ " profile.json).\n"
+ " --paged Layout the page in paginated mode.\n");
+ return NS_OK;
+}
diff --git a/layout/tools/layout-debug/src/nsLayoutDebugCLH.h b/layout/tools/layout-debug/src/nsLayoutDebugCLH.h
new file mode 100644
index 0000000000..aa99bf9eff
--- /dev/null
+++ b/layout/tools/layout-debug/src/nsLayoutDebugCLH.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:tabstop=4:expandtab:shiftwidth=4:
+/* 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/. */
+
+#ifndef nsLayoutDebugCLH_h_
+#define nsLayoutDebugCLH_h_
+
+#include "nsICommandLineHandler.h"
+#define ICOMMANDLINEHANDLER nsICommandLineHandler
+
+class nsLayoutDebugCLH : public ICOMMANDLINEHANDLER {
+ public:
+ nsLayoutDebugCLH();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOMMANDLINEHANDLER
+
+ protected:
+ virtual ~nsLayoutDebugCLH();
+};
+
+#endif /* !defined(nsLayoutDebugCLH_h_) */
diff --git a/layout/tools/layout-debug/src/nsLayoutDebuggingTools.cpp b/layout/tools/layout-debug/src/nsLayoutDebuggingTools.cpp
new file mode 100644
index 0000000000..05ad47356d
--- /dev/null
+++ b/layout/tools/layout-debug/src/nsLayoutDebuggingTools.cpp
@@ -0,0 +1,327 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:tabstop=4:expandtab:shiftwidth=4:
+/* 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/. */
+
+#include "nsLayoutDebuggingTools.h"
+
+#include "nsIDocShell.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocumentViewer.h"
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsService.h"
+
+#include "nsAtom.h"
+
+#include "nsIContent.h"
+
+#include "nsCounterManager.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsViewManager.h"
+#include "nsIFrame.h"
+
+#include "nsLayoutCID.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+
+using namespace mozilla;
+using mozilla::dom::Document;
+
+static already_AddRefed<nsIDocumentViewer> doc_viewer(nsIDocShell* aDocShell) {
+ if (!aDocShell) return nullptr;
+ nsCOMPtr<nsIDocumentViewer> viewer;
+ aDocShell->GetDocViewer(getter_AddRefs(viewer));
+ return viewer.forget();
+}
+
+static PresShell* GetPresShell(nsIDocShell* aDocShell) {
+ nsCOMPtr<nsIDocumentViewer> viewer = doc_viewer(aDocShell);
+ if (!viewer) return nullptr;
+ return viewer->GetPresShell();
+}
+
+static nsViewManager* view_manager(nsIDocShell* aDocShell) {
+ PresShell* presShell = GetPresShell(aDocShell);
+ if (!presShell) {
+ return nullptr;
+ }
+ return presShell->GetViewManager();
+}
+
+#ifdef DEBUG
+static already_AddRefed<Document> document(nsIDocShell* aDocShell) {
+ nsCOMPtr<nsIDocumentViewer> viewer(doc_viewer(aDocShell));
+ if (!viewer) {
+ return nullptr;
+ }
+ return do_AddRef(viewer->GetDocument());
+}
+#endif
+
+nsLayoutDebuggingTools::nsLayoutDebuggingTools() { ForceRefresh(); }
+
+nsLayoutDebuggingTools::~nsLayoutDebuggingTools() = default;
+
+NS_IMPL_ISUPPORTS(nsLayoutDebuggingTools, nsILayoutDebuggingTools)
+
+NS_IMETHODIMP
+nsLayoutDebuggingTools::Init(mozIDOMWindow* aWin) {
+ if (!Preferences::GetService()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ {
+ if (!aWin) return NS_ERROR_UNEXPECTED;
+ auto* window = nsPIDOMWindowInner::From(aWin);
+ mDocShell = window->GetDocShell();
+ }
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_UNEXPECTED);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutDebuggingTools::SetReflowCounts(bool aShow) {
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
+ if (PresShell* presShell = GetPresShell(mDocShell)) {
+#ifdef MOZ_REFLOW_PERF
+ presShell->SetPaintFrameCount(aShow);
+#else
+ printf("************************************************\n");
+ printf("Sorry, you have not built with MOZ_REFLOW_PERF=1\n");
+ printf("************************************************\n");
+#endif
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutDebuggingTools::SetPagedMode(bool aPagedMode) {
+ nsCOMPtr<nsIPrintSettingsService> printSettingsService =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ nsCOMPtr<nsIPrintSettings> printSettings;
+
+ printSettingsService->CreateNewPrintSettings(getter_AddRefs(printSettings));
+
+ // Use the same setup as setupPrintMode() in reftest-content.js.
+ printSettings->SetPaperWidth(5);
+ printSettings->SetPaperHeight(3);
+
+ nsIntMargin unwriteableMargin(0, 0, 0, 0);
+ printSettings->SetUnwriteableMarginInTwips(unwriteableMargin);
+
+ printSettings->SetHeaderStrLeft(u""_ns);
+ printSettings->SetHeaderStrCenter(u""_ns);
+ printSettings->SetHeaderStrRight(u""_ns);
+
+ printSettings->SetFooterStrLeft(u""_ns);
+ printSettings->SetFooterStrCenter(u""_ns);
+ printSettings->SetFooterStrRight(u""_ns);
+
+ printSettings->SetPrintBGColors(true);
+ printSettings->SetPrintBGImages(true);
+
+ nsCOMPtr<nsIDocumentViewer> docViewer(doc_viewer(mDocShell));
+ docViewer->SetPageModeForTesting(aPagedMode, printSettings);
+
+ ForceRefresh();
+ return NS_OK;
+}
+
+static void DumpContentRecur(nsIDocShell* aDocShell, FILE* out) {
+#ifdef DEBUG
+ if (nullptr != aDocShell) {
+ fprintf(out, "docshell=%p \n", static_cast<void*>(aDocShell));
+ RefPtr<Document> doc(document(aDocShell));
+ if (doc) {
+ dom::Element* root = doc->GetRootElement();
+ if (root) {
+ root->List(out);
+ }
+ } else {
+ fputs("no document\n", out);
+ }
+ }
+#endif
+}
+
+NS_IMETHODIMP
+nsLayoutDebuggingTools::DumpContent() {
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
+ DumpContentRecur(mDocShell, stdout);
+ return NS_OK;
+}
+
+static void DumpFramesRecur(
+ nsIDocShell* aDocShell, FILE* out,
+ nsIFrame::ListFlags aFlags = nsIFrame::ListFlags()) {
+ if (aFlags.contains(nsIFrame::ListFlag::DisplayInCSSPixels)) {
+ fprintf(out, "Frame tree in CSS pixels:\n");
+ } else {
+ fprintf(out, "Frame tree in app units:\n");
+ }
+
+ fprintf(out, "docshell=%p \n", aDocShell);
+ if (PresShell* presShell = GetPresShell(aDocShell)) {
+ nsIFrame* root = presShell->GetRootFrame();
+ if (root) {
+ root->List(out, "", aFlags);
+ }
+ } else {
+ fputs("null pres shell\n", out);
+ }
+}
+
+static void DumpTextRunsRecur(nsIDocShell* aDocShell, FILE* out) {
+ fprintf(out, "Text runs:\n");
+
+ fprintf(out, "docshell=%p \n", aDocShell);
+ if (PresShell* presShell = GetPresShell(aDocShell)) {
+ nsIFrame* root = presShell->GetRootFrame();
+ if (root) {
+ root->ListTextRuns(out);
+ }
+ } else {
+ fputs("null pres shell\n", out);
+ }
+}
+
+NS_IMETHODIMP
+nsLayoutDebuggingTools::DumpFrames() {
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
+ DumpFramesRecur(mDocShell, stdout);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutDebuggingTools::DumpFramesInCSSPixels() {
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
+ DumpFramesRecur(mDocShell, stdout, nsIFrame::ListFlag::DisplayInCSSPixels);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutDebuggingTools::DumpTextRuns() {
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
+ DumpTextRunsRecur(mDocShell, stdout);
+ return NS_OK;
+}
+
+static void DumpViewsRecur(nsIDocShell* aDocShell, FILE* out) {
+#ifdef DEBUG
+ fprintf(out, "docshell=%p \n", static_cast<void*>(aDocShell));
+ RefPtr<nsViewManager> vm(view_manager(aDocShell));
+ if (vm) {
+ nsView* root = vm->GetRootView();
+ if (root) {
+ root->List(out);
+ }
+ } else {
+ fputs("null view manager\n", out);
+ }
+#endif // DEBUG
+}
+
+NS_IMETHODIMP
+nsLayoutDebuggingTools::DumpViews() {
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
+ DumpViewsRecur(mDocShell, stdout);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutDebuggingTools::DumpCounterManager() {
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
+ if (PresShell* presShell = GetPresShell(mDocShell)) {
+ presShell->FrameConstructor()->GetContainStyleScopeManager().DumpCounters();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutDebuggingTools::DumpStyleSheets() {
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
+#if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
+ FILE* out = stdout;
+ if (PresShell* presShell = GetPresShell(mDocShell)) {
+ presShell->ListStyleSheets(out);
+ } else {
+ fputs("null pres shell\n", out);
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLayoutDebuggingTools::DumpMatchedRules() {
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
+ FILE* out = stdout;
+ if (PresShell* presShell = GetPresShell(mDocShell)) {
+ nsIFrame* root = presShell->GetRootFrame();
+ if (root) {
+ root->ListWithMatchedRules(out);
+ }
+ } else {
+ fputs("null pres shell\n", out);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutDebuggingTools::DumpComputedStyles() {
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
+#ifdef DEBUG
+ FILE* out = stdout;
+ if (PresShell* presShell = GetPresShell(mDocShell)) {
+ presShell->ListComputedStyles(out);
+ } else {
+ fputs("null pres shell\n", out);
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutDebuggingTools::DumpReflowStats() {
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
+#ifdef DEBUG
+ if (RefPtr<PresShell> presShell = GetPresShell(mDocShell)) {
+# ifdef MOZ_REFLOW_PERF
+ presShell->DumpReflows();
+# else
+ printf("************************************************\n");
+ printf("Sorry, you have not built with MOZ_REFLOW_PERF=1\n");
+ printf("************************************************\n");
+# endif
+ }
+#endif
+ return NS_OK;
+}
+
+nsresult nsLayoutDebuggingTools::ForceRefresh() {
+ RefPtr<nsViewManager> vm(view_manager(mDocShell));
+ if (!vm) return NS_OK;
+ nsView* root = vm->GetRootView();
+ if (root) {
+ vm->InvalidateView(root);
+ }
+ return NS_OK;
+}
+
+nsresult nsLayoutDebuggingTools::SetBoolPrefAndRefresh(const char* aPrefName,
+ bool aNewVal) {
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
+
+ nsIPrefService* prefService = Preferences::GetService();
+ NS_ENSURE_TRUE(prefService && aPrefName, NS_OK);
+
+ Preferences::SetBool(aPrefName, aNewVal);
+ prefService->SavePrefFile(nullptr);
+
+ ForceRefresh();
+
+ return NS_OK;
+}
diff --git a/layout/tools/layout-debug/src/nsLayoutDebuggingTools.h b/layout/tools/layout-debug/src/nsLayoutDebuggingTools.h
new file mode 100644
index 0000000000..f6b37fecfb
--- /dev/null
+++ b/layout/tools/layout-debug/src/nsLayoutDebuggingTools.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:tabstop=4:expandtab:shiftwidth=4:
+/* 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/. */
+
+#ifndef nsLayoutDebuggingTools_h
+#define nsLayoutDebuggingTools_h
+
+#include "nsILayoutDebuggingTools.h"
+#include "nsIDocShell.h"
+#include "nsCOMPtr.h"
+
+class nsLayoutDebuggingTools : public nsILayoutDebuggingTools {
+ public:
+ nsLayoutDebuggingTools();
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSILAYOUTDEBUGGINGTOOLS
+
+ protected:
+ virtual ~nsLayoutDebuggingTools();
+
+ nsresult SetBoolPrefAndRefresh(const char* aPrefName, bool aNewValue);
+
+ nsCOMPtr<nsIDocShell> mDocShell;
+};
+
+#endif
diff --git a/layout/tools/layout-debug/tests/browser/browser.toml b/layout/tools/layout-debug/tests/browser/browser.toml
new file mode 100644
index 0000000000..a5fa83336e
--- /dev/null
+++ b/layout/tools/layout-debug/tests/browser/browser.toml
@@ -0,0 +1,4 @@
+[DEFAULT]
+
+["browser_openLayoutDebug.js"]
+run-if = ["debug"]
diff --git a/layout/tools/layout-debug/tests/browser/browser_openLayoutDebug.js b/layout/tools/layout-debug/tests/browser/browser_openLayoutDebug.js
new file mode 100644
index 0000000000..4402f36a7f
--- /dev/null
+++ b/layout/tools/layout-debug/tests/browser/browser_openLayoutDebug.js
@@ -0,0 +1,41 @@
+"use strict";
+
+/*
+When run locally this won't test whether the files are packaged and available
+in a distributed build unless `./mach mochitest --appname dist` is used
+(after `./mach package`)
+*/
+
+function test() {
+ waitForExplicitFinish();
+
+ const windowListener = {
+ onOpenWindow(win) {
+ info("Observed window open");
+
+ const domWindow = win.docShell.domWindow;
+ waitForFocus(() => {
+ is(
+ domWindow.location.href,
+ "chrome://layoutdebug/content/layoutdebug.xhtml",
+ "Window location is correct"
+ );
+ domWindow.close();
+ }, domWindow);
+ },
+
+ onCloseWindow() {
+ info("Observed window closed");
+ Services.wm.removeListener(this);
+ finish();
+ },
+ };
+ Services.wm.addListener(windowListener);
+
+ const menuitem = document.getElementById("menu_layout_debugger");
+ ok(menuitem, "Menuitem present");
+ if (menuitem) {
+ // open the debugger window
+ menuitem.click();
+ }
+}
diff --git a/layout/tools/layout-debug/tests/unit/test_componentsRegistered.js b/layout/tools/layout-debug/tests/unit/test_componentsRegistered.js
new file mode 100644
index 0000000000..eaf1783cb7
--- /dev/null
+++ b/layout/tools/layout-debug/tests/unit/test_componentsRegistered.js
@@ -0,0 +1,6 @@
+function run_test() {
+ Assert.ok("@mozilla.org/layout-debug/layout-debuggingtools;1" in Cc);
+ Assert.ok(
+ "@mozilla.org/commandlinehandler/general-startup;1?type=layoutdebug" in Cc
+ );
+}
diff --git a/layout/tools/layout-debug/tests/unit/xpcshell.toml b/layout/tools/layout-debug/tests/unit/xpcshell.toml
new file mode 100644
index 0000000000..f6e9021291
--- /dev/null
+++ b/layout/tools/layout-debug/tests/unit/xpcshell.toml
@@ -0,0 +1,4 @@
+[DEFAULT]
+head = ""
+
+["test_componentsRegistered.js"]
diff --git a/layout/tools/layout-debug/ui/content/layoutdebug.ftl b/layout/tools/layout-debug/ui/content/layoutdebug.ftl
new file mode 100644
index 0000000000..98c6fa3b92
--- /dev/null
+++ b/layout/tools/layout-debug/ui/content/layoutdebug.ftl
@@ -0,0 +1,81 @@
+# 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/.
+
+### This file is not in a locales directory to prevent it from being
+### translated as the layout debugger is only available in debug builds.
+
+layoutdebug-main-window =
+ .title = Layout Debugger
+
+layoutdebug-back-button =
+ .label = Back
+layoutdebug-forward-button =
+ .label = Forward
+layoutdebug-reload-button =
+ .label = Reload
+layoutdebug-stop-button =
+ .label = Stop
+
+## Toggle Menu
+
+layoutdebug-toggle-menu =
+ .label = Toggle
+ .accesskey = T
+layoutdebug-paint-dumping =
+ .label = Paint Dumping
+ .accesskey = P
+layoutdebug-invalidate-dumping =
+ .label = Invalidate Dumping
+ .accesskey = I
+layoutdebug-event-dumping =
+ .label = Event Dumping
+ .accesskey = E
+layoutdebug-motion-event-dumping =
+ .label = Motion Event Dumping
+ .accesskey = M
+layoutdebug-crossing-event-dumping =
+ .label = Crossing Event Dumping
+ .accesskey = C
+layoutdebug-reflow-counts =
+ .label = Reflow Counts
+ .accesskey = R
+layoutdebug-paged-mode =
+ .label = Paged Mode
+ .accesskey = g
+
+## Dump Menu
+
+layoutdebug-dump-menu =
+ .label = Dump
+ .accesskey = D
+layoutdebug-dump-content =
+ .label = Content
+ .accesskey = C
+layoutdebug-dump-frames =
+ .label = Frames (app units)
+ .accesskey = F
+layoutdebug-dump-frames-in-css-pixels =
+ .label = Frames (CSS pixels)
+ .accesskey = p
+layoutdebug-dump-text-runs =
+ .label = Text Runs
+ .accesskey = T
+layoutdebug-dump-views =
+ .label = Views and Widgets
+ .accesskey = V
+layoutdebug-dump-counter-manager =
+ .label = CSS Counters
+ .accesskey = n
+layoutdebug-dump-style-sheets =
+ .label = Style Sheets
+ .accesskey = S
+layoutdebug-dump-matched-rules =
+ .label = Matched CSS Rules
+ .accesskey = M
+layoutdebug-dump-computed-styles =
+ .label = Style Contexts
+ .accesskey = x
+layoutdebug-dump-reflow-stats =
+ .label = Reflow Statistics
+ .accesskey = R
diff --git a/layout/tools/layout-debug/ui/content/layoutdebug.js b/layout/tools/layout-debug/ui/content/layoutdebug.js
new file mode 100644
index 0000000000..0c68f0155c
--- /dev/null
+++ b/layout/tools/layout-debug/ui/content/layoutdebug.js
@@ -0,0 +1,520 @@
+/* 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 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, !!Services.prefs.getBoolPref(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;
+ Services.prefs.setBoolPref(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(Services.io.newURI("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: {
+ esModuleURI: "resource://gre/actors/LayoutDebugChild.sys.mjs",
+ },
+ 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.
+ loadStringURI(gArgs.url, { delayLoad: 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) {
+ loadStringURI(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
+ ) {
+ loadURIObject(fp.fileURL);
+ }
+ });
+}
+
+// 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.getRemoteTypeForURIObject(aURL, {
+ multiProcess: gMultiProcessBrowser,
+ remoteSubFrames: gFissionBrowser,
+ preferredRemoteType: gBrowser.remoteType,
+ currentURI: gBrowser.currentURI,
+ originAttributes: 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 loadStringURI(aURLString, aOptions) {
+ let realURL;
+ try {
+ realURL = Services.uriFixup.getFixupURIInfo(aURLString).preferredURI;
+ } catch (ex) {
+ alert(
+ "Couldn't work out how to create a URL from input: " +
+ aURLString.substring(0, 100)
+ );
+ return;
+ }
+ return loadURIObject(realURL, aOptions);
+}
+
+async function loadURIObject(aURL, { delayLoad } = {}) {
+ // We don't bother trying to handle navigations within the browser to new URLs
+ // that should be loaded in a different process.
+ updateBrowserRemotenessByURL(aURL);
+ // When attaching the profiler we may want to delay the actual load a bit
+ // after switching remoteness.
+ if (delayLoad) {
+ await new Promise(r => setTimeout(r, delayLoad));
+ }
+ gBrowser.loadURI(aURL, {
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+}
+
+function focusURLBar() {
+ gURLBar.focus();
+ gURLBar.select();
+}
+
+function go() {
+ loadStringURI(gURLBar.value);
+ gBrowser.focus();
+}
diff --git a/layout/tools/layout-debug/ui/content/layoutdebug.xhtml b/layout/tools/layout-debug/ui/content/layoutdebug.xhtml
new file mode 100644
index 0000000000..995a1a4b44
--- /dev/null
+++ b/layout/tools/layout-debug/ui/content/layoutdebug.xhtml
@@ -0,0 +1,280 @@
+<?xml version="1.0"?>
+<!-- vim: set shiftwidth=2 tabstop=8 expandtab :
+ -
+ -
+ - 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/. -->
+
+<!DOCTYPE window>
+
+<!--
+
+ NOTE: Because this window is used for layout regression tests, the
+ persist attribute should never be used on anything. Otherwise there
+ is a risk of running baseline and verify runs under different
+ conditions.
+
+-->
+
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ id="main-window"
+ align="stretch"
+ data-l10n-id="layoutdebug-main-window"
+ windowtype="mozapp:layoutdebug"
+ onload="OnLDBLoad();"
+ onclose="OnLDBBeforeUnload(event);"
+ onunload="OnLDBUnload();"
+ width="1024"
+ height="768"
+ screenX="4"
+ screenY="4"
+>
+ <linkset>
+ <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
+
+ <html:link rel="localization" href="layoutdebug/layoutdebug.ftl" />
+ </linkset>
+
+ <script src="chrome://layoutdebug/content/layoutdebug.js" />
+
+ <commandset id="tasksCommands">
+ <command id="cmd_open" oncommand="openFile();" />
+ <command id="cmd_close" oncommand="window.close();" />
+ <command id="cmd_focusURLBar" oncommand="focusURLBar();" />
+ <command id="cmd_reload" oncommand="gBrowser.reload();" />
+ <command id="cmd_dumpContent" oncommand="gDebugger.dumpContent();" />
+ <command id="cmd_dumpFrames" oncommand="gDebugger.dumpFrames();" />
+ <command
+ id="cmd_dumpFramesInCSSPixels"
+ oncommand="gDebugger.dumpFramesInCSSPixels();"
+ />
+ <command id="cmd_dumpTextRuns" oncommand="gDebugger.dumpTextRuns();" />
+ <command id="cmd_openDevTools" oncommand="gDebugger.openDevTools();" />
+ </commandset>
+
+ <keyset id="tasksKeys">
+ <key id="key_open" key="O" modifiers="accel" command="cmd_open" />
+ <key id="key_close" key="W" modifiers="accel" command="cmd_close" />
+ <key
+ id="key_focusURLBar"
+ key="L"
+ modifiers="accel"
+ command="cmd_focusURLBar"
+ />
+ <key id="key_reload" key="R" modifiers="accel" command="cmd_reload" />
+ <key
+ id="key_dumpContent"
+ key="D"
+ modifiers="accel"
+ command="cmd_dumpContent"
+ />
+ <!-- "D" means DOM tree -->
+ <key
+ id="key_dumpFrames"
+ key="F"
+ modifiers="accel"
+ command="cmd_dumpFrames"
+ />
+ <key
+ id="key_dumpFramesInCSSPixels"
+ key="P"
+ modifiers="accel"
+ command="cmd_dumpFramesInCSSPixels"
+ />
+ <key
+ id="key_dumpTextRuns"
+ key="T"
+ modifiers="accel"
+ command="cmd_dumpTextRuns"
+ />
+ <key id="key_devTools" keycode="VK_F12" command="cmd_openDevTools" />
+ </keyset>
+
+ <vbox flex="1">
+ <toolbox>
+ <toolbar type="menubar">
+ <menubar id="main-menubar">
+ <menu id="menu_file" label="File" accesskey="F">
+ <menupopup id="menu_FilePopup">
+ <menuitem
+ id="menu_open"
+ label="Open Fileā€¦"
+ accesskey="O"
+ key="key_open"
+ command="cmd_open"
+ />
+ <menuitem
+ id="menu_close"
+ label="Close"
+ accesskey="C"
+ key="key_close"
+ command="cmd_close"
+ />
+ </menupopup>
+ </menu>
+ <menu data-l10n-id="layoutdebug-toggle-menu">
+ <menupopup>
+ <menuitem
+ type="checkbox"
+ id="menu_paintDumping"
+ data-l10n-id="layoutdebug-paint-dumping"
+ oncommand="toggle(this);"
+ />
+ <menuitem
+ type="checkbox"
+ id="menu_invalidateDumping"
+ data-l10n-id="layoutdebug-invalidate-dumping"
+ oncommand="toggle(this);"
+ />
+ <menuseparator />
+ <menuitem
+ type="checkbox"
+ id="menu_eventDumping"
+ data-l10n-id="layoutdebug-event-dumping"
+ oncommand="toggle(this);"
+ />
+ <menuitem
+ type="checkbox"
+ id="menu_motionEventDumping"
+ data-l10n-id="layoutdebug-motion-event-dumping"
+ oncommand="toggle(this);"
+ />
+ <menuitem
+ type="checkbox"
+ id="menu_crossingEventDumping"
+ data-l10n-id="layoutdebug-crossing-event-dumping"
+ oncommand="toggle(this);"
+ />
+ <menuseparator />
+ <menuitem
+ type="checkbox"
+ id="menu_reflowCounts"
+ data-l10n-id="layoutdebug-reflow-counts"
+ oncommand="toggle(this);"
+ />
+ <menuitem
+ type="checkbox"
+ id="menu_pagedMode"
+ data-l10n-id="layoutdebug-paged-mode"
+ oncommand="toggle(this);"
+ />
+ </menupopup>
+ </menu>
+ <menu data-l10n-id="layoutdebug-dump-menu">
+ <menupopup>
+ <menuitem
+ id="menu_processIDs"
+ label="Process IDs"
+ accesskey="P"
+ oncommand="gDebugger.dumpProcessIDs();"
+ />
+ <menuitem
+ id="menu_dumpContent"
+ data-l10n-id="layoutdebug-dump-content"
+ oncommand="gDebugger.dumpContent();"
+ />
+ <menuitem
+ id="menu_dumpFrames"
+ data-l10n-id="layoutdebug-dump-frames"
+ oncommand="gDebugger.dumpFrames();"
+ />
+ <menuitem
+ id="menu_dumpFramesInCSSPixels"
+ data-l10n-id="layoutdebug-dump-frames-in-css-pixels"
+ oncommand="gDebugger.dumpFramesInCSSPixels();"
+ />
+ <menuitem
+ id="menu_dumpTextRuns"
+ data-l10n-id="layoutdebug-dump-text-runs"
+ oncommand="gDebugger.dumpTextRuns();"
+ />
+ <menuitem
+ id="menu_dumpViews"
+ data-l10n-id="layoutdebug-dump-views"
+ oncommand="gDebugger.dumpViews();"
+ />
+ <menuitem
+ id="menu_dumpCounterManager"
+ data-l10n-id="layoutdebug-dump-counter-manager"
+ oncommand="gDebugger.dumpCounterManager();"
+ />
+ <menuseparator />
+ <menuitem
+ id="menu_dumpStyleSheets"
+ data-l10n-id="layoutdebug-dump-style-sheets"
+ oncommand="gDebugger.dumpStyleSheets();"
+ />
+ <menuitem
+ id="menu_dumpMatchedRules"
+ data-l10n-id="layoutdebug-dump-matched-rules"
+ oncommand="gDebugger.dumpMatchedRules();"
+ />
+ <menuitem
+ id="menu_dumpComputedStyles"
+ data-l10n-id="layoutdebug-dump-computed-styles"
+ oncommand="gDebugger.dumpComputedStyles();"
+ />
+ <menuseparator />
+ <menuitem
+ id="menu_dumpReflowStats"
+ data-l10n-id="layoutdebug-dump-reflow-stats"
+ oncommand="gDebugger.dumpReflowStats();"
+ />
+ </menupopup>
+ </menu>
+ <menu id="tasksMenu" />
+ <menu id="menu_Help" />
+ </menubar>
+ </toolbar>
+
+ <toolbar>
+ <toolbarbutton
+ id="back-button"
+ class="toolbarbutton-1"
+ data-l10n-id="layoutdebug-back-button"
+ oncommand="gBrowser.goBack();"
+ />
+ <toolbarbutton
+ id="forward-button"
+ class="toolbarbutton-1"
+ data-l10n-id="layoutdebug-forward-button"
+ oncommand="gBrowser.goForward();"
+ />
+ <toolbarbutton
+ id="reload-button"
+ class="toolbarbutton-1"
+ data-l10n-id="layoutdebug-reload-button"
+ command="cmd_reload"
+ />
+ <toolbarbutton
+ id="stop-button"
+ class="toolbarbutton-1"
+ data-l10n-id="layoutdebug-stop-button"
+ oncommand="gBrowser.stop();"
+ />
+
+ <html:input
+ id="urlbar"
+ style="flex: 1"
+ onkeypress="if (event.keyCode == 13) go();"
+ />
+ </toolbar>
+ </toolbox>
+
+ <browser
+ flex="1"
+ id="browser"
+ type="content"
+ primary="true"
+ remote="true"
+ remoteType="web"
+ />
+
+ <hbox>
+ <description id="status-text" value="" />
+ </hbox>
+ </vbox>
+</window>
diff --git a/layout/tools/layout-debug/ui/jar.mn b/layout/tools/layout-debug/ui/jar.mn
new file mode 100644
index 0000000000..01d6b08012
--- /dev/null
+++ b/layout/tools/layout-debug/ui/jar.mn
@@ -0,0 +1,8 @@
+# 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/.
+
+layoutdebug.jar:
+% content layoutdebug %content/layoutdebug/
+ content/layoutdebug/layoutdebug.xhtml (content/layoutdebug.xhtml)
+ content/layoutdebug/layoutdebug.js (content/layoutdebug.js)
diff --git a/layout/tools/layout-debug/ui/moz.build b/layout/tools/layout-debug/ui/moz.build
new file mode 100644
index 0000000000..d988c0ff9b
--- /dev/null
+++ b/layout/tools/layout-debug/ui/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ["jar.mn"]