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. A type="module" script is fetched with its whole import graph — in CORS mode, with the credentials its crossorigin attribute asks for, each response held to a JavaScript type — and runs after the parse in document order with the deferred classic scripts, or at once when marked async or inserted by a script; an inline module has the document's URL as its base and as its import.meta.url, module code is strict with no this and no document.currentScript, a module named twice is fetched and evaluated once, a failure anywhere in a graph is the element's error event and a console line, and import() from any script resolves against the document on the same module map. Data blocks (JSON, templates, import maps) are skipped by name. <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); on the page sideTextEncoderandTextDecoder(UTF-8 with its streaming and fatal modes, UTF-16, windows-1252),BlobandFilewithslice,text,arrayBufferandbytes, andcrypto.getRandomValuesover any integer view. -
The network —
fetchwithHeaders,Request,ResponseandFormData(bodies as strings,Blobs, buffers,FormDataas multipart,URLSearchParamsas urlencoded;text,json,arrayBuffer,blob,bytes,formDatato read one),XMLHttpRequestwith its five states, its events andresponseType,AbortController/AbortSignal,MessageChannel/MessagePortandwindow.postMessageto the window itself. A request goes through the shell's own loader with the page's session — cookies only where the request allows them — and the browser's rules apply: a cross-origin response reaches the page only when itsAccess-Control-Allow-Originsays so, a request that is not a simple one sends a preflight first,no-corsgets an opaque response. The round trip runs on the event loop's next task, synchronously inside it, until the process split; a response never settles a promise or fires an event in the same turn that asked for it. -
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; on the page, import maps, so a bare module specifier fails to resolve. 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. WebSocket, streams (Response.body is null; a body is read whole), URL.createObjectURL, 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> [<times>]moves the pages' clock and runs the timers due, that many times over (so a chain of timers each set by the last runs through), 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