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.