Source code
Revision control
Copy as Markdown
Other Tools
# 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
import importlib.util
import os
import subprocess
import tempfile
from pathlib import Path
from mach.decorators import Command, CommandArgument
def _load_renderdoc_bootstrap(topsrcdir):
"""Import the WebRender RenderDoc bootstrap helper by path."""
path = os.path.join(
topsrcdir, "gfx", "wr", "wrench", "script", "renderdoc_bootstrap.py"
)
spec = importlib.util.spec_from_file_location("renderdoc_bootstrap", path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
@Command(
"wrshell",
category="devenv",
description="Launch Firefox under the WebRender debugger (wrshell) with "
"RenderDoc capture support.",
)
@CommandArgument(
"url",
nargs="?",
default="about:blank",
help="URL to open in the launched Firefox.",
)
@CommandArgument(
"--gui",
action="store_true",
help="Run wrshell in GUI mode (default is the CLI repl).",
)
@CommandArgument(
"--renderdoc-version",
default=None,
help="RenderDoc version to download/use (default: pinned version).",
)
@CommandArgument(
"--no-firefox",
action="store_true",
help="Don't launch Firefox; connect wrshell to an already-running instance.",
)
def wrshell(command_context, url, gui, renderdoc_version, no_firefox):
topsrcdir = command_context.topsrcdir
topobjdir = command_context.topobjdir
bootstrap = _load_renderdoc_bootstrap(topsrcdir)
def log(msg):
command_context.log(20, "wrshell", {}, msg)
# Ensure RenderDoc is available (download into ~/.mozbuild/renderdoc if not).
version = renderdoc_version or bootstrap.DEFAULT_VERSION
lib_path, bin_dir = bootstrap.ensure(version, log=log)
renderdoc_root = str(Path(bin_dir).parent)
firefox_proc = None
profile_dir = None
if not no_firefox:
firefox = Path(topobjdir) / "dist" / "bin" / "firefox"
if not firefox.exists():
command_context.log(
40, "wrshell", {}, f"Firefox binary not found at {firefox}; run `./mach build` first."
)
return 1
captures_dir = Path(topobjdir) / "tmp" / "renderdoc-captures"
captures_dir.mkdir(parents=True, exist_ok=True)
# A fresh, disposable profile with the GPU process disabled so WebRender
# renders (and is captured) in the parent process.
profile_dir = tempfile.mkdtemp(prefix="wrshell-profile-")
with open(os.path.join(profile_dir, "user.js"), "w") as fh:
fh.write('user_pref("layers.gpu-process.enabled", false);\n')
env = dict(os.environ)
env["LD_PRELOAD"] = str(lib_path)
env["MOZ_DISABLE_CONTENT_SANDBOX"] = "1"
env["WR_RENDERDOC_CAPTURE_PATH"] = str(captures_dir / "wr")
log(f"Launching Firefox with RenderDoc preloaded ({lib_path})")
firefox_proc = subprocess.Popen(
[
str(firefox),
"-no-remote",
"-new-instance",
"-profile",
profile_dir,
url,
],
env=env,
stdin=subprocess.DEVNULL,
)
try:
# Build and run wrshell, telling it where to find qrenderdoc.
manifest = os.path.join(topsrcdir, "gfx", "wr", "wrshell", "Cargo.toml")
wrshell_env = dict(os.environ)
wrshell_env["WR_RENDERDOC_DIR"] = renderdoc_root
mode = "gui" if gui else "repl"
log(f"Starting wrshell ({mode}); close it to shut down.")
return subprocess.call(
["cargo", "run", "--manifest-path", manifest, "--", mode],
env=wrshell_env,
)
finally:
# Tear down only the Firefox instance we launched.
if firefox_proc is not None and firefox_proc.poll() is None:
firefox_proc.terminate()
try:
firefox_proc.wait(timeout=5)
except subprocess.TimeoutExpired:
firefox_proc.kill()