A browser pane: a web view with Web Inspector in a placeholder herdr pane, on This Mac and on remote machines
herdr cannot draw a web page. So a herdr pane holds the place in the layout,
and the app draws a web view in its rectangle. The browser is a pane kind in
the plugin registry, built into the app with a manifest written in Swift
(The browser). What is not built yet is in
Future work. Research:
browser.md.
A pane is a browser pane when its herdr label is ovvi:browser#<iid> <url>,
for example ovvi:browser#m4p8sa http://localhost:3000/dashboard
(Labels and instance ids). The app knows
the pane from the label alone: a pane made by the app, by an agent or in
herdr's TUI is the same.
iid (the instance id): exactly 6 characters of [a-z0-9], not used by
another pane of the session. It names the pane's state file and never
changes.
url: http:// or https://, at most 1024 characters, no control
characters. A bare host:port gets http://. file:// opens only on This
Mac; a remote machine serves its files through a server. A new pane has no
url yet: ovvi:browser#<iid>.
The address is as the machine sees it: localhost means the pane's machine
(Remote pages). herdr keeps it in its session file and
shows it to every device, so an address must not hold a secret (for
example a ?token= URL).
The label is the only pane field that has no length limit and survives
moves and cold restarts (herdr 0.9.5). Metadata tokens are cut at 80
characters and lost on restart.
Rename Pane works on a browser pane. A name that breaks the label turns the
card back into a terminal. Reset Pane Name is disabled.
The app picks a free iid and makes the pane in one call:
layout.apply {workspace_id, focus:false, root:{type:"pane", cwd, label, command}} in a new tab, with the old pane's live folder, the label
ovvi:browser#<iid> and the placeholder as its command
(Create).
pane.move puts it beside the old pane, and pane.close closes the old
pane, as Replace Pane does. If the move fails, the new pane closes. The new
pane stays hidden by its label in every tab until the old one is gone, so
the temporary tab never shows.
The card shows "Opening Browser…" until the snapshot that puts the new
pane in the old one's place, so the old pane's shell never shows. Then
it shows an empty address field that has the keyboard.
The user types an address. Return loads it, approves its origin
(When a page loads by itself), removes the
saved state, and sets the label with
pane.rename {label: "ovvi:browser#<iid> <url>"}.
The placeholder, about 25 lines of sh that herdr runs as the pane's command,
prints "Browser", the address without its scheme (example.com/docs, or
/Users/me/report.html for a file: address; nothing for none) and "Shown in
Ovvi" in the middle of the pane. It follows the page: when the label changes
(a typed address, a link, an agent's ovvi open --replace), the app sends the
placeholder SIGWINCH, and it reads its label from herdr (herdr pane get)
and draws again. A resize draws it again the same way. Keys typed into the pane
do nothing. The TUI's border shows the current label. After a cold restart herdr brings the pane back as a plain shell
with its label, and the app types the placeholder into it again
(The placeholder).
The card sits in herdr's split like any card, for example beside the dev
server's terminal.
Header. Two rows, as on an agent card
(Pane card). Row 1 (36 pt): the page's icon
(16 pt) and its title, else its host, else its address, else "New Tab".
Then the card buttons, on the keyboard card and on a hovered card. Row 2
(32 pt) is the toolbar: Back, Forward and Reload (Stop while a page
loads), 22 × 22 pt buttons with 11 pt glyphs, then the address field,
which takes the free width, then the Web Inspector button (hammer). Back,
Forward and Reload touch; 6 pt between them, the address field and the Web
Inspector button. The toolbar's buttons have the card buttons' color and dim
as any disabled card button when they cannot act. Row 2 ends where row 1
does, 8 pt in.
Swipes. A two-finger sideways swipe over the page goes back or forward,
as in Safari (Swiping).
Page icon. WebKit has no favicon API. After each load, one script in
the page reads its <link rel~=icon> (else /favicon.ico) and fetches it,
at most 1 MiB in 5 s, and gives the image back in base64. The page's own
fetch goes through its proxy and its loopback rule
(Remote localhost), so CORS keeps a few cross-origin
icons out. The globe shows while there is no icon, and over a failed
load.
Address field. A capsule 26 pt tall, at least 120 pt wide, filled with
the primary color at 0.06 under a 0.5 pt edge at 0.08, text 12.5 pt: the
host and port in full, the rest in secondary. Empty, it says "Ask Google or
type a URL".
On a remote machine the machine's name sits in a chip at its end (11 pt, in
the needs-you color on a 0.18 fill of it). A click on the chip switches the
card to This Mac's network: the chip then says "This Mac" in secondary text
on a 0.08 fill of the primary color
(This Mac's network). Under the pointer the field
shows the text cursor (the chip shows the arrow) and its fill goes to 0.1.
A click or ⌘L selects the whole address under a 1.5 pt accent edge. Return loads it and gives the keyboard
to the page. Text that is not an address goes to a Google search
(https://www.google.com/search?q=…): text with a space, or text with no
scheme (https://), no dot, and a host that is not localhost or an IPv6
address ([::1]). So localhost:3000 and example.com load, and
swift actors, ovvi and devbox:3000 search. Esc puts back the current address and gives the keyboard to the
page.
Empty card. Until its first page, the card shows a 44 pt globe in
tertiary. The web view stays hidden until a page commits, because an empty
web view draws white.
A click off the page. The hidden web view takes no click. So a click
anywhere on the empty card, the loader, an error or "Open this page?" makes
the card the keyboard card, as a click on a page does, also a click on
its buttons. The page takes the keyboard, or the address field when the
card has no address.
Loading. A 2 pt progress line in the accent runs under the header while
a page loads.
Loader. Only while a card first opens or reads its saved state: the
globe at 32 pt, and under it a spinner left of "Opening Browser…" in
13 pt secondary. It fades out over 0.15 s (at once with Reduce Motion). A
typed address shows the progress line alone. A card made again after it
was dropped (What stays in memory) shows its last
picture in place of the loader, with no fade, until its page draws. A page
draws once its document is parsed and painted (DOMContentLoaded and one
frame), not when every image and script finished: one that never answers
would keep the loader over the page.
Error. A page that fails shows, in the middle of the card, a warning
symbol, the app's words in 15 pt semibold, a line in secondary text for a refused
local port, and Reload:
Case
Text
nothing listens on a localhost port
"Nothing answers on localhost:3001" (on a remote machine "… on ‹machine›"), then "Is the dev server running?"
a remote machine cannot reach a name
"‹machine› cannot reach ‹host:port›"
the remote session is not live
"‹machine› is not connected"
an unknown name
"Cannot find ‹host›"
the proxy did not start
"Ovvi could not start the proxy to ‹machine›"
anything else
WebKit's own text
The address field keeps the address that failed. WebKit goes back to the
last page's address, but the field does not follow it. Reload loads the
failed address again.
The page is inset 6 pt from the card's sides and bottom, with a 6 pt
radius, and starts 6 pt under the toolbar, so Back has 11 pt under it. Cards are never glass.
Resize. While the card's size changes (a divider or a window drag, the
sidebar), the page keeps its size, clipped, or with the card's fill at its
new edge. 150 ms after the last change, or when a window drag ends, the
page takes the new size: one page layout per drag, as a terminal keeps its
grid (States). A split's motion gives the page its new
size once, as the motion starts. A page laid out at every frame makes the
split's motion stutter.
The tab's title is "Browser", then the page's title (else its host) in
secondary text, as a terminal tab's "zsh ovvi" ("Browser | Hacker News"
where a title is plain text: Tab bar). Its
program icon shows globe. Its overview tile shows the page's last
picture (Overview).
The window's PaneViewRegistry keeps each web view by terminal id, so a
card scrolled away or moved keeps its page, up to the budget in
What stays in memory.
A floated browser pane (Floating pane)
keeps the toolbar under the floating window's title row, 4 pt down from the
card's top edge. The title row
shows "Browser" and the page's title in secondary text, with no place.
A kept tab's page (Tab page) keeps its web views
in the window, hidden, and a switch to it shows them. A tab whose page was
not kept puts its kept web views back into the window as they are: recorded
at 60 fps, WebKit draws no blank frame on such a switch, so nothing covers
the page. Each web card (a browser's or a plugin's) keeps a picture of
its page, for its overview tile and for a card made again:
Taken as the card leaves view, and for the cards in view as the
overview opens (takeSnapshot, about 8 ms in WebKit's process, also out
of a window). Every picture is taken at 1x: 6 MB for a full-window card
on a Retina screen, not 24. A tile is scaled down; a dropped card made
again shows the picture until its page loaded.
The cap. One cap over every picture in the app (web cards' and
chats'): 200 MB, the least recently taken out first. A card with no
picture shows its loader. Pictures are not purgeable: macOS takes
volatile surfaces back within seconds.
A web card is made as its tab first shows, not ahead: a switch to a tab
whose browser is made then takes 62 ms to its first frame, and one made
ahead 61 ms.
When a new host adopts a web view, the view keeps its size: a view set to
the host's zero size lays the page out again and draws it blank.
The web cards of the shown tab and of every floating window stay, plus
the 25 most recently shown other web cards, browsers and plugins together,
over all windows (spareWebCards, D46). Each kept card costs about 28 MB,
mostly its WebKit process, and no CPU at rest.
An older one is dropped: its web view and its process go, and its last
picture stays for its tile and its next card. When it shows again, the
card is made from its label, under that picture. A browser comes back
with its saved history (State on the machine).
A plugin card comes back at the place in its label, with its state from
its state file (State), and with no Back and
Forward.
A floated card, a plugin card with unsaved edits and a browser with a popup
open (a sign-in window) are never dropped.
On critical memory pressure from macOS
(DispatchSource.makeMemoryPressureSource), the kept cards out of view go
down to half the bound, and so do the kept terminals. A warning does
nothing: macOS can send one every 30 minutes while memory is free, and a
dropped card costs its next show 100–200 ms.
The two warm web views (Other rules) cost about 10 MB each, their
page processes with no page.
spareWebCards and spareTerminals (defaults, or -spareWebCards N at
launch) set the two bounds.
New tabs. ⌘-click on a link, a link with target=_blank, and Open
Link in New Tab in the page's context menu open the link in a new browser
pane, in a new herdr tab right after the card's tab. The new tab stays
in the background, as in Safari. The menu's Open Image, Open Frame
and Open Video in New Tab do the same. These items replace WebKit's "in
New Window" items.
The user picked the link, so its origin is approved
(When a page loads by itself) and the
new card loads it with no question.
Only http, https and file links open a tab. A target=_blank form
and a blob: or data: page load in the card.
WebKit does not tell rel=opener apart, so such a link opens a tab too.
To the side. ⌥-click on a link (WebKit's download gesture) and Open
Link to the Side, under Open Link in New Tab, open the link in a new
browser pane beside the card, in the background, its origin approved.
Links open as everywhere in the app (Opening links and
files).
The page's context menu has no download items: the app saves no
downloads.
A page's window.open (an OAuth login, for example) opens over the card,
24 pt in, on the floating glass, with its host and a ×. It gets the
keyboard. The popup keeps its opener. window.close(), the × and Esc close
it, Esc also while the popup's own page has the keyboard. The page gets
the keyboard back if the popup had it.
A mailto: or app link that the user clicks opens its app. The page stays.
A video's full-screen button and a page's requestFullscreen (a site's
own player) show it full screen in a space of its own. Esc or the
player's button brings it back to the card.
Page alerts and confirms show as a sheet on the window, titled with the
origin. A file input opens the system's open panel as a sheet.
The Web Inspector button or ⌥⌘I (Safari's key) shows WebKit's inspector,
docked at the bottom of the card. Its own control moves it to a window.
Showing it has no public API: the app calls WebKit's _WKInspectorshow
(cmux ships the same). WebKit keeps the last docked or window choice in the
app's defaults (__WebInspectorPageGroupLevel1__.WebKit2InspectorStartsAttached),
so the app removes that key before each show, and it docks every time.
Every browser card sets isInspectable, so Safari's Develop menu
(Develop › ‹this Mac› › Ovvi) reaches each page too, and the page's menu (a
right-click) has Inspect Element. Plugin cards have neither: a
right-click on empty space in their page shows only Reload.
The button and ⌥⌘I are disabled until the card has a page.
The page and its back and forward list are saved on the pane's machine, in
the instance state file
(State):
$HOME/.local/state/ovvi/<session>/browser/<iid>.json.
It holds {"url": …, "history": …}. url is as the machine sees it.
history is WebKit's interactionState (the back and forward list) in
base64, left out at 128 KiB and over.
Written 500 ms after a navigation ends or the address changes (a
single-page app's route). Last writer wins. Nothing is written after the
pane closed.
Read when the card is built: a relaunch, a session restart, another
device. The card opens the saved page with its history, else the label's
address. Both follow the load rule below, and so does the history's current
page: one from another origin that is not approved stops and asks.
Typing an address removes the file. The device that closes the pane
removes it, and the 30-day cleanup on connect takes the rest.
An open card follows its label. When the label gets another address
from elsewhere (an agent's rename), the card loads it as a new entry of its
history, by the load rule. It ignores the address that its own rename
replaced, so an older snapshot does not undo a typed address.
Any process on a machine (a prompt-injected agent, a script) can rename a
pane or write its state file. So:
A page the user types loads at once and approves its origin for that
machine; so do Open and a link opened in a new tab. A page that only
finishes loading approves nothing: a link the user clicks loads in the
card, but after a relaunch the card asks before it opens a saved page from
an origin not approved.
A page from the machine (the label, the state file, a rename) loads by
itself only if its origin is approved for that machine or on the safe list.
Otherwise the card asks Open this page?, under a globe, with the
address (host in full, path dimmed, cut in the middle) and two buttons:
Not Now and Open. Return opens and Esc is Not Now while the card has
the keyboard. Open approves the origin. Not Now leaves the page the card
had, or the empty card.
The safe list: the machine's own localhost, 127.0.0.1, [::1] and
*.localhost on any port, This Mac's file:// pages, and github.com,
gitlab.com, bitbucket.org, developer.apple.com,
developer.mozilla.org, docs.github.com, vercel.com, *.vercel.app
and *.netlify.app. On This Mac's network the
loopback names are This Mac's, so they ask like any other origin.
Approvals are per device and per machine, the newest 200 web origins, in
the app's defaults (browserApprovals.<machine id>,
Data). Removing a machine removes them.
Settings ▸ Privacy lists, adds and removes them
(Privacy).
Each machine has its own WKWebsiteDataStore(forIdentifier:), with the
machine's id as its identifier (This Mac has a fixed one).
The store keeps cookies, local storage, IndexedDB, the cache and service
workers on disk, under ~/Library/WebKit/<bundle id>/WebsiteDataStore/<id>/,
across relaunches. Logins persist, and machines stay apart.
Stores are per device: another Mac has its own logins.
Removing a machine removes its store
(WKWebsiteDataStore.remove(forIdentifier:)). If a window still uses the
store, it stays on disk.
WebKit uses no keychain item for cookies or storage, so no keychain prompt
appears.
On a remote machine, localhost:3000 means that machine, and private names
resolve as the machine resolves them. Every page of the machine's cards goes
through one SOCKS proxy on the Mac, carried by the machine's existing SSH
connection. Nothing is installed on the machine.
The front. A SOCKS5 listener of the app's own (HerdrKit's SOCKSFront)
on a free loopback port is the store's proxy. It passes each connection to
ssh's own SOCKS forward on the machine's ControlMaster
(The SSH connection):
ssh's -D closes a connection it cannot open with no SOCKS reply, and
WebKit then waits 60 s. The front answers "refused" at once instead.
<machine>.localhost. WebKit never sends localhost, 127.0.0.1 or
[::1] through a proxy. So a remote card loads
http://<machine>.localhost:3000 where the machine sees
http://localhost:3000, and the front asks the machine for localhost
wherever a page asked for <anything>.localhost. The machine needs no
*.localhost resolver. The address field and the label show localhost,
with the machine's chip.
<machine> is the machine's name in lowercase words joined by dashes
("machine" when none is left). It is not made unique: each machine has its
own store and proxy, so a clash mixes nothing. After a rename, pages keep
working, but their origin changes and their logins go.
This Mac's loopback is never reached from a remote card. Loads and a
restored back and forward list wait for the proxy. A main-frame link,
redirect or form post to localhost is sent as <machine>.localhost. A
content rule blocks every other loopback request: images, fetches,
websockets, frames and popups, and spellings such as 127.1, localhost.
and [::ffff:7f…]. So a remote page whose script calls
http://localhost:8000 is blocked: serve that API on the page's own origin.
Public sites in a remote card also load through the machine.
The proxy starts with the machine's first browser card and ends with its
last (ssh -O cancel). The -D ends with its master, and the next page
connection forwards again on the new master. When nothing answers on the
-D port, the front asks for a new forward once and tries again. A front
whose listener ends by itself listens again on the same port, every 2 s until
it can. A card whose proxy did not start tries again on Reload. So a page
that failed loads again on Reload, without closing the window.
No page waits for ever. ssh answers a page's connection once the machine
opened it. With no answer in 5 s, the app sends a command over the relay. If
that command gets no answer in 3 s (longer when the relay must start), the
master or its link hangs: a stopped
master, or a link left half open by a sleep. The app ends the master if it
still answers -O exit, starts a new one, and tries the page once on it.
If the relay answers, the machine is only slow, and the page waits 30 s more.
A page that still gets no answer shows its error and Reload. A connection
the front already
carries (a dev server's live-reload websocket) lives on after the last card
until WebKit drops it. Two machines with one SSH target share one proxy.
The forward uses no SSH session. It needs
AllowTcpForwarding yes on the server (the default). A server that refuses
shows as "cannot reach".
The machine chip in a remote card's address field is a switch. A click loads
the card through This Mac's network, and a second click loads it through the
machine again.
On This Mac's network the card uses This Mac's store, with no proxy. So
localhost is This Mac's, and logins are This Mac's. The chip says "This
Mac", plain.
Each switch makes a new web view, because a store's network cannot change.
The card loads the address in its field again. Its back and forward list
starts again, because its entries belong to the other network.
file:// pages stay closed to a remote card on either network.
A page that the machine puts in the label or the state file loads by
itself only from an approved origin. This Mac's localhost is not safe
then. A switch approves the address it loads.
A link opened in a new tab keeps the card's network.
A new card starts on the network that Settings ▸ Plugins ▸ Browser
names: "Web pages on remote machines load through" The Machine (the
default) or This Mac. The setting is one value for every remote
machine, kept in settings.json as "plugins": {"browser": {"network": "mac"}} (Plugin settings in settings.json).
It changes no open card: the chip switches a card.
The choice is kept per card on this Mac, through relaunches: in the app's
defaults, under browserViaMac.<machine id>, a list of
<session>/<iid>, the newest 200. Another Mac has its own choice.
Removing a machine removes its list.
Reload Page (enabled once there is a page or an error, not while the card asks)
View
⌘L
Open Location: the address field, all selected
Edit
⌥⌘I
Show Web Inspector
Edit
⌘+ ⌘− ⌘0
zoom the page, 50 to 300 % in 10 % steps, while the card has the keyboard (in a floating window, its own card's); ⌘0 also undoes a pinch
View
⇧⌘R
Replace Pane: a fresh shell with the New pane chooser, with no question
File
A trackpad pinch magnifies the page around the fingers, and a two-finger
double tap zooms to the block under them and back, as in Safari
(allowsMagnification). The page keeps its layout: it grows as a picture
does, and scrolls inside the card; ⌘+ and ⌘− reflow it instead. Smart zoom
needs the trackpad's Smart zoom setting. Overview tiles show pictures, so a
pinch over one does nothing. Plugin cards and window.open popups do not
magnify.
Open Browser Here is hidden. Open Terminal Here and Start Agent Here replace
the pane with no question while it runs only its placeholder or a plain
shell, and closing it asks nothing then either. Any other program asks, as
in any pane: any process can label a pane.
Find (⌘F), Insert Path, Go to Directory and Reset Pane Name are disabled.
A floating window offers Reload Page, Open Location and Show Web Inspector
too.
Any agent in a herdr pane opens a browser card next to itself, on any
machine, with the ovvi command
(CLI):
ovvi open browser http://localhost:3000 # beside the agent's paneovvi open browser http://localhost:3000 --tab # in a new tab, with a notice that offers Showovvi open browser http://localhost:3000/x --replace w1:p7 # the card at w1:p7 goes to a new addressovvi open browser ./report.html # a file, on This Mac only
Ovvi makes the pane as Open Browser Here does, with the label
ovvi:browser#<iid> <url> and its placeholder, and answers with the
pane, the label and whether the card shows.
--replace renames the card's label with its own iid. The open card loads
the new address as a new history entry, by the load rule, and Back
returns. Ovvi removes the card's saved state first, so a card made later
loads the new address, not the saved one.
A card with the same address already in the session is the answer, unless
--new.
The label is how it works underneath: any process that labels a pane by hand
gets the same card.
herdr pane split "$HERDR_PANE_ID" --direction right --no-focus makes a
pane beside its own.
herdr pane rename <pane> "ovvi:browser#<iid> <url>" makes it a browser
pane, with a fresh iid.
herdr pane report-metadata <pane> --source <name> --token ovvi-label=1 --ttl-ms 1000 on that pane. herdr sends no event for a rename, so this
token's pane.updated event makes the app read the snapshot at once.
Without it the card shows at the next event or the 30 s health check.
The app types the placeholder into the labeled pane's shell
(The placeholder).
Plugin pages get the ovvi object, under the rules in
Security and trust.
A file:// page can show any file that the user can read, as in Safari: a
page's image, video or script can come from another folder
(../clips/intro.mp4), and a link to a page in another folder opens in the
card. The page's own rules stay WebKit's: a file page is an origin of its
own, so its scripts cannot fetch or read other files. Plugin pages never
load files this way: they come through the ovvi-plugin: scheme.
The web view's user agent ends with Version/‹macOS major.minor› Safari/605.1.15 (applicationNameForUserAgent), as Safari's does, so sites
that sniff for Safari work.
When the system ends the page's process
(webViewWebContentProcessDidTerminate), the card loads the page again by
itself, once per load, and WebKit brings back its scroll. A page whose
process ends again before it loaded shows "The page stopped working" with
Reload, so the card never stays blank, also in a card out of view. A
load counts until it finished: a page whose load never ends (an image that
never answers) shows the failure on its next ended process.
WebKit runs helper processes for web cards: one GPU process and one
network process, which all cards share, and one page process for each
card. It starts the network process only after the first page process
runs. On a busy Mac, the first start of these processes can take many
seconds, and a new web view's page process takes 0.1 s to a few seconds.
So the app keeps two warm web views out of any window: one for This Mac's
browser cards and one for plugin cards. Each runs one script with no page,
so its page process starts (and with it the GPU and network processes),
and it keeps no history. A new card takes the warm view of its kind, and
the app starts the next one. So a card that opens beside another (a
⌥-click on a link) draws its page with no wait for a page process. Both
start 3 s after launch, after the restored cards. A remote machine's
browser card that loads through the machine makes its own web view, with
its own page process. The app never waits for these processes: a card
keeps its loader or its empty look until its page draws.
When WebKit's GPU process stops answering (a busy Mac), WebKit gives up on
it and starts another, but it cannot end the old one, and every page keeps
drawing through it: each web card, browser and plugin, stays blank, also
after Reload. The app ends that process within a few seconds. The pages
connect to the new one and draw again where they were, with no reload; a
card out of view does so when it shows. WebKit has no public API for its
GPU process: the app reads its process id with _gpuProcessIdentifier.
Plain http needs NSAllowsArbitraryLoadsInWebContent and
NSAllowsLocalNetworking in Info.plist.
Labels or state files written by a hostile machine
a page opens by itself
an origin outside the approvals and the safe list needs a click, also for the saved history's current page
A hostile machine serves a page on its own localhost (safe list)
that page loads by itself, and its links, redirects and requests go anywhere
accepted: the rule stops a label or state file alone from opening an unapproved origin, not a page the machine serves; a link it leads to approves nothing
The SOCKS port is open to other accounts on the Mac
another account can reach the machine's network
accepted for a single-user Mac; a random port, open only while a card needs it
A server with AllowTcpForwarding no
remote pages cannot load
the card says "cannot reach"; Tailscale or LAN addresses still load
_WKInspector is private
the Web Inspector button can break on a macOS update
Safari's Develop menu still reaches every browser card
Web view memory
memory grows with many browser cards
at most 25 web cards kept out of view, half on critical memory pressure (What stays in memory)
WKWebView with Web Inspector. No size cost, the same engine as plugin
cards and iOS, and a proxy per store. Considered: Chromium through CEF, for
Chrome DevTools. It adds 330 MB to the app, uses 195–281 MB for the first
page, needs an engine update about every 4 weeks, and its shared keychain
item holds every request behind a password prompt.
Rejected: real Chrome driven over its DevTools protocol; a Chromium build of
our own.
A built-in pane kind with one store per machine, and the load rule
above. A label or state file alone never opens an origin that is not
approved or safe, and machines stay apart. A page that loads (the machine's
own localhost among them) can still lead anywhere: see Risks. Rejected: loading every label by itself; tracking
which device created or edited a label.
Remote pages through one SOCKS proxy per machine, with the app's front
before ssh's -D. One forward covers every port and name on the machine,
refusals come at once, and nothing is installed there. Rejected: a forward
per port (ports clash between machines and with the Mac, and the origin
changes); a server helper of our own.
A file page can show the whole disk, its scripts stay in their origin.
A page and its media often sit in sibling folders, and Safari loads them.
WebKit already gives read access to the whole disk to a file page opened
from a link or the history. Rejected: only the page's folder (a sibling's
media is blocked); file access for scripts (allowFileAccessFromFileURLs), which
lets any downloaded page read the user's files.
The typed address in the label, the current one in the state file.
Coming back to a browser card, on any device or after a restart, shows the
page where the user left it. Rejected: rewriting the label on every
navigation (a rename per click, seen by every TUI); state kept per device
in the app.