From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- layout/tools/layout-debug/LayoutDebugChild.jsm | 34 + layout/tools/layout-debug/moz.build | 13 + layout/tools/layout-debug/src/components.conf | 21 + layout/tools/layout-debug/src/moz.build | 24 + .../layout-debug/src/nsILayoutDebuggingTools.idl | 46 + layout/tools/layout-debug/src/nsLayoutDebugCLH.cpp | 188 ++ layout/tools/layout-debug/src/nsLayoutDebugCLH.h | 24 + .../layout-debug/src/nsLayoutDebuggingTools.cpp | 334 ++++ .../layout-debug/src/nsLayoutDebuggingTools.h | 30 + .../tools/layout-debug/tests/browser/browser.ini | 2 + .../tests/browser/browser_openLayoutDebug.js | 41 + .../tests/unit/test_componentsRegistered.js | 6 + layout/tools/layout-debug/tests/unit/xpcshell.ini | 4 + .../tools/layout-debug/ui/content/layoutdebug.js | 522 ++++++ .../layout-debug/ui/content/layoutdebug.xhtml | 137 ++ layout/tools/layout-debug/ui/jar.mn | 10 + .../layout-debug/ui/locale/en-US/layoutdebug.dtd | 59 + layout/tools/layout-debug/ui/moz.build | 7 + layout/tools/recording/RecordingCmdLine.jsm | 69 + layout/tools/recording/components.conf | 15 + layout/tools/recording/jar.mn | 4 + layout/tools/recording/moz.build | 15 + layout/tools/recording/recording.js | 52 + layout/tools/recording/recording.xhtml | 13 + layout/tools/reftest/README.txt | 717 ++++++++ layout/tools/reftest/ReftestFissionChild.jsm | 289 +++ layout/tools/reftest/ReftestFissionParent.jsm | 238 +++ layout/tools/reftest/api.js | 155 ++ layout/tools/reftest/chrome/binding.xml | 9 + layout/tools/reftest/chrome/userContent-import.css | 3 + layout/tools/reftest/chrome/userContent.css | 26 + layout/tools/reftest/clean-reftest-output.pl | 38 + layout/tools/reftest/globals.jsm | 167 ++ layout/tools/reftest/jar.mn | 60 + layout/tools/reftest/mach_commands.py | 305 +++ layout/tools/reftest/mach_test_package_commands.py | 122 ++ layout/tools/reftest/manifest.jsm | 782 ++++++++ layout/tools/reftest/manifest.json | 22 + layout/tools/reftest/moz.build | 36 + layout/tools/reftest/output.py | 192 ++ .../reftest/reftest-analyzer-structured.xhtml | 649 +++++++ layout/tools/reftest/reftest-analyzer.xhtml | 934 ++++++++++ layout/tools/reftest/reftest-content.js | 1668 +++++++++++++++++ layout/tools/reftest/reftest-to-html.pl | 118 ++ layout/tools/reftest/reftest.jsm | 1937 ++++++++++++++++++++ layout/tools/reftest/reftest.xhtml | 13 + layout/tools/reftest/reftest/__init__.py | 165 ++ layout/tools/reftest/reftestcommandline.py | 655 +++++++ layout/tools/reftest/remotereftest.py | 581 ++++++ layout/tools/reftest/runreftest.py | 1158 ++++++++++++ layout/tools/reftest/schema.json | 1 + layout/tools/reftest/selftest/conftest.py | 149 ++ layout/tools/reftest/selftest/files/assert.html | 7 + layout/tools/reftest/selftest/files/crash.html | 7 + layout/tools/reftest/selftest/files/defaults.list | 7 + .../selftest/files/failure-type-interactions.list | 11 + layout/tools/reftest/selftest/files/green.html | 6 + .../selftest/files/invalid-defaults-include.list | 4 + .../reftest/selftest/files/invalid-defaults.list | 3 + .../reftest/selftest/files/invalid-include.list | 2 + layout/tools/reftest/selftest/files/leaks.log | 73 + layout/tools/reftest/selftest/files/red.html | 6 + .../reftest/selftest/files/reftest-assert.list | 1 + .../reftest/selftest/files/reftest-crash.list | 1 + .../tools/reftest/selftest/files/reftest-fail.list | 3 + .../tools/reftest/selftest/files/reftest-pass.list | 3 + .../reftest/selftest/files/scripttest-pass.html | 17 + layout/tools/reftest/selftest/files/types.list | 5 + layout/tools/reftest/selftest/python.ini | 8 + .../selftest/test_python_manifest_parser.py | 39 + .../selftest/test_reftest_manifest_parser.py | 74 + .../tools/reftest/selftest/test_reftest_output.py | 164 ++ 72 files changed, 13300 insertions(+) create mode 100644 layout/tools/layout-debug/LayoutDebugChild.jsm create mode 100644 layout/tools/layout-debug/moz.build create mode 100644 layout/tools/layout-debug/src/components.conf create mode 100644 layout/tools/layout-debug/src/moz.build create mode 100644 layout/tools/layout-debug/src/nsILayoutDebuggingTools.idl create mode 100644 layout/tools/layout-debug/src/nsLayoutDebugCLH.cpp create mode 100644 layout/tools/layout-debug/src/nsLayoutDebugCLH.h create mode 100644 layout/tools/layout-debug/src/nsLayoutDebuggingTools.cpp create mode 100644 layout/tools/layout-debug/src/nsLayoutDebuggingTools.h create mode 100644 layout/tools/layout-debug/tests/browser/browser.ini create mode 100644 layout/tools/layout-debug/tests/browser/browser_openLayoutDebug.js create mode 100644 layout/tools/layout-debug/tests/unit/test_componentsRegistered.js create mode 100644 layout/tools/layout-debug/tests/unit/xpcshell.ini create mode 100644 layout/tools/layout-debug/ui/content/layoutdebug.js create mode 100644 layout/tools/layout-debug/ui/content/layoutdebug.xhtml create mode 100644 layout/tools/layout-debug/ui/jar.mn create mode 100644 layout/tools/layout-debug/ui/locale/en-US/layoutdebug.dtd create mode 100644 layout/tools/layout-debug/ui/moz.build create mode 100644 layout/tools/recording/RecordingCmdLine.jsm create mode 100644 layout/tools/recording/components.conf create mode 100644 layout/tools/recording/jar.mn create mode 100644 layout/tools/recording/moz.build create mode 100644 layout/tools/recording/recording.js create mode 100644 layout/tools/recording/recording.xhtml create mode 100644 layout/tools/reftest/README.txt create mode 100644 layout/tools/reftest/ReftestFissionChild.jsm create mode 100644 layout/tools/reftest/ReftestFissionParent.jsm create mode 100644 layout/tools/reftest/api.js create mode 100644 layout/tools/reftest/chrome/binding.xml create mode 100644 layout/tools/reftest/chrome/userContent-import.css create mode 100644 layout/tools/reftest/chrome/userContent.css create mode 100755 layout/tools/reftest/clean-reftest-output.pl create mode 100644 layout/tools/reftest/globals.jsm create mode 100644 layout/tools/reftest/jar.mn create mode 100644 layout/tools/reftest/mach_commands.py create mode 100644 layout/tools/reftest/mach_test_package_commands.py create mode 100644 layout/tools/reftest/manifest.jsm create mode 100644 layout/tools/reftest/manifest.json create mode 100644 layout/tools/reftest/moz.build create mode 100644 layout/tools/reftest/output.py create mode 100644 layout/tools/reftest/reftest-analyzer-structured.xhtml create mode 100644 layout/tools/reftest/reftest-analyzer.xhtml create mode 100644 layout/tools/reftest/reftest-content.js create mode 100755 layout/tools/reftest/reftest-to-html.pl create mode 100644 layout/tools/reftest/reftest.jsm create mode 100644 layout/tools/reftest/reftest.xhtml create mode 100644 layout/tools/reftest/reftest/__init__.py create mode 100644 layout/tools/reftest/reftestcommandline.py create mode 100644 layout/tools/reftest/remotereftest.py create mode 100644 layout/tools/reftest/runreftest.py create mode 100644 layout/tools/reftest/schema.json create mode 100644 layout/tools/reftest/selftest/conftest.py create mode 100644 layout/tools/reftest/selftest/files/assert.html create mode 100644 layout/tools/reftest/selftest/files/crash.html create mode 100644 layout/tools/reftest/selftest/files/defaults.list create mode 100644 layout/tools/reftest/selftest/files/failure-type-interactions.list create mode 100644 layout/tools/reftest/selftest/files/green.html create mode 100644 layout/tools/reftest/selftest/files/invalid-defaults-include.list create mode 100644 layout/tools/reftest/selftest/files/invalid-defaults.list create mode 100644 layout/tools/reftest/selftest/files/invalid-include.list create mode 100644 layout/tools/reftest/selftest/files/leaks.log create mode 100644 layout/tools/reftest/selftest/files/red.html create mode 100644 layout/tools/reftest/selftest/files/reftest-assert.list create mode 100644 layout/tools/reftest/selftest/files/reftest-crash.list create mode 100644 layout/tools/reftest/selftest/files/reftest-fail.list create mode 100644 layout/tools/reftest/selftest/files/reftest-pass.list create mode 100644 layout/tools/reftest/selftest/files/scripttest-pass.html create mode 100644 layout/tools/reftest/selftest/files/types.list create mode 100644 layout/tools/reftest/selftest/python.ini create mode 100644 layout/tools/reftest/selftest/test_python_manifest_parser.py create mode 100644 layout/tools/reftest/selftest/test_reftest_manifest_parser.py create mode 100644 layout/tools/reftest/selftest/test_reftest_output.py (limited to 'layout/tools') diff --git a/layout/tools/layout-debug/LayoutDebugChild.jsm b/layout/tools/layout-debug/LayoutDebugChild.jsm new file mode 100644 index 0000000000..ba815a8cc7 --- /dev/null +++ b/layout/tools/layout-debug/LayoutDebugChild.jsm @@ -0,0 +1,34 @@ +/* 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/. */ +"use strict"; + +var EXPORTED_SYMBOLS = ["LayoutDebugChild"]; + +const NS_LAYOUT_DEBUGGINGTOOLS_CONTRACTID = + "@mozilla.org/layout-debug/layout-debuggingtools;1"; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +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..9f8cca5b38 --- /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.ini"] + +BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.ini"] + +FINAL_TARGET_FILES.actors += ["LayoutDebugChild.jsm"] 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..5ee0cc91b5 --- /dev/null +++ b/layout/tools/layout-debug/src/nsILayoutDebuggingTools.idl @@ -0,0 +1,46 @@ +/* -*- 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. + */ + +[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 setVisualDebugging(in boolean enabled); + void setVisualEventDebugging(in boolean enabled); + void setReflowCounts(in boolean enabled); + void setPagedMode(in boolean enabled); + + /* Run various tests. */ + void dumpContent(); + void dumpFrames(); + void dumpFramesInCSSPixels(); + void dumpTextRuns(); + void dumpViews(); + + 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..e6a0097c3b --- /dev/null +++ b/layout/tools/layout-debug/src/nsLayoutDebugCLH.cpp @@ -0,0 +1,188 @@ +/* -*- 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 "plstr.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 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 argsArray = nsArray::Create(); + + nsCOMPtr 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 wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID); + NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE); + + nsCOMPtr 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 [] Start with Layout Debugger\n" + " --autoclose [] 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 [] 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..7251c704ba --- /dev/null +++ b/layout/tools/layout-debug/src/nsLayoutDebuggingTools.cpp @@ -0,0 +1,334 @@ +/* -*- 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 "nsIContentViewer.h" +#include "nsIPrintSettings.h" +#include "nsIPrintSettingsService.h" + +#include "nsAtom.h" +#include "nsQuickSort.h" + +#include "nsIContent.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 doc_viewer(nsIDocShell* aDocShell) { + if (!aDocShell) return nullptr; + nsCOMPtr result; + aDocShell->GetContentViewer(getter_AddRefs(result)); + return result.forget(); +} + +static PresShell* GetPresShell(nsIDocShell* aDocShell) { + nsCOMPtr cv = doc_viewer(aDocShell); + if (!cv) return nullptr; + return cv->GetPresShell(); +} + +static nsViewManager* view_manager(nsIDocShell* aDocShell) { + PresShell* presShell = GetPresShell(aDocShell); + if (!presShell) { + return nullptr; + } + return presShell->GetViewManager(); +} + +#ifdef DEBUG +static already_AddRefed document(nsIDocShell* aDocShell) { + nsCOMPtr cv(doc_viewer(aDocShell)); + if (!cv) return nullptr; + RefPtr result = cv->GetDocument(); + return result.forget(); +} +#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::SetVisualDebugging(bool aVisualDebugging) { +#ifdef DEBUG + nsIFrame::ShowFrameBorders(aVisualDebugging); + ForceRefresh(); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsLayoutDebuggingTools::SetVisualEventDebugging(bool aVisualEventDebugging) { +#ifdef DEBUG + nsIFrame::ShowEventTargetFrameBorder(aVisualEventDebugging); + ForceRefresh(); +#endif + 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 printSettingsService = + do_GetService("@mozilla.org/gfx/printsettings-service;1"); + nsCOMPtr printSettings; + + printSettingsService->GetNewPrintSettings(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 contentViewer(doc_viewer(mDocShell)); + contentViewer->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(aDocShell)); + RefPtr 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(aDocShell)); + RefPtr 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::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 = 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 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 mDocShell; +}; + +#endif diff --git a/layout/tools/layout-debug/tests/browser/browser.ini b/layout/tools/layout-debug/tests/browser/browser.ini new file mode 100644 index 0000000000..b732158dc3 --- /dev/null +++ b/layout/tools/layout-debug/tests/browser/browser.ini @@ -0,0 +1,2 @@ +[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.ini b/layout/tools/layout-debug/tests/unit/xpcshell.ini new file mode 100644 index 0000000000..46035d5f13 --- /dev/null +++ b/layout/tools/layout-debug/tests/unit/xpcshell.ini @@ -0,0 +1,4 @@ +[DEFAULT] +head = + +[test_componentsRegistered.js] 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..214e97f5d8 --- /dev/null +++ b/layout/tools/layout-debug/ui/content/layoutdebug.js @@ -0,0 +1,522 @@ +/* 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.import( + "resource://gre/modules/E10SUtils.jsm" +); +const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); +const { Preferences } = ChromeUtils.import( + "resource://gre/modules/Preferences.jsm" +); +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +const FEATURES = { + paintFlashing: "nglayout.debug.paint_flashing", + 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", + "dumpStyleSheets", + "dumpMatchedRules", + "dumpComputedStyles", + "dumpReflowStats", +]; + +class Debugger { + constructor() { + this._flags = new Map(); + this._visualDebugging = false; + this._visualEventDebugging = false; + 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 visualDebugging() { + return this._visualDebugging; + } + + set visualDebugging(v) { + v = !!v; + this._visualDebugging = v; + this._sendMessage("setVisualDebugging", v); + } + + get visualEventDebugging() { + return this._visualEventDebugging; + } + + set visualEventDebugging(v) { + v = !!v; + this._visualEventDebugging = v; + this._sendMessage("setVisualEventDebugging", v); + } + + get pagedMode() { + return this._pagedMode; + } + + set pagedMode(v) { + v = !!v; + this._pagedMode = v; + this.setPagedMode(this._pagedMode); + } + + setPagedMode(v) { + this._sendMessage("setPagedMode", v); + } + + 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.jsm + // 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) { + let env = Cc["@mozilla.org/process/environment;1"].getService( + Ci.nsIEnvironment + ); + if (!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("paintFlashing"); + 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 = OS.Path.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(); +} 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..4908f6237e --- /dev/null +++ b/layout/tools/layout-debug/ui/content/layoutdebug.xhtml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + +
+ +

Reftest analyzer: load raw structured log

+ +

Either paste your log into this textarea:
+