Per-Tab Proxy Routing with Firefox's proxy.onRequest API

This article explores the fundamental architectural differences between Firefox and Chromium regarding network proxy management. Specifically, it examines how Firefox’s browser.proxy.onRequest API intercepts network requests dynamically to enable granular, per-tab, and per-container proxy routing, contrasting this with Chromium's more restrictive, profile-wide global proxy system.

The Limitation of Chromium's Global Proxy Setting

Chromium-based browsers manage proxies using the chrome.proxy API. This system relies on applying a fixed configuration or a Proxy Auto-Configuration (PAC) script globally to the active browser profile.

When a network request occurs in Chromium, the underlying system evaluates the global proxy rules. Even if an extension uses a dynamic PAC script to route traffic, that script is sandboxed and only receives two pieces of information: the target url and the host. The PAC script is completely blind to the browser's user interface context. It does not know which tab, window, or extension triggered the request. Consequently, routing Tab A through one proxy and Tab B through another—especially when both tabs are visiting the exact same domain—is impossible in Chromium without launching entirely separate browser profiles.

Firefox's Event-Driven Approach

Firefox solves this limitation by abandoning the static, state-based proxy model in favor of an event-driven architecture. Through the browser.proxy.onRequest API, Firefox allows extensions to register an event listener that intercepts every single network request right before the browser establishes a connection.

Instead of telling the browser how to behave globally in advance, a Firefox extension acts as a real-time traffic controller. For every outgoing request, Firefox pauses, hands the request details to the extension, and waits for the extension to return a ProxyInfo object. This object instructs Firefox on how to route that single, specific request—whether through a SOCKS proxy, an HTTP proxy, or a direct connection.

The Power of the details Object

The reason Firefox can route traffic on a per-tab or per-container basis comes down to the rich metadata passed into the browser.proxy.onRequest listener. When a request is intercepted, Firefox provides the extension with a details object. Unlike Chromium's PAC scripts, this object contains comprehensive browser context, including:

  • tabId: The unique integer identifying the specific browser tab that initiated the request.
  • cookieStoreId: The string identifier for the Contextual Identity (Firefox Container) where the request originated.
  • url: The destination URL of the request.
  • documentUrl: The URL of the document in which the resource will be loaded.

How Per-Tab and Per-Container Routing Works in Practice

Because the onRequest listener runs inside the extension's background script with full access to the details object, developers can write straightforward conditional logic to isolate and route network traffic dynamically.

Per-Tab Routing: A user wants Tab 1 to use a US proxy and Tab 2 to use a UK proxy, both accessing the same website. The extension records the tabId of both tabs. When Tab 1 makes a network request, browser.proxy.onRequest fires. The extension reads details.tabId, sees it matches Tab 1, and returns the configuration for the US proxy. Milliseconds later, Tab 2 makes a request to the same URL. The listener fires again, identifies the details.tabId as Tab 2, and returns the UK proxy configuration.

Per-Container Routing: Firefox Multi-Account Containers separate browsing sessions by assigning a unique cookieStoreId to different tabs (e.g., firefox-container-1 for Work, firefox-container-2 for Personal). A proxy extension can map specific proxy servers to these IDs. When browser.proxy.onRequest intercepts a network call, it checks the details.cookieStoreId. If the request comes from the Work container, the extension routes it through a corporate proxy. If it comes from the Personal container, it routes it through a direct connection.

By evaluating proxy rules per-request rather than per-profile, and by exposing internal browser metadata like tabId and cookieStoreId to the network layer, Firefox's browser.proxy.onRequest API achieves a level of routing granularity that Chromium's architecture fundamentally cannot support.