js

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2026 License: BSD-3-Clause Imports: 14 Imported by: 0

Documentation

Overview

Package js runs a page's JavaScript against a minimal but real DOM binding on a pure-Go goja ECMAScript runtime. It is deliberately not a full WHATWG implementation: the goal is progressive-enhancement and light hydration — class toggling, attribute/style mutation, small tree edits — so that the subsequent CSS cascade and layout see what a real browser would.

All mutations write through to the shared dom.Node tree. Execution is bounded by a wall-clock budget and a script error never propagates: it is caught, optionally logged, and the render continues.

Index

Constants

View Source
const DefaultTimeout = 5 * time.Second

DefaultTimeout is the script budget when Options.Timeout is zero.

Variables

This section is empty.

Functions

func MarkJSEnabled

func MarkJSEnabled(root *dom.Node)

MarkJSEnabled sets the client-js signal on the document root (swapping the client-nojs no-JS fallback for client-js on <html>) without running any script. The engine calls it before the INITIAL cascade so the first layout — the geometry scripts read back — already reflects a JS-enabled browser.

Types

type Metrics

type Metrics interface {
	// Rect returns the used border-box rectangle (document coords, CSS px) of n.
	// ok is false when n was not laid out (display:none or detached).
	Rect(n *dom.Node) (x, y, w, h float64, ok bool)
	// Computed returns the resolved used value of a CSS property (lower-case,
	// hyphenated — e.g. "width", "display", "margin-top", "font-size"). ok is
	// false for a property the resolver does not model, matching a browser
	// returning "" for an unsupported computed property.
	Computed(n *dom.Node, prop string) (string, bool)
}

Metrics supplies real used geometry and resolved (used) style values from a completed cascade+layout pass. The DOM binding reads it so that getBoundingClientRect / offset* / client* / scroll* and window.getComputedStyle answer with real numbers instead of zeros — the signal responsive scripts and MediaWiki's mw.loader consult to decide what to collapse or reveal. A nil Metrics (the legacy js.Run path) makes those APIs report zeros / inline styles, exactly as before.

type Options

type Options struct {
	// PageURL is the document's final URL, used to resolve script src and to
	// populate window.location.
	PageURL string
	// UserAgent is exposed as navigator.userAgent and sent when fetching scripts.
	UserAgent string
	// Client fetches external <script src>. When nil, external scripts are skipped
	// (inline scripts still run).
	Client *http.Client
	// Ctx bounds network fetches; its cancellation also stops script execution.
	Ctx context.Context
	// Timeout is the total wall-clock budget for all scripts and drained timers.
	// Zero selects DefaultTimeout.
	Timeout time.Duration
	// ViewportWidth / ViewportHeight back window.innerWidth / innerHeight.
	ViewportWidth  int
	ViewportHeight int
	// Log, if non-nil, receives console.* output as "level: message" lines.
	Log func(string)
}

Options configures a Run.

type Result

type Result struct {
	ScriptsRun    int   // scripts that executed (inline + fetched)
	ScriptsFailed int   // scripts that threw or failed to compile
	TimersRun     int   // queued timer/animation callbacks drained
	Err           error // first fatal setup error (nil on normal completion)
}

Result reports what a Run did (used by tests and by the engine's logging).

func Run

func Run(root *dom.Node, opt Options) Result

Run builds the DOM binding on root (a dom.Document node), sets the JS-enabled signal, executes the page's scripts in document order, drains queued timers, and dispatches DOMContentLoaded/load. It never returns a script error as fatal; Result.Err is only set for setup failures. It is the one-shot, no-layout-feedback entry point (used by the js package's own tests); the engine drives a Session directly so scripts can read real geometry.

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session is a live page-script execution bound to one DOM tree and one goja runtime. Unlike the one-shot Run, it stays resident across layout passes: the engine lays the page out, feeds the geometry in via SetMetrics, runs scripts (which may read that geometry and mutate the DOM / inject <script>/<style>), re-lays-out, and runs any newly-injected scripts — iterating to a bounded fixpoint before the final paint.

func Begin

func Begin(root *dom.Node, opt Options) *Session

Begin builds the DOM/BOM binding on a fresh runtime, sets the client-js signal, and starts the wall-clock/interrupt watchdog. The caller MUST call Close to release the watchdog goroutine. A binding-setup panic is contained; Begin always returns a usable (possibly inert) Session.

func (*Session) Close

func (s *Session) Close()

Close stops the watchdog. It is idempotent.

func (*Session) Result

func (s *Session) Result() Result

Result returns the cumulative execution tally.

func (*Session) RunInitial

func (s *Session) RunInitial()

RunInitial executes every page <script> in document order, then dispatches DOMContentLoaded/load, draining queued timers/promises/XHR callbacks to quiescence within the budget. Contained against panics.

func (*Session) RunPending

func (s *Session) RunPending() (ran bool)

RunPending executes any <script> elements inserted into the DOM since the last run (the core of a ResourceLoader-style dynamic loader) in document order, then drains the async loop. It reports whether at least one new script ran.

func (*Session) SetMetrics

func (s *Session) SetMetrics(m Metrics)

SetMetrics installs (or replaces) the geometry/used-value source the DOM binding reads back. Call it after each layout pass, before running scripts.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL