Skip to content

Network and TLS

losenine edited this page Oct 31, 2026 · 1 revision

English · 中文

Network and TLS

Necko, Firefox's own network stack

In-page fetch and XHR, external scripts and navigations go out through Necko — Firefox's network stack with HTTP/1.1, HTTP/2 and HTTP/3 — and TLS is done by NSS, the same library real Firefox uses. Nothing is emulated. Each Context has its own proxy, TLS profile, cookie jar and resource directory.

Call What happens on the network
load_page(url, html) You supply the HTML; url becomes the document address, so relative URLs resolve against it. Classic scripts with src are read from the resource directory or the network.
navigate(url) A browser-initiated top-level navigation, fetched over Necko with no Referer, Sec-Fetch-Site: none and Sec-Fetch-User: ?1. Call advance() to let it fetch and commit.
fetch / XHR in the page Start them with eval(), then drive the event loop with advance().

Proxies

with Context(proxy="http://127.0.0.1:7890") as ctx:
    ctx.navigate("https://example.com/")
  • The proxy belongs to one context; it does not alter the host environment or other contexts.
  • None or "" means a direct connection.
  • The Context docstring describes proxy as an explicit HTTP(S) proxy URL; the README FAQ also lists SOCKS5.
  • If a context hangs, check that the proxy is reachable.

The shipped examples choose a proxy for you unless you pass --direct: --proxy, then the HTTPS_PROXY / https_proxy / HTTP_PROXY / http_proxy environment variables, then http://127.0.0.1:7890 if something listens there, otherwise a direct connection.

TLS profiles: Firefox 100 – 159

The TLS ClientHello — cipher suites, extensions, ALPN, key shares and their order — is produced by the real NSS library. The Necko transport can switch between Firefox 100 – 159 profiles; each reproduces that version's ClientHello, so the JA3 / JA4 fingerprint on the wire changes with it. That is useful for rotating browser identities or testing how a site reacts to different Firefox releases.

with Context(proxy="http://127.0.0.1:7890", timeout=30, tls="120") as context:
    context.load_page("https://example.com/", "")
    context.eval("globalThis.r = null; fetch('/api').then(x => x.json()).then(v => r = v)")
    context.advance(25)               # drive the event loop until it settles
    print(context.eval("r"))
  • Pick the release per context with tls=: "100" – "159" (also 120, "120.0" or "firefox-120"). The default is 159; with tls=None an NECKO_FIREFOX environment variable is used if set. context.tls shows the resolved version.
  • On Windows this sets the TLS ClientHello, the HTTP/2 settings and the HTTP/3 priorities of that context's Necko transport.
  • It changes the network fingerprint only. Set a matching user agent with configure_fingerprint when the page should agree — see Fingerprint and Environment. The Akamai example's --tls option sets both.

The profile table the Windows wheel ships (_tls_profiles.py, as described in docs/tls-profile.md) is organised as seven clusters rather than sixty unrelated hellos:

Versions What changes from the previous cluster
100–118 17 cipher values, 6 groups, one extra key share, no GREASE ECH, no certificate compression, no SCT
119–131 GREASE ECH turns on
132–134 A hybrid group is first, two extra key shares, certificate compression turns on
135–150 SCT turns on
151–153 The cipher list loses one value (16 left)
154–155 The cipher list loses one more value (15 left)
156–159 Two finite-field groups drop out (5 groups left)

On macOS

On the macOS source build, direct HTTPS follows tls= through an arm64 libnecko_core.dylib when that library reports NSS 3.131; a proxy, a missing library or a different NSS build stays on the donor xpcshell. Pages were checked for 100 and 159, and eight cluster-boundary versions as library requests. Details and the remaining gaps: docs/macos.md, docs/tls-profile.md, docs/tls-profile-report.zh-CN.md, docs/closed-route.zh-CN.md.

Offline resources

The resource directory lets you serve responses yourself, up to replaying a whole captured flow with no network:

context.put_resource("https://smoke.test/app.js",
                     "window.loaded = document.title + ':' + location.host;")
context.load_page("https://smoke.test/",
                  "<!doctype html><title>smoke</title><script src='/app.js'></script>")
print(context.eval("window.loaded"))   # smoke:smoke.test
  • put_resource(url, body, status=200, headers=None) stores a str (text) or bytes body under the key url, atomically and without any network request. headers are ordered name/value pairs. The runtime validates status, fields and storage limits.
  • get_resource(url) returns an independent snapshot or None; resource_status() returns the resource count and charged bytes; clear_resources() empties the directory.
  • The directory belongs to one context; iframes use their creator's directory.
  • The resource-network owner record (docs/owners/RESOURCE_NETWORK.md) records the contract: a put to an existing key replaces it after full validation; a get tries the exact URL first, then the URL without its query; limits are 16 KiB per key, 10,000 resources, 8 MiB per body, 256 header fields / 64 KiB per header list and 64 MiB charged in total; a failed put keeps the previous value.

Network log

net_log() returns the page's requests in order. Each entry has id, method, url, requestBody, status and matched (whether the resource directory matched it); a request that carried cookies also lists cookieNames — names only, never values. The DataDome example reads its interstitial POST body from requestBody.

Cookies

  • The context's cookie jar is shared by document.cookie, CookieStore, HTTP requests and frames. Necko stores Set-Cookie from in-page responses and sends the cookies on later in-page requests; the DataDome example relies on this to fetch the homepage with its new cookie.
  • export_cookies() snapshots every live cookie, HttpOnly included, with domain/path, flags, sameSite, expiry (Unix seconds; None for session cookies), creation time and order.
  • cookie_header(url, *, source_url=None, navigation=False, method="GET") gives you the Cookie header the runtime would select, for a request you make with another HTTP client. Recompute it for every redirect target. Set-Cookie headers in that external response are not stored in the context; handle them yourself.

Firefox-aligned details (from the CHANGELOG)

  • Origin and the body Content-Type on fetch / XHR / sendBeacon, and Sec-Fetch-Site across redirects (0.2.0).
  • Navigation and resource timing report the DNS, connect and TLS phases measured by Necko; a reused connection reads fetchStart, as in Firefox (0.2.1).
  • IP literals are resolved without a lookup, and names are cached (network.dnsCacheExpiration 60 s, 1600 entries) (0.2.1).

Related project

ruyiHttp — a Requests-style Python HTTP library built on Firefox's native networking components.

Clone this wiki locally