Skip to content

Kernel

wybthon.kernel

kernel

Batched DOM command buffer and rendering backends.

This module is the single point of contact between Wybthon's renderer and the real DOM. Instead of calling DOM APIs directly (each call crosses the Python-to-JS bridge in Pyodide), the reconciler and prop appliers emit compact operations into a buffer. At well-defined commit points (end of a render, end of an effect flush) the whole buffer is serialized once and handed to a small JavaScript kernel that applies every operation natively. A mount of a 1,000-row table becomes one bridge crossing instead of tens of thousands.

Core concepts:

  • Node handles. DOM nodes are referred to by integer ids allocated on the Python side. The kernel keeps an id -> Node registry, so no JsProxy objects flow through the hot path.
  • Ops. Each operation is a small tuple, (opcode, ...args), serialized as a JSON array. See the OP_* constants.
  • Backends. BrowserBackend drives the real DOM through the embedded JS kernel. PythonBackend is a reference interpreter that applies the same ops to any DOM-like stub document; it backs the unit tests and the stubbed benchmark so both exercise the exact protocol the browser sees.
  • Events. Event delegation lives in the kernel: one native listener per event type walks the ancestor chain natively and calls into Python once per matched handler with a JSON payload. See wybthon.events for the Python half.

Application code never imports this module directly; it's plumbing for the reconciler, wybthon.props, and wybthon.events.

Classes:

Name Description
BrowserBackend

Backend that drives the real DOM through the embedded JS kernel.

PythonBackend

Reference op interpreter over a DOM-like stub document.

Functions:

Name Description
commit

Flush all queued ops to the backend in one crossing.

set_backend

Install a rendering backend (tests pass a PythonBackend).

reset

Test helper: clear the op buffer, id counters, and template registry.

BrowserBackend

BrowserBackend()

Backend that drives the real DOM through the embedded JS kernel.

Created automatically on first use inside Pyodide. Every apply serializes the op list to JSON and makes exactly one call across the bridge.

Methods:

Name Description
apply

Serialize ops to JSON and apply them in one kernel call.

get_node

Return the raw DOM node registered under node_id.

adopt

Register an existing raw DOM node under node_id.

query

Register the first selector match under node_id; False when none.

supports_html

The browser can always parse template HTML.

set_dispatcher

Install the Python event dispatcher as the kernel's callback proxy.

current_event

Return the native event currently being dispatched, or None.

apply

apply(ops: List[Any]) -> None

Serialize ops to JSON and apply them in one kernel call.

get_node

get_node(node_id: int) -> Any

Return the raw DOM node registered under node_id.

adopt

adopt(node_id: int, node: Any) -> None

Register an existing raw DOM node under node_id.

query

query(node_id: int, selector: str) -> bool

Register the first selector match under node_id; False when none.

supports_html

supports_html() -> bool

The browser can always parse template HTML.

set_dispatcher

set_dispatcher(fn: Callable[[int, str, str], int]) -> None

Install the Python event dispatcher as the kernel's callback proxy.

current_event

current_event() -> Any

Return the native event currently being dispatched, or None.

PythonBackend

PythonBackend(document: Any)

Reference op interpreter over a DOM-like stub document.

Mirrors the JS kernel's semantics against plain Python objects that quack like DOM nodes (the test suite's StubNode and the benchmark runner's _Node). Unit tests and the stubbed benchmark run the real wire protocol through this class, so protocol bugs surface without a browser.

Methods:

Name Description
apply

Interpret a batch of ops against the stub document.

get_node

Return the stub node registered under node_id, or None.

adopt

Register an existing stub node under node_id.

query

Register the document's first selector match; False when none.

supports_html

Whether the stub document parses <template> innerHTML.

set_dispatcher

Install the Python event dispatcher used by dispatch.

current_event

Return the raw event passed to the in-flight dispatch, or None.

dispatch

Simulate a bubbling native event dispatch for tests.

apply

apply(ops: List[Any]) -> None

Interpret a batch of ops against the stub document.

get_node

get_node(node_id: int) -> Any

Return the stub node registered under node_id, or None.

adopt

adopt(node_id: int, node: Any) -> None

Register an existing stub node under node_id.

query

query(node_id: int, selector: str) -> bool

Register the document's first selector match; False when none.

supports_html

supports_html() -> bool

Whether the stub document parses <template> innerHTML.

set_dispatcher

set_dispatcher(fn: Callable[[int, str, str], int]) -> None

Install the Python event dispatcher used by dispatch.

current_event

current_event() -> Any

Return the raw event passed to the in-flight dispatch, or None.

dispatch

dispatch(event_type: str, target: Any, raw_event: Any = None, payload: Optional[dict] = None) -> None

Simulate a bubbling native event dispatch for tests.

Walks the stub-node ancestor chain from target, invoking the Python dispatcher for every registered (node, event_type) handler, exactly like the JS kernel's root listener.

alloc_id

alloc_id() -> int

Allocate one fresh node id.

alloc_ids

alloc_ids(count: int) -> int

Allocate a dense block of count ids; returns the first id.

template_id

template_id(html: str) -> int

Return the template id for html, registering it on first use.

The registration op travels in the same batch as the clone that needs it, so no extra bridge crossing occurs.

emit

emit(op: Any) -> None

Queue one op tuple for the next commit.

commit

commit() -> None

Flush all queued ops to the backend in one crossing.

No-op when the buffer is empty. Safe to call at any time; the renderer calls it at the end of render, at the end of every effect flush, and before any synchronous DOM read.

get_node

get_node(node_id: int) -> Any

Return the raw DOM node for node_id, committing pending ops first.

adopt

adopt(node: Any) -> int

Register an existing raw DOM node and return its new id.

query

query(selector: str) -> Optional[int]

Resolve a CSS selector to a node id, or None when nothing matches.

Commits pending ops first so the selector can match nodes created earlier in the same logical update.

supports_html

supports_html() -> bool

Return whether the backend can parse templates (OP_REGISTER_TPL).

current_event

current_event() -> Any

Return the native event being dispatched right now, or None.

Only valid synchronously inside an event handler; used as the escape hatch behind DomEvent.raw.

set_event_dispatcher

set_event_dispatcher(fn: Callable[[int, str, str], int]) -> None

Install the Python-side event dispatcher (called by wybthon.events).

set_backend

set_backend(backend: Any) -> None

Install a rendering backend (tests pass a PythonBackend).

Clears the template registry: a fresh backend has no registered skeletons, so they must be re-sent on next use.

reset

reset(backend: Optional[Any] = None) -> None

Test helper: clear the op buffer, id counters, and template registry.

What's in this module

kernel is the single point of contact between Wybthon's renderer and the real DOM. The reconciler, prop appliers, and event system never call DOM APIs; they emit compact operations (JSON-serializable tuples against integer node ids) into a buffer that commit() flushes to the active backend in one Python-to-JS bridge crossing.

Two backends implement the protocol:

  • BrowserBackend drives the real DOM through a small JavaScript kernel evaluated once in the page. The kernel owns the id-to-node registry, the registered template protos (parsed once, cloned per mount), and native event delegation.
  • PythonBackend is a reference interpreter that applies the same ops to any DOM-like stub document. The unit tests and the stubbed benchmark run the full wire protocol through it, so protocol bugs surface without a browser.

The op protocol

Op Payload Effect
CREATE_ELEMENT id, tag document.createElement
CREATE_TEXT id, text document.createTextNode
CREATE_COMMENT id document.createComment
REGISTER_TPL tpl_id, html Parse a skeleton once via <template>
CLONE_TPL first_id, count, tpl_id Clone the proto; assign a dense id block in pre-order
INSERT parent_id, id, anchor_id insertBefore (None anchor appends)
REMOVE id Detach from parent
SET_TEXT id, text nodeValue assignment
SET_ATTR id, name, value setAttribute / removeAttribute (None removes)
SET_PROP id, name, value DOM property assignment (value, checked)
SET_STYLE id, decls style.setProperty / removeProperty per declaration
LISTEN / UNLISTEN id, type Delegated handler bookkeeping plus root-listener refcounts
RELEASE [ids] Drop registry entries and listener sets for a retired subtree

Application code never imports this module directly; it's plumbing for the reconciler, wybthon.props, and wybthon.events.

See also