JBrowser Source Docs

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:

  1. 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.
  2. Link cleaning (privacy.strip_tracking, on by default). For top-level and frame GET navigations, strip_tracking() removes known tracking parameters: utm_*, fbclid, gclid, msclkid, mc_eid and about 40 more, plus site-specific ones such as YouTube's si or Amazon's ref_. Allowed sites are skipped.
  3. HTTPS-first (privacy.https_upgrade, off by default). A main-frame http:// navigation to a public host is redirected to https://. 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.

New in 1.5.2 Local files. Before anything else, a request from a local page (file://) for another local file is blocked when it could read the file's contents (fetch/XHR, frames, objects, workers: _FILE_READ_TYPES). Qt lets local pages read any local file, unlike Chrome, so a downloaded HTML file opened in JBrowser could otherwise read the user's documents. Images, style sheets, scripts, fonts and media still load, so saved web pages look right.

should_block(): blocking#

In order:

  1. Main-frame navigations are never blocked here: a page you asked for always loads (dangerous sites are stopped earlier, in BrowserPage.acceptNavigationRequest).
  2. Local hosts (localhost, private addresses) are never blocked.
  3. Threats: a sub-resource or frame from a known phishing or malware host is always blocked, even with tracker blocking off.
  4. With privacy.block_trackers off, stop here.
  5. The allowed list: when the page's own site (the first party) is on privacy.allowlist, or is a sign-in page (SIGNIN_PAGE_HOSTS, why), nothing is blocked.
  6. List exemptions: @@||site^$document rules in the filter lists exempt a site completely.
  7. Domain rules (Blocklist: the built-in list plus the downloaded domain lists) block third-party requests to tracker and ad domains.
  8. Network filters (FilterEngine.blocking_filter) match the full URL, the request type and the first party against the Adblock Plus rules.
  9. 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 return None quickly 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.