Inside the Firefox Profiler and Browser Integration

This article provides a quick overview of how the Firefox Profiler (profiler.firefox.com) integrates directly with the Firefox Web Browser to capture essential performance data. It covers the underlying architecture of the built-in Gecko Profiler, explaining the specific mechanisms used to sample CPU call stacks, instrument internal browser markers, and monitor network activity, before transferring this data to the web-based front-end for analysis.

The Dual-Part Architecture

The Firefox Profiler is split into two distinct components: the Gecko Profiler (the back-end recording engine built directly into the Firefox source code) and the analysis front-end hosted at profiler.firefox.com.

Because the recording engine is compiled directly into the browser, it has low-level access to Firefox’s internal threads and memory. When a user starts a recording via the browser toolbar or keyboard shortcuts, the Gecko Profiler allocates a fixed-size circular buffer in the system memory. All performance data is written to this buffer to ensure minimal runtime overhead and to prevent the profiler from consuming all available RAM.

Recording CPU Call Stacks via Statistical Sampling

To capture CPU activity, the Gecko Profiler relies on statistical sampling rather than logging every single function call, which would severely slow down the browser.

  1. Thread Registration: Critical browser threads—such as the Main thread, Compositor, and DOM Workers—register themselves with the profiler at startup.
  2. Periodic Pausing: At a user-defined interval (typically every 1 millisecond), a dedicated sampler thread interrupts and pauses the registered threads using operating system signals.
  3. Stack Walking: While a thread is paused, the profiler inspects the CPU registers and walks the call stack. It reads the memory addresses of native C++ and Rust functions. Simultaneously, it queries Firefox's JavaScript engine (SpiderMonkey) to read the current JavaScript frames, including Just-In-Time (JIT) compiled code.
  4. Unified Stacks: The profiler merges the native C++, Rust, and JavaScript frames into a single, unified call stack, writes the raw memory addresses into the circular buffer, and immediately resumes the paused thread.

Capturing Browser Markers via Instrumentation

Statistical sampling is effective for measuring CPU load, but it can miss instantaneous events that occur between sampling intervals. To capture these, the profiler uses deterministic code instrumentation known as "markers."

Firefox developers manually place profiler marker macros directly into the C++, Rust, and JavaScript source code of the browser. Whenever Firefox executes a specific, notable operation—such as a Garbage Collection pass, a DOM event, a layout reflow, or a CSS animation frame—the thread actively pushes a marker directly into the profiler's circular buffer. Unlike samples, markers are never missed. They record the exact start and end timestamps of an event along with a JSON payload containing contextual metadata.

Monitoring Network Activity

Network activity is recorded as a specialized extension of the marker system, integrated directly into Necko, Firefox’s internal networking library.

Rather than inspecting packets externally, Necko emits network markers at every stage of a resource request. When a network request is initiated, a start marker is logged. As the request progresses through the browser's networking pipeline, Necko logs precise timestamps for intermediate phases, including:

  • DNS lookup start and end
  • TCP connection establishment
  • TLS handshake completion
  • HTTP request transmission and response waiting times

When the request finishes or fails, a final marker is recorded containing the status code, MIME type, caching status, and transfer size. The profiler later correlates the start and end markers using a shared internal ID to build a complete network waterfall.

Data Serialization and WebChannel Transfer

Once the user chooses to capture the profile, the Gecko Profiler halts the sampler thread and extracts the raw data from the circular buffer. It serializes the collected stack memory addresses, markers, and network events into a compressed JSON format.

Firefox then opens a new tab pointed at profiler.firefox.com. Because a standard webpage cannot access internal browser data for security reasons, Firefox uses an internal, privileged communication mechanism called a WebChannel. Through the WebChannel, the browser injects the serialized JSON profile directly into the web application.

Finally, because the captured CPU stacks only contain raw memory addresses for native code, the web front-end uses an asynchronous browser API to ask Firefox (and Mozilla's remote symbol servers) to translate those hexadecimal addresses into human-readable C++ and Rust function names—a process called symbolication. Once complete, the data is rendered into the interactive timeline and call tree.