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 for the matching bubbling route 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

Return an existing handle or register the node under node_id.

query

Return the canonical handle of a selector match, or 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) -> int

Return an existing handle or register the node under node_id.

query

query(node_id: int, selector: str) -> int | None

Return the canonical handle of a selector match, or 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

Return an existing handle or register this stub node.

query

Return the canonical handle of a selector match, or 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.

roots

Return the registered delegation roots (test helper).

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) -> int

Return an existing handle or register this stub node.

query

query(node_id: int, selector: str) -> int | None

Return the canonical handle of a selector match, or 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.

roots

roots() -> list[Any]

Return the registered delegation roots (test helper).

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.

stats

stats() -> dict[str, int]

Return node, template, listener, and root registry sizes.

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 the renderer and the real DOM. The reconciler, prop appliers, and event system emit compact ops (JSON-serializable tuples against integer node ids) into a buffer; commit() hands the whole buffer to the active backend in one Python-to-JS bridge crossing. Application code never imports this module; it matters when you're writing tests against the stub backend or debugging the wire protocol.

Name Description
commit Flush every queued op to the backend in one crossing (no-op when empty).
BrowserBackend Drives the real DOM through the embedded JS kernel; created automatically in Pyodide.
PythonBackend Reference interpreter applying the same ops to a DOM-like stub document (used by the unit tests and the stubbed benchmark).
set_backend Install a backend (tests pass a PythonBackend).
reset Test helper: clear the op buffer, id counters, and template registry, optionally installing a backend.

Wire protocol

Each op is a JSON array whose first element is the opcode. Node ids are allocated on the Python side, so no JsProxy objects flow through the hot path; None anchors mean "append".

Op Payload Effect
CREATE_ELEMENT id, tag document.createElement
CREATE_ELEMENT_NS id, namespace, tag document.createElementNS (SVG, MathML)
CREATE_TEXT id, text document.createTextNode
CREATE_COMMENT id Empty comment marker (fragment and hole anchors)
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 the parent
MOVE_RANGE parent_id, first_id, last_id, anchor_id Move a contiguous mounted range
REMOVE_RANGE first_id, last_id Detach a contiguous mounted range
HOLE_TEXT id, text Reuse a hole anchor as a visible text node
RELEASE_TPL tpl_id Evict a native template prototype
SET_TEXT id, text nodeValue assignment
SET_ATTR id, name, value setAttribute, or removeAttribute when value is None
SET_PROP id, name, value DOM property assignment (value, checked, selectedValues, innerHTML)
SET_STYLE id, decls style.setProperty / removeProperty per kebab-case declaration
LISTEN / UNLISTEN id, event_type[, options] Delegated bookkeeping or direct native options; unlisten omits options
RELEASE [ids] Drop registry entries and listener sets for a retired subtree
ROOT / UNROOT id Start or stop delegating events from this container instead of document

Events travel the other way: the JS kernel installs one native listener per event type on each render root, walks the ancestor chain natively, and calls the Python dispatcher once for the matching bubbling route with a small JSON payload (see events).

from wybthon import kernel

# In a CPython test, install the stub backend over any DOM-like document
# (the test suite's conftest does this for you).
kernel.reset(kernel.PythonBackend(stub_document))

See also