The request pipeline
Qt WebEngine lets an application inspect every network request before Chromium sends it. JBrowser uses two
QWebEngineUrlRequestInterceptors, both in services/privacy.py, and both
delegate their decisions to PrivacyService:
page request
│
▼
ProfileInterceptor (one per space) rewrite(): developer host → redirect
│ tracking parameters → redirect to the clean URL
│ http → https (HTTPS-first) → redirect
│ otherwise: add DNT: 1 and Sec-GPC: 1 headers
│ Firefox User-Agent for accounts.google.com
▼
PageInterceptor (one per card) should_block(): block, and count it on the card
│ (a WebSocket request is noted for the memory saver)
▼
Chromium's network service
Qt calls the profile interceptor first, then the page's own. Both run on the UI thread for every request, so they
must be fast and must never raise: each wraps its work in try/except and logs failures.
rewrite(): redirects#
In order, the first rule that applies wins:
- Developer hosts. A request to a mapped name (
app.test) is redirected to its local target (http://127.0.0.1:3000), keeping the path and query. See networking. - Link cleaning (
privacy.strip_tracking, on by default). For top-level and frameGETnavigations,strip_tracking()removes known tracking parameters:utm_*,fbclid,gclid,msclkid,mc_eidand about 40 more, plus site-specific ones such as YouTube'ssior Amazon'sref_. Allowed sites are skipped. - HTTPS-first (
privacy.https_upgrade, off by default). A main-framehttp://navigation to a public host is redirected tohttps://. If the site bounces back to http within 4 seconds, or the https version fails to load or has a certificate error, JBrowser opens the original http page instead, shows a "not encrypted" warning, and doesn't try that host again this session.
If nothing redirects, the interceptor adds DNT: 1 (privacy.dnt) and Sec-GPC: 1 (privacy.gpc).
New in 1.5.1 Requests to Google's sign-in server (SIGNIN_UA_HOSTS, accounts.google.com) also get a Firefox
User-Agent header, so Google lets the user sign in. Fingerprinting and bot checks
explains why.
should_block(): blocking#
In order:
- Main-frame navigations are never blocked here: a page you asked for always loads (dangerous sites are stopped
earlier, in
BrowserPage.acceptNavigationRequest). - Local hosts (localhost, private addresses) are never blocked.
- Threats: a sub-resource or frame from a known phishing or malware host is always blocked, even with tracker blocking off.
- With
privacy.block_trackersoff, stop here. - The allowed list: when the page's own site (the first party) is on
privacy.allowlist, nothing is blocked. - List exemptions:
@@||site^$documentrules in the filter lists exempt a site completely. - Domain rules (
Blocklist: the built-in list plus the downloaded domain lists) block third-party requests to tracker and ad domains. - Network filters (
FilterEngine.blocking_filter) match the full URL, the request type and the first party against the Adblock Plus rules. - Exceptions: if something matched,
@@rules can still allow the request, unless the matching filter was$important.
The request type comes from Qt's resource type (_RTYPE maps script, image, stylesheet, font, sub-document, XHR,
media, ping, object, WebSocket and "other"). Third-party means the request's registrable domain differs from the
first party's (registrable_domain() in core/urls.py).
The content blocking page explains the lists and the filter engine.
A blocked request increments tab.blocked, which the shield button in the address pill shows.
Cookies#
Each profile's cookie store has a filter, PrivacyService.allow_cookie(). With
privacy.block_third_party_cookies on (the default), third-party cookies are refused, except:
- cookies set by CAPTCHA providers (
CAPTCHA_COOKIE_SITES: reCAPTCHA, hCaptcha, Cloudflare challenges, Arkose Labs), which keep their "this person already passed" state in their own cookies; without them every site shows a harder challenge; - on sites where protection is switched off (the allowed list).
Adding a rule#
- Something that changes the URL: add it to
rewrite(), keep it cheap, and returnNonequickly for the common case. - Something that blocks: put it in
should_block()at the right place in the order above, and make sure it can't block main-frame navigations. - Always test with a page that makes many requests (a news site) and watch the log for exceptions.