Repository navigation
Scripting
Pages run their scripts. A JavaScript engine of our own (src/js, see Architecture) is connected to the document through hand-written bindings (src/bindings), and every host — the window, --render, the WPT runner and the shell's --script replay — drives the same realm the same way.
The parser hands each <script> element over as its end tag goes by, with the parser paused, as the HTML specification asks: an inline classic script runs at once, an external one is fetched through the page's own session and runs at once, and one marked defer or async runs after the parse, in order, before DOMContentLoaded. type="module" scripts and data blocks (JSON, templates, import maps) are skipped by name — modules are not written. <noscript> content is raw text, since scripting is on. A <script> element a script inserts into the document runs; the scripts inside markup set through innerHTML never do, as the specification says. document.write while the parser runs puts its text at the insertion point.
When the parse is done, readyState moves to interactive, the deferred scripts run, DOMContentLoaded fires on the document, readyState moves to complete, and load fires on the window. Timers then run on the host's clock — wall time in the window, a virtual clock in the renderer and the runners — and every entry from the host into script ends with a microtask checkpoint, which runs the promise reactions and queueMicrotask callbacks queued so far, in one order, until none is left.
-
The tree —
Node,Element,Document,Text,Comment,DocumentFragment,DocumentType; the HTML element interfaces fromHTMLAnchorElementtoHTMLVideoElementwith their attributes reflected (hrefresolved against the page,valueandcheckedlive from the shell's form state,selectedIndex,options,elements,rows,cells…);querySelectorandquerySelectorAllover the engine's own selector matcher,getElementById,getElementsByClassName,getElementsByTagName,closest,matches;appendChild,insertBefore,replaceChild,removeChild,append,prepend,before,after,replaceWith,cloneNode,insertAdjacentHTML;innerHTMLandouterHTMLthrough a fragment serializer and the fragment parser;textContent;classList,dataset,attributes,getAttributeand the rest;DOMParser,document.implementation.createHTMLDocument. -
Style and geometry —
element.stylereads and writes the style attribute declaration by declaration;getComputedStyleanswers from the computed style;getBoundingClientRect,offsetWidthand the other box measures come from a layout the host runs on demand, so a script that measures and then sets a width gets the number the page would have. -
Events — the dispatch algorithm with capture, target and bubble phases,
addEventListeneroptions (capture,once,passive),on<type>handlers set as properties or content attributes (areturn falsecancels),Event,CustomEvent,UIEvent,MouseEvent,KeyboardEvent,InputEvent,FocusEvent,WheelEvent,PopStateEvent; the shell firesmousedown,click,keydownandinputat the element under the pointer or in focus, andpreventDefaultkeeps a link from being followed or a control from toggling. -
Binary data —
ArrayBuffer(fixed or resizable,slice,transfer), the typed arrays fromInt8ArraytoFloat64ArraywithFloat16Array, andDataView, all the engine's own (Architecture). -
The window —
setTimeout,setInterval,requestAnimationFrame,requestIdleCallback,queueMicrotask;location(assigning it navigates),history(pushStatemoves the URL),navigator,screen,innerWidth,scrollTo,matchMediaover the engine's media queries,localStorageandsessionStorage(in memory),document.cookie(through the session's jar in the window),URLandURLSearchParams,DOMException,atob/btoa,structuredClone,performance.now,console,alert(logged),IntersectionObserverandResizeObserver(each observed target reported once, in view),MutationObserver(constructible; records are not written),customElements.define(recorded, not upgraded).
An uncaught exception in any script, listener or timer goes to the console with its message and to window.onerror; a promise rejected with no handler by the end of the checkpoint goes to the console once as Uncaught (in promise); the page keeps running.
Everything on the engine's own list in Not-written-yet: async generators and for await, modules, BigInt, Proxy. A page whose bundle needs them fails with a syntax error that names the feature, at parse time or at the first call, and the page stands as its markup alone. fetch, XMLHttpRequest, WebSocket, shadow trees, document.createRange, tree walkers and document.write after the parse are not written either, each throwing or absent rather than pretending. Named access on the window — a bare foo for the element with id="foo" — is not written.
-
sashfold --render page.html --report out.jsonruns the page's scripts first, their timers on a virtual clock for--script-timemilliseconds (3000 by default);--no-scriptsrenders the markup alone, which is the A of every A/B. The report carries ascriptsobject (run, failed, skipped, external, timers, events, uncaught errors, time), and everyconsole.*line and uncaught error is printed to stderr asconsole.<level>: …. - In a
--scriptreplay,advance <ms>moves the pages' clock and runs the timers due, andassert-console <text>checks the page's console output. The window and the replay stop a script that runs past ten seconds. -
tests/test_bindings.cppdrives a realm the way the shell does, under heap stress, andtests/shell/scripting.scriptdrives the shell with a scripted page.
Getting started
What works today
Measured
Design decisions
Testing and tools