Skip to content

The shell

codingncaffeine edited this page Sep 19, 2026 · 27 revisions

The shell

The browser around the engine. It is one OS-free component — tabs, history, the address bar, menus, hit-testing, scrolling, forms, selection, find, reader mode, link hints and devtools — painted through the theme's tokens (Themes) and driven either by a window or by a script (Scripting the shell). The Win32 window blits the shell's frame and feeds it input; the same shell runs headless in CI on all three OSes.

Browsing

  • Tabs — open, close, switch; each keeps its own document, scroll position, controls, selection and find state. A link sent to a tab of its own — Ctrl or the middle button with the click, the link's menu — opens behind the page being read and beside it, after the tabs that page has already opened, so three links opened from one page stand in the order they were opened; once the reader has been to another tab, the next one stands right beside its opener again. A link that asks for a tab of its own (target=_blank), a window a page opens, and Ctrl+Shift with the click come to the front, right beside their opener. A closed tab comes back: Ctrl+Shift+T (or the tab's menu, or the palette) reopens the tab closed last where it stood in the strip, in its container, with its whole history and the place it had scrolled to — drawn from the bytes kept, nothing fetched. The last 25 are kept, as the browsers keep them, and the session file carries them across a restart; a tab that never left the new-tab page is not kept. Duplicate tab opens a copy beside the tab, at the same place in the same history, in the same container. A pinned tab (the tab's menu: Pin tab, Unpin tab) is its icon alone — the page's own, or a sheet of paper for a page that has none — among the strip's first tabs, with no close button: pinning takes a tab to the end of that row and unpinning to the first place after it. "Close other tabs" and "Close tabs to the right" leave pinned tabs standing; one closes when it is asked to itself (its menu, Ctrl+W, the middle button) and comes back pinned; its copy is pinned beside it; what it opens stands after the pinned row; and the session file keeps the pins.
  • History — Back and Forward keep each page's bytes, so going back never refetches.
  • What the shell has to say — where the link under the pointer leads, what is loading, a notice (a page loaded over plain HTTP, a form refused by the page's policy) — floats in a small box over the page's bottom left corner while there is something to say, and is not there otherwise: the page has the window down to its foot, and no status bar is kept. It draws from status-background, status-text and chrome-border (Themes).
  • The command palette — Ctrl+Shift+P opens one list over the page with every command the shell has: a new tab, a new tab in each container, close, reload, back, forward, find, reader mode, the developer tools, the page's source, a switch to each open tab by its title, and each theme preset by name. Typing narrows the list word by word (each word a substring of the label, case aside), the arrow keys move along it and wrap, Enter runs the highlighted command, Escape closes it with nothing done; a click on a row runs it, a click anywhere else closes it.
  • Containers — a tab in a container has a cookie jar and a localStorage of its own, kept apart from the default container's and from every other's, so a site opened in one never sees what it set in another; a container tab wears its container's colour along its top edge. The profile's containers.json names them in order ([{"name": "Work", "color": "#f59e0b"}, …]; a fresh profile gets Personal, Work, Banking and Shopping). Ctrl+Shift+N opens a tab in the container after the active tab's, round to the default; a link opened in a new tab stays in its container; the session keeps each tab's container. The cache and the connections are shared between containers.
  • The profile — --profile <dir> (see Running Sashfold) holds cookies.txt (the default container's jar in the Netscape format curl and wget read, cookies-<name>.txt for each container's), storage.json (every page's localStorage, per container and origin — a page's storage outlives the page), containers.json and session.json, each written back whenever what it holds changes, at most once a second and once more on the way out, whole or not at all.
  • Session restore — the window writes the session — every tab, each entry of its history with its title and scroll position, which entry each tab is on and which tab is active — to session.json in the profile folder (--profile <dir>, else the platform's configuration directory; see Running Sashfold) whenever it changes, at most once a second and once more on the way out, whole or not at all, so a crash costs a second of it at most. The next start brings the tabs back with their titles and fetches a page the first time its tab is shown (the active one at once), never every tab at start; a URL on the command line opens beside them.
  • Its words — a tab's title, the address, a menu, the palette — are set in a face of the machine's, through the engine's own font stack (Painting and text): the families the theme names (type.font-family, see Themes), else the machine's interface face and its sans-serif, measured and kerned glyph by glyph, with the built-in face last. A page's @font-face fonts take no part in it: the window's words are never a page's to dress. Where the machine's fonts are off, as they are for a script, the built-in face is all there is, and every golden is the same bytes on every OS.
  • The address bar — a caret, selection, Ctrl+A/C/V; a typed host is tried over https first (see Networking); a typed non-URL opens an internal page, never a search engine.
  • Links — hit-tested through the element pointers on text runs and picture boxes; fragment links scroll to their target. A middle click, or a click with Ctrl held, opens a link in a tab of its own; a page hears the keys held with a click (ctrlKey, shiftKey, altKey).
  • Internal pages — about:sashfold, the error page, the certificate-error page (no override), a plain-text page for text types, an unsupported-content page for the rest, and view-source:.
  • The new-tab page — about:newtab, where a new tab opens with the address bar empty and focused: a clock, a greeting by the hour and the date, over a gradient of the theme's colors or over the theme's own pictures. A theme names a folder beside itself (new-tab.backgrounds, see Themes); each new tab opens on the next picture in name order, and the page fades to the next one every new-tab.rotate milliseconds. The clock ticks on the page's own timers from the minute it opened. Nothing on it touches the network.
  • A page's icon — the last <link rel=icon>, else /favicon.ico, fetched through the page's own loader, decoded (ICO, PNG, GIF, JPEG or BMP) and drawn in the tab before the title.
  • Content blocking — every request the shell makes is judged first by the lists in the blocklists folder (--blocklists <dir>, else blocklists/ beside the executable or its parent; see Security defaults): filter lists in the Adblock Plus syntax EasyList is written in refuse what pages request and hide, through a stylesheet added after the page's own, the elements their ## rules name for the site (the #@# exceptions lift them; the procedural and scriptlet forms are read past), and lists of phishing and malware sites keep the shell off those sites as pages too, with a page saying which list and which line. A refused request costs no connection; no lookup leaves the machine. The lists judge every redirect hop as well, and beside them each page's own Content Security Policy judges what the page fetches, runs and applies (Security defaults); a form whose target the policy refuses says so in the status line.
  • Scrolling — the wheel, Page Up/Down, Home/End, the arrow keys, for the page and for any box that scrolls (see below).

When a page runs out of memory

One process holds every tab, so a page that allocates without end — its own leak, or a script that misbehaves because something it expects is not written yet — would otherwise stop only at the machine's memory, gigabytes a second, and take the desktop down with it. Two ceilings stand in the way.

A page's script heap has a ceiling (--js-heap-limit, a quarter of the machine's memory between 512 MB and 4 GB). What counts is what is live after a collection — garbage is not the page's fault — and the heap asks its cells their sizes again as it goes (every so many allocations, every so many steps of a script, and whenever a callback returns), because an array grows after it is made. A page found over its ceiling has the running script ended, as a runaway loop is, and no other runs; its console says so once — out of memory: the page's scripts were stopped, its heap over N MB — the page stays as it was drawn, and every other tab, and the next page in the same tab, goes on with a heap of its own.

The process has a ceiling (--memory-ceiling, half the machine's memory between 2 GB and 16 GB) for whatever the first does not see: a thread of its own — the one thread the engine has beside its own — reads the resident set four times a second, and the first time it is over, the window ends at once: a line on stderr (the journal, for a window started from the desktop) saying how much, then the main thread's call stack by function name — which in a runaway is the runaway — then abort. The session file is at most a second old, so the tabs come back at the next start.

Menus

A right click opens the menu of what it landed on, and what is there decides the items, group by group:

  • a link — open it in a new tab, or in a new tab of any container (a submenu), and copy its address; for a link that is not an address to go to (javascript:, mailto:) the two that open it are dimmed;
  • a picture — open it in a new tab, copy its address (the source the page chose from its srcset), and save it to the downloads folder as a download is saved, with the extension its bytes call for when its name has none;
  • a selection — Copy;
  • a field — the click puts the caret in it, and Paste types the clipboard there. Cut and Copy are shown and cannot be chosen yet: a field has a caret and no selection of its own;
  • the page, when none of those is under the pointer — Back, Forward, Reload, Select all, View page source;
  • and Inspect last in every one, which opens the developer tools on the element under the pointer.

The page hears the click first — mousedown, then contextmenu, in the frame's own document when the click is inside a frame — and a page that prevents the default keeps the shell's menu away, which is how a web application puts up a menu of its own. Shift with the click shows the shell's menu regardless.

A tab's menu opens a new tab, reloads that tab, duplicates it, pins or unpins it, closes it, closes the others, or closes those to its right, and reopens the tab closed last. The address bar's has Cut, Copy, Paste, Paste and go, and Select all. The main menu hangs from the button at the toolbar's right end: a new tab, a new tab in any container, find in page, reader mode, the themes with the one in use checked, the command palette, the developer tools, the page's source, the about page, and Quit.

Menus are drawn by the shell like the rest of the chrome, from the theme's popup- tokens (Themes), and stay inside the window. A menu opens with its corner at the pointer, on the pointer's other side where the window leaves no room; a submenu opens beside its item on whichever side has room, and on neither, against the window's edge on the roomier side. An item that cannot be chosen is dimmed, one that is on carries a check mark, one with a submenu an arrow, and the keys that do the same are written at the right.

An open menu holds the pointer and the keyboard. The pointer highlights the item it is over and opens that item's submenu — a move that heads for an open submenu across the rows between leaves it open. A press on an item chooses it; so does letting go of the button that opened the menu, after a slide to the item. A press anywhere else closes the menus and does nothing more, and the wheel moves nothing meanwhile. The Menu key or Shift+F10 opens the menu of whatever has the focus — the address bar, a field, else the page; Up and Down move along it past what cannot be chosen and wrap, Home and End go to its ends, Right opens a submenu and Left closes it, a letter goes to the next item that begins with it, Enter chooses, and Escape closes the innermost menu.

Beside the menus: Ctrl+U shows the page's source, Ctrl+X cuts the address bar's text, and Ctrl+Shift+Q quits.

Boxes that scroll

A box whose overflow is auto, scroll or hidden holds a scrollport its content can be moved within. Layout settles how far each can be moved — the scrollable overflow region past the scrollport's end edges, the end padding included, the walk stopping at a box that scrolls because that box holds its own content — and the shell moves what is inside it.

A wheel notch goes to the innermost box under the pointer that has room to take it; a box with none passes it outward, to the box around it and finally to the page, which is the chaining every browser does. A box that can only move across takes a plain wheel that way, which is what a wide table in a narrow column needs. The keyboard follows whatever the reader last moved by hand. hidden clips and holds a scrollport, but gives a reader no way in at all.

auto shows a scrollbar once there is something out of sight, scroll shows one whether or not there is. The bar is drawn inside the scrollport over the content rather than taking room from it, its thumb as long a part of the track as the scrollport is of the whole content; the thumb can be dragged, and a click on the track beside it moves a scrollport at a time, towards the pointer. A render taken with no shell behind it — --render, and the reference tests — draws no bar at all: nothing has been scrolled there and nothing can be.

Scrolling is applied to the fragments rather than at painting time, so painting, hit-testing, the selection, find-in-page and the devtools overlay all read the coordinates the content is actually drawn at. A link scrolled out of sight is out of reach with it.

Find in page and a fragment link reach into a box: every box between the target and the root is moved until the target is inside each of their scrollports, outermost first — an inner box travels with the outer one, so its own numbers are only settled once the outer one has stopped — and then the page, as before. A target too big for a scrollport is shown from its start edge rather than its end.

What is left: layout reserves no room for a bar, so the bars sit over the content the way an overlay scrollbar does rather than narrowing the scrollport, and a box that sticks does not re-stick as a scrollport is moved.

Frames

A frame takes the wheel, the keyboard, a click, typing and a selection much as the page does, but for itself first. A notch over a frame moves a box inside its document before the document itself, and is passed outward — to the frame around it, and finally to the page — only once each has nowhere left to move; the keyboard follows whichever frame the notch last moved, End and Home included. A frame with more below its own bottom edge draws a thumb down its right edge, as a scrolling box does. A click inside a frame fires in the frame's own realm, at the frame's own coordinates, and a link followed there navigates the frame itself unless its target says otherwise — the frame around it, the page, another frame by name, or a new tab. A field inside a frame takes focus and whatever is typed, as a field on the page does. A selection can begin and grow inside a frame's text, and a drag that crosses the frame's edge carries the selection on past it rather than stopping there.

A document whose body is a frameset is laid out as the grid its rows and cols attributes name (HTML §15.6): each row and column sized from those lists, absolute and percentage entries first, the rest sharing what is left. Between the cells run borders taken out of that space, at the width the border attribute gives, six pixels by default, or none where frameborder says so, in the colour bordercolor names or else gray. A cell holds a frame or another frameset nested inside it, and every frame is drawn live from its own document, the way an iframe is.

Forms, without scripts

Text and password fields, textareas, checkboxes, radio buttons (grouped by name within a form), selects and buttons are drawn, take focus (a click, Tab and Shift+Tab, a label by for or containment), and edit: typing with a caret, Backspace/Delete/Left/Right/Home/End, Space to toggle, Up/Down to pick an option, Enter to submit (a newline in a textarea), Escape to blur. A GET form submits to its action with its data set urlencoded as the query, per the standard's form data set rules (disabled controls, unchecked boxes and non-submitter buttons left out); a form that posts says so in the status bar instead. Author backgrounds and borders replace the built-in look.

Selection and the clipboard

A press on page text sets a caret, a drag extends the selection (held to the content area's edges), Shift with the arrow keys steps by character or by line, Shift+Home/End to the line's ends, Ctrl+A takes everything, Escape drops it. Ctrl+C copies the selected text (or the address bar's) to the operating system's clipboard through CF_UNICODETEXT; Ctrl+V pastes into a field or the address bar. Scripted runs use a clipboard private to the process.

Find in page

Ctrl+F opens a find bar under the toolbar. The count updates as you type ("3 of 12", "No matches"), every match is highlighted with the current one stronger, Enter and Shift+Enter walk them and scroll each into view, Escape closes.

Reader mode

The pilcrow button at the right of the address bar (or the reader: scheme) shows a page's article alone: the container of its paragraphs, found by scoring text length, commas, link density and the names of classes and ids, with the site's heading, the source, absolute links and pictures, and none of the navigation, sidebars or comments. Pressing it again returns to the page.

Keyboard link hints

Pressing f with nothing focused puts a home-row label (asdfghjkl, one letter for up to nine links, two for up to eighty-one) on every link in view. Typing a label follows its link; Backspace takes a letter back; Escape puts the labels away.

Devtools

F12 (or Ctrl+Shift+I) opens a panel under the page: the document's tree on the left, and for the inspected element — a click on the page or on a line of the tree — its summary, its box from the layout, and its computed style on the right, with the box overlaid on the page.

Downloads

Saved under a fresh name in the Downloads folder (--downloads <dir> chooses another), marked with the mark of the web, never opened. See Security defaults.

Testing the shell

The shell is tested by scripts under tests/shell/ that run in CI on Linux, Windows and macOS with chrome screenshots compared byte for byte — see Testing and Scripting the shell.

Clone this wiki locally