byteface/chromonic

A python first browser that sits on domonic

1

stars

14

commits

Python

primary language

Sep 14, 2026

updated

README

chromonic

Experimental. Chromonic is a standalone project proving that Domonic can be the DOM/CSSOM behind a native rendering pipeline:

Chromonic rendering preview

Build & run

cd chromonic
make venv
make develop                         # compiles the Rust extension and installs chromonic editable
python examples/poc.py     # -> examples/poc.png, examples/poc_mutated.png
make test

Needs a Rust toolchain (cargo/rustc) on PATH; skia-python installs from a prebuilt wheel (no C++ build needed on macOS/Linux/Windows x86_64 or macOS arm64).

Public API

Chromonic exposes a small layered surface. The engine functions are useful for headless rendering and tests; App is the native desktop application wrapper; Browser opens a URL in the native browser shell.

from domonic.html import body, h1
import chromonic

root = body(h1("Headless render"))
chromonic.layout(root)
png = chromonic.render(root)
from chromonic import App
from domonic.html import body, button, h1, p

root = body(
    h1("Counter"),
    button("Increment", _id="inc"),
    p("0", _id="value"),
)

app = App(root, width=700, height=500)

@app.click("#inc")
def increment(event):
    app.document.querySelector("#value").textContent = "1"

app.run()

App keeps the Domonic DOM as the application state. Event handlers can use normal Domonic APIs such as querySelector, appendChild, remove, textContent, value, checked, and addEventListener. The convenience decorators delegate through the document, so they also match elements created after startup.

@app.click(".delete")
def delete_task(event):
    event.currentTarget.parentNode.remove()

@app.key("#new-task", "Enter")
def add_with_enter(event):
    app.trigger("#add", "click")

The public attributes are:

app.document  # Domonic Document
app.window    # Domonic defaultView

The native GLFW window and Skia renderer stay internal for now. See examples/counter_app.py and examples/todo_app.py for small apps that only import Domonic HTML tags and chromonic.App.

from chromonic import Browser

browser = Browser("https://eventual.technology")
browser.run()

Direct GPU browser (browse2.py)

python examples/browse2.py https://example.com/
python -m pytest tests
python chromonic/benchmarks/smoke_native.py
python chromonic/examples/browse2.py https://example.com/ --frames 2

Web fonts

The browser loads @font-face URL sources in the background, resolving paths relative to their stylesheet (or the page for inline CSS). OTF, TTF, WOFF and WOFF2 are decoded into the same font bytes for Parley/fontique layout and Skia painting. Arrival triggers relayout and repaint; fonts are never installed into the OS. Family, numeric weight and normal/italic/oblique style select a document-private face before system fonts and generic fallbacks.

This first implementation supports static faces in top-level @font-face rules. local() sources, @import, conditional font-face rules, variable-font descriptor ranges, unicode-range and font-display policies are not yet implemented. Failed downloads retain fallback text; diagnostic errors are available on page.document._chromonic_webfonts.errors.

Comparing a live page with Chrome

For real sites, first capture what Chrome actually rendered. Open the page in Chrome, then paste tools/chrome_probe.js into DevTools Console. It downloads a JSON file containing loaded stylesheets, accessible CSS rules, resource timing, web font status, image natural sizes, visible element rectangles, and focused computed styles.

That dump is the quickest way to answer whether Chromonic missed an external stylesheet, missed a background image/font resource, or parsed the CSS but resolved a different computed value. For fixture-sized cases, use the permanent Chrome-vs-Chromonic harness below.

Chrome layout conformance

The permanent numeric correctness harness runs the focused fixtures in tests/layout/fixtures through both installed headless Chrome and chromonic:

make layout-conformance

It compares every data-layout element's getBoundingClientRect() geometry with a 0.5 CSS-pixel tolerance and reports exact Chrome, chromonic, and delta values. Each fixture also writes focused computed styles plus chrome.png, ours.png, and an amplified diff.png. Geometry controls the exit status; screenshots and style serialization remain diagnostic. See tests/layout/README.md for fixture conventions, individual commands, and the current conformance baseline.

Chromonic depends on Domonic 1.8.1 or newer and uses the released DOM/CSSOM implementation directly.

Executable Python inside HTML

There's no limit on what a <script type="text/python"> can do — it runs with the same power as a normal Python exec() (no import allowlist, no resource limits). Treat a .py-in-HTML page exactly like trusted application code you'd run yourself (python app.py) — never point this at arbitrary, remote, or user-supplied HTML/Python; there is no isolation here to protect against it.

<script type="text/python">
button = document.querySelector("#hello")

def clicked(event):
    button.textContent = "Clicked"

button.addEventListener("click", clicked)
</script>
python examples/pyscript_demo.py        # the inline form above
python examples/pyscript_src_demo.py    # the src="app.py" form

Contributors

byteface

14 commits

byteface/chromonic

A python first browser that sits on domonic

1

stars

14

commits

Python

primary language

Sep 14, 2026

updated

README

chromonic

Experimental. Chromonic is a standalone project proving that Domonic can be the DOM/CSSOM behind a native rendering pipeline:

Chromonic rendering preview

Build & run

cd chromonic
make venv
make develop                         # compiles the Rust extension and installs chromonic editable
python examples/poc.py     # -> examples/poc.png, examples/poc_mutated.png
make test

Needs a Rust toolchain (cargo/rustc) on PATH; skia-python installs from a prebuilt wheel (no C++ build needed on macOS/Linux/Windows x86_64 or macOS arm64).

Public API

Chromonic exposes a small layered surface. The engine functions are useful for headless rendering and tests; App is the native desktop application wrapper; Browser opens a URL in the native browser shell.

from domonic.html import body, h1
import chromonic

root = body(h1("Headless render"))
chromonic.layout(root)
png = chromonic.render(root)
from chromonic import App
from domonic.html import body, button, h1, p

root = body(
    h1("Counter"),
    button("Increment", _id="inc"),
    p("0", _id="value"),
)

app = App(root, width=700, height=500)

@app.click("#inc")
def increment(event):
    app.document.querySelector("#value").textContent = "1"

app.run()

App keeps the Domonic DOM as the application state. Event handlers can use normal Domonic APIs such as querySelector, appendChild, remove, textContent, value, checked, and addEventListener. The convenience decorators delegate through the document, so they also match elements created after startup.

@app.click(".delete")
def delete_task(event):
    event.currentTarget.parentNode.remove()

@app.key("#new-task", "Enter")
def add_with_enter(event):
    app.trigger("#add", "click")

The public attributes are:

app.document  # Domonic Document
app.window    # Domonic defaultView

The native GLFW window and Skia renderer stay internal for now. See examples/counter_app.py and examples/todo_app.py for small apps that only import Domonic HTML tags and chromonic.App.

from chromonic import Browser

browser = Browser("https://eventual.technology")
browser.run()

Direct GPU browser (browse2.py)

python examples/browse2.py https://example.com/
python -m pytest tests
python chromonic/benchmarks/smoke_native.py
python chromonic/examples/browse2.py https://example.com/ --frames 2

Web fonts

The browser loads @font-face URL sources in the background, resolving paths relative to their stylesheet (or the page for inline CSS). OTF, TTF, WOFF and WOFF2 are decoded into the same font bytes for Parley/fontique layout and Skia painting. Arrival triggers relayout and repaint; fonts are never installed into the OS. Family, numeric weight and normal/italic/oblique style select a document-private face before system fonts and generic fallbacks.

This first implementation supports static faces in top-level @font-face rules. local() sources, @import, conditional font-face rules, variable-font descriptor ranges, unicode-range and font-display policies are not yet implemented. Failed downloads retain fallback text; diagnostic errors are available on page.document._chromonic_webfonts.errors.

Comparing a live page with Chrome

For real sites, first capture what Chrome actually rendered. Open the page in Chrome, then paste tools/chrome_probe.js into DevTools Console. It downloads a JSON file containing loaded stylesheets, accessible CSS rules, resource timing, web font status, image natural sizes, visible element rectangles, and focused computed styles.

That dump is the quickest way to answer whether Chromonic missed an external stylesheet, missed a background image/font resource, or parsed the CSS but resolved a different computed value. For fixture-sized cases, use the permanent Chrome-vs-Chromonic harness below.

Chrome layout conformance

The permanent numeric correctness harness runs the focused fixtures in tests/layout/fixtures through both installed headless Chrome and chromonic:

make layout-conformance

It compares every data-layout element's getBoundingClientRect() geometry with a 0.5 CSS-pixel tolerance and reports exact Chrome, chromonic, and delta values. Each fixture also writes focused computed styles plus chrome.png, ours.png, and an amplified diff.png. Geometry controls the exit status; screenshots and style serialization remain diagnostic. See tests/layout/README.md for fixture conventions, individual commands, and the current conformance baseline.

Chromonic depends on Domonic 1.8.1 or newer and uses the released DOM/CSSOM implementation directly.

Executable Python inside HTML

There's no limit on what a <script type="text/python"> can do — it runs with the same power as a normal Python exec() (no import allowlist, no resource limits). Treat a .py-in-HTML page exactly like trusted application code you'd run yourself (python app.py) — never point this at arbitrary, remote, or user-supplied HTML/Python; there is no isolation here to protect against it.

<script type="text/python">
button = document.querySelector("#hello")

def clicked(event):
    button.textContent = "Clicked"

button.addEventListener("click", clicked)
</script>
python examples/pyscript_demo.py        # the inline form above
python examples/pyscript_src_demo.py    # the src="app.py" form

Contributors

byteface

14 commits

Languages

Python

88.9%

Rust

6.1%

HTML

3.8%

JavaScript

1.1%