jkunz 1371b4728d biq-engine: a page Chromium is still making holds DevTools subscriptions, and says it is pending (AGL-1001, AGL-986)
Since AGL-986 Chromium makes pages asynchronously, and a page it is
still making has no DevTools session: every DevTools call on it
answered NoPage ("no page has that key"), though the key is biq's.
The facts probe subscribes to its second page right after opening
it, so the second page's events went unrecorded ("Inspector.detached"
when its target closes).

- Subscriptions (and unsubscriptions, and a reset's) on a page still
  being made go to a session kept for it, which the page takes when
  it exists, like an address loaded meanwhile. If it closes first,
  its owners hear Detached(Closed), as for a page that exists.
- Calls on it answer the new DevToolsError::Pending: it takes calls
  from its first event on.

Two facts probe runs on this (Linux, Docker, Xvfb) agree with each
other and with the committed Linux facts, except that the closed
target's events arrive after Target.closeTarget's answer and are kept
by the next step ("wait 1000 ms"). Runs without this change show the
same shift: CEF calls back in that order on main.
2026-10-27 04:02:00 +00:00

biq

A sidebar-first browser. The window (sidebar, tab sets, Launcher, sheets) is drawn with egui. Pages render with Chromium by default, through CEF and the cef crate, offscreen into biq's window. Any tab can open with Servo instead, from its page options (AGL-794).

Epic AGL-733, design AGL-734, build AGL-738. The design is in biq.io/design (browser/).

Crates

  • biq-store: tab sets, favorites, pinned and open tabs, History and settings, and the library file, written with an atomic replace at most once a second (on a thread of its own) and on exit. A profile lock keeps a second biq off the same profile. Also the Launcher's search and address-or-search input.
  • biq-engine: biq's pages in biq's own terms (events, questions with responders, input), and the engines behind them.
    • Chromium (CEF 154): windowless browsers. Each page renders into its own GL texture; on macOS each frame is an IOSurface copied GPU-to-GPU, elsewhere a BGRA upload.
    • Servo (0.7.0): starts with its first page. Pages render offscreen into up to three slots.
  • biq-helper: the small binary CEF runs as its GPU, renderer, network and other processes.
  • biq-chrome: the egui window: the dees-catalog 23.1 tokens with Inter, the sidebar (open beside the page, or closed under a 44 px strip and floating in from the window's left edge, AGL-928), the page card, split view, the Launcher, History, Settings, the first run, and the questions pages ask (menus, dialogs, permissions, sign-in).
  • biq: the binary. It ties the window, egui_glow, the engine and the library together. The spike harness is the spike cargo feature. idp.global sign-in (PKCE with a loopback redirect) waits on branch feat/idp-sign-in until idp.global accepts native apps (AGL-769).

Build and run

Rust 1.97.1 (rust-toolchain.toml). Servo builds from crates.io; Cargo.lock starts from Servo 0.7.0's own.

CEF:

  • The build downloads CEF's binary distribution (about 340 MB on macOS, 1.4 GB with symbols on Linux). Set CEF_PATH to reuse one: the cef crate's export-cef-dir writes it.

  • Building CEF's C++ wrapper needs cmake and ninja.

  • macOS: Chromium runs only from the app bundle (ci/bundle-mac.sh builds target/bundle/biq.app, about 480 MB). cargo run gives a biq with Servo only.

  • Linux: ci/bundle-linux.sh puts biq beside libcef.so and CEF's resources (about 450 MB stripped).

  • Development builds: on macOS, Chromium's cookie key uses its stand-in keychain until biq is signed (AGL-789); Settings says so.

  • Privacy: Chromium's background traffic is off (component updates, Safe Browsing, optimization hints, sync, pings); the UI test checks it with a net log.

  • macOS: brew install cmake ninja pkg-config uv, then cargo build --release && ci/bundle-mac.sh and open target/bundle/biq.app. Servo's binding generator needs Python 3.11 or later; .cargo/config.toml asks uv for 3.12.

  • Linux: the dev headers are in ci/linux.Dockerfile. On a machine without them (gitzone_dev):

docker build -t biq-build:3 -f ci/linux.Dockerfile ci
ci/in-docker.sh cargo build --release
ci/in-docker.sh sh -c 'ci/xvfb.sh "$CARGO_TARGET_DIR/release/biq" https://code.foss.global'

Each checkout builds into a target directory of its own in the cache ($CARGO_TARGET_DIR in the container), so worktrees don't mix their crates; a checkout's first build compiles everything.

Linux runtime: besides GL (Mesa) and fontconfig, winit needs libxkbcommon-x11-0 and libxcb-xkb1; without them it stops at start. gitzone_dev lacks both: run in the build image, or unpack the two packages (apt-get download) and point LD_LIBRARY_PATH at them.

Options: biq --help. biq keeps its library and Servo's site data in the user data directory (biq/); --profile DIR puts them elsewhere.

What's on disk (AGL-792):

  • The key: with a keychain, biq makes a key for the profile and keeps it there. On macOS that's the login keychain; on Linux, a Secret Service such as GNOME Keyring, KWallet or KeePassXC.
  • Sealed with AES-256-GCM:
    • the library (library.biq);
    • Servo's cookies (cookies.biq);
    • its HTTPS-only list (hsts.biq).
  • Servo's plain files exist only while Servo starts and right after it exits.
  • HTTP sign-in passwords are never kept.
  • What pages store themselves (localStorage, Cache API) stays in Servo's own files. Servo's IndexedDB is off unless --indexeddb (AGL-1021). The profile directory's permissions (0700) and your disk encryption (FileVault, LUKS) protect it.
  • Without a keychain, biq keeps everything unencrypted and says so in Settings › Account.
  • While the keychain asks to be unlocked or for access, biq's window shows already and says it is opening your tab sets (AGL-940).
  • If the keychain doesn't answer (access denied, or a locked keyring), biq opens nothing it sealed and saves nothing until the next start.
  • Older backups made before this change still hold the unencrypted library.json.
  • Unsigned macOS builds ask for keychain access again after each rebuild.

Tests

  • cargo test --workspace: unit tests (library, saving and its failure paths, input, search, theme, icons, card corners, the window driven headless through egui with the sidebar open, closed and floating, shortcuts).
  • ci/in-docker.sh sh -c 'ci/bundle-linux.sh release && ci/ui-test.sh': the end-to-end UI test on a virtual display, on the bundled biq. It drives the real app with xdotool through the first run, the Launcher, split view, the page's questions, a <select>, the context menu and link preview, HTTP sign-in, History, Settings, a new tab set, "Close all", the sidebar closing with Ctrl+Shift+L, floating in from the left edge and staying closed or open across restarts, light mode, a certificate error, and a Servo that panics (BIQ_TEST_SERVO_PANIC=input, a test-only switch): biq goes on, the page says it stopped, and Open with Chromium loads it there. It keeps a screenshot of each screen and checks what biq saved. It runs first without a keyring (unencrypted), then with GNOME Keyring: a bus without a keyring and a keyring without a default collection keep it unencrypted; then the library moves into library.biq, a cookie survives two restarts sealed, no plain cookie jar or sign-in cache is left behind, SIGTERM quits cleanly, and a locked keyring whose prompt is dismissed leaves biq read-only with the sealed files untouched.
  • ci/in-docker.sh ci/spike.sh (build with --features spike): the Servo spike (AGL-739). It loads every site in ci/spike-sites.txt in its own process, in three modes, and writes the table.
  • ci/in-docker.sh ci/bench.sh: the main thread's costs that grow with the library (AGL-932): a save of a small and of a big library (500 tabs, a full History) step by step, the stale-tab sweep, and the chrome's frames with 10 to 500 tabs, History open, the Launcher searching and a 2,000-option <select>. They are ignored tests (bench_*).
  • ci/in-docker.sh ci/memory.sh (bundle first): Servo's memory (AGL-938). biq with Servo only opens twelve tabs of the scroll fixture one after another, then closes them, and prints its proportional memory after each.
  • ci/in-docker.sh ci/idle.sh (build and bundle with --features spike): the idle probe (AGL-932). Per engine and round, one biq each: a still page left alone, the same with a toast, an animated page whose window another window covers for a while, and the pointer moving over a page four times a frame. It writes a table of what biq did per second (loop turns, wake-ups, spins, Chromium's message-loop runs, redraws, draws, new page images, processor time) and how long its calls into the engine took for the pointer moves.
  • ci/in-docker.sh ci/devtools-facts.sh (build with --features spike, then ci/bundle-linux.sh): the CEF facts probe (AGL-1001). It opens ci/fixtures/devtools.html in Chromium (through a second site, with a second page beside it), makes the DevTools calls the assistant's allowlist depends on (AGL-990) and records each answer and event as given, with the fixture server's log as witness. It writes ci/devtools-facts/cef-<version>-<os>-<arch>.json, a table beside it, and a .stable.json copy with the ids that change between runs replaced, for diffing runs and platforms. Run it again for each new CEF, on each platform (on macOS, pass the spike build's binary).

Choices made provisionally

Phil hasn't answered Q1–Q9 on AGL-734 yet; biq follows Linus's defaults:

  • Every page opens in biq: what you type in the Launcher, links you follow, previews and windows that pages open. "Open in other browser" in a tab's or link's menu hands one page to the system browser on purpose.
  • macOS, then Linux.
  • No sync and no profiles yet.
  • Track Servo's monthly releases.
  • Private, with no crash reports; the search engine is a setting.
  • Manual releases.
S
Description
biq, the browser: Servo engine, egui window
Readme
5.1 MiB
0 Stars 2 Watchers 0 Forks
Languages
Rust 92.3%
Shell 4.5%
Python 2.2%
HTML 0.8%
Dockerfile 0.2%