Repository navigation
TermDOM follows the web platform specifications where they apply. Where it departs from one, these notes say so.
0.2.4 makes restyling and relayout after a small change much cheaper on large pages, and renders far more of the real web correctly: GitHub, Wikipedia, Hacker News, DuckDuckGo and newsletter email all show content they dropped or garbled before. It adds a scripting option, and markup's handler attributes no longer run unless you turn it on. See Changed.
Added
-
scriptingsays whether the page's scripts run, with@media (scripting)'s values:"none", the default, or"enabled". TermDOM runs no<script>itself, so it's the embedder's to say. With"none", a<noscript>'s markup is parsed and shown, as in a browser with scripting off, and@media (scripting: none)applies. With"enabled", handler attributes such asonclick="..."run, with the program's own powers, so enable it only for markup you trust. Acspwhosescript-src-attr, or thescript-srcordefault-srcit falls back to, is'none'turns it off. Any other value throws aTypeError.new TermDOM({scripting: "enabled"}); // markup you wrote yourself
-
reducedMotiontells the page whether its reader prefers reduced motion, with@media (prefers-reduced-motion)'s values:"no-preference", the default, or"reduce". Pages that hide content until a script reveals it often show it underreduce. -
A history the embedder can follow.
pushState()andreplaceState()move the document's URL under HTML's rules, and a fragment navigation is an entry of its own. The TermDOM dispatcheshistorychange(withurl,stateandreplace) for each entry, andhistorytraverse(withdelta) forback(),forward()andgo().term.traverse(url, state)takes the document to one of its entries, firingpopstate, thenhashchangewhen only the fragment differs. -
The terminal's cursor follows the caret's CSS (#156).
caret-shapesets its style (block,underscoreorbar),caret-animation: manualstops it blinking, andcaret-colorsets its color.autokeeps the terminal's own. The cursor is reset when the session ends or is suspended, but only if a page changed it. -
autofocusin a page's starting markup focuses its first focusable element, as at a browser's page load. Before, it worked only for elements inserted later, so a page given to the constructor whole kept the focus on the body. -
translateand a transform's translation move where a box is drawn and takes clicks, leaving layout alone, as in a browser. A percentage is of the box's own size. Rotation, scale and skew are still ignored. A side menu kept off screen withtranslateX(-100%)no longer covers the page. -
clipon an absolutely positioned box cuts it in painting and hit testing, so the "visually hidden" pattern for skip links and screen-reader text hides it. -
opacity: 0draws nothing of a box or what it holds, while the box keeps its place and takes pointer events. Any other opacity still draws whole, since a cell has no partial opacity.
Changed
- Handler attributes run only with
scripting: "enabled". Anonclick="..."in markup used to run by default, with the program's full powers, so markup a program only meant to show could run code. Documents without a window, and pages under acspthat disallows inline scripts, already didn't run them. A program that relies on handler attributes in its own markup must now passscripting: "enabled". Handlers added from script, withaddEventListener()orelement.onclick = ..., are unaffected. <noscript>is shown by default. Its markup used to be held as text, as though scripts ran, and nothing hid it, so search pages showed their fallback markup as literal tags. With the defaultscripting: "none"it's parsed and shown. With"enabled"it shows nothing.@media (scripting)answers as the document runs, and no longer always matchesenabled.- The root's background fills the whole screen, or the body's when the root has none, as CSS Backgrounds says. A page's background used to stop where its content did.
- A
border="0"table's cells have no borders. The UA sheet borders every cell, which suits a table written for the terminal, but layout tables, as Hacker News nests three deep, drew a box around every cell. Author CSS can put the borders back. execCommand()and thequeryCommand*()methods answer a command TermDOM doesn't support withfalse, or""for its value, as browsers do, rather than throwingNotSupportedError. A page that checks for a command before using it, as xterm.js does, no longer stops at the check.
Performance
- Restyling a page after a change reads far less of it. Rules are filed by the ids, classes and attributes their subjects need, including through a lone
:is()or:where(), and ruled out by what an element's ancestors can't satisfy. Elements that style alike share their computed style, and a stylesheet's text is parsed once. A sheet joining the document is read alone and merged into the cascade order, instead of every sheet being read and sorted again. - A change deep in a document lays out only what it touched. A key used to lay out every message of a 500-message transcript again. Now each block lays out only its stale children and keeps its layout when they come back the same size.
- Smaller frames. Moving down the screen uses one cursor movement instead of a newline per row, so a change on the bottom row of a 40-row transcript writes 40 bytes instead of 115.
- Clicking on a long page no longer walks all its text. Reading the selection on mouse release took about 450 ms on a long Hacker News thread, and takes under a millisecond.
- A form's controls are kept between frames. On a long Hacker News thread with one form, a frame after a small change takes 10 to 50 ms instead of 100 to 140.
- A disposed document's styles are released, and a table of shared style records no longer grows without bound on a page that animates a length.
Fixed
- A block image with alt text that loaded after showing that text, as in an email client that blocks remote images until asked, stayed one row tall. Newsletter mastheads (
<img alt="Brand" style="display:block;width:100%">) are written this way. display: inline-flexlaid out as a block whose children stacked one per row. It's an inline flex container now.- A fit-content flex container of
flex: 1items came out as wide as its padding, squeezing its items to nothing. Growing items now take their max-content size there (css-flexbox-1 §9.9.1). - Text in a grid track or table column being sized didn't break at its
max-width, so it came out one line, drawn clipped. - A
<br>underwhite-space: nowrapdidn't break the line. - An isolated box (
isolation: isolate) that wasn't positioned never painted its positioned descendants. Pages that wrap everything in one, as GitHub does, showed no fixed header or positioned content. It paints as a positioned box withz-index: 0would (CSS 2 Appendix E). - A positioned box clipped out of view still took clicks meant for the page around it.
- A shorthand holding
var()was split by its tokens before substitution, sobackground: var(--rule)put a gradient inbackground-color, andpadding: var(--p)gave every side both lengths. It's split once its variables are substituted (css-variables-1). - A CSS string's hex escapes kept their digits as text, so Wikipedia's
content: "\a0 · "drew "a0 · ". Escapes decode as css-syntax says. - A scroller with vertical padding could scroll by its padding with nothing to show.
- A page's class extending an engine class, such as a router extending
EventTargetor a custom element, lost its own methods named with a leading_. - Assigning to an attribute that forwards its value, such as
iframe.sandbox = "allow-scripts"(as reCAPTCHA does),classList,relListorstyle, threw aTypeError([PutForwards]). - An HTML element with a namespace prefix, such as an
h:brmade withcreateElementNS(), wasn't recognized as its kind.
These notes were generated with Claude Code.