Nob ~/notas/un-navegador-dentro

~/notas

A browser that doesn't open windows

How I built pane to open real pages inside Minecraft: Chromium without a window, pixels drawn by the game and the native details that don't tell you anything

published Oct 9, 20264 min readNob

abstract

question
Can a real web page (Ko-fi, with payments) open inside Minecraft, on the new versions, without depending on MCEF?
method
Write my own library on top of java-cef in off-screen mode: Chromium paints into a buffer, the game uploads it to a texture, and mouse and keyboard go in as GLFW codes
result
pane v0.1.1 (MIT, on JitPack). Tested on all three Minecraft versions jukz supports with Ko-fi, text fields, scrolling, links and back
limits
Chromium weighs about 120 MB and is downloaded the first time. The builds are CinemaMod's, copied unchanged; I don't build Chromium

I wanted jukz's Ko-fi button to open the page inside the game, not in the system browser. What existed for that is MCEF, and the prototype worked with it on Minecraft 1.21.1. But MCEF stops at 1.21.4 (jukz supports 1.21.11 and 26.2) and it's LGPL. So I wrote my own library: pane

The idea: Chromium without a window

A normal browser draws into its own window. In a game that doesn't work: the page has to be one more texture, drawn wherever the game wants. CEF (the Chromium Embedded Framework) has a mode for this, off-screen: Chromium opens nothing, it just paints the page into a memory buffer and says when it changed

how it works
Pane.open("https://…")
 │
 ▼
Chromium off-screen (java-cef)
 │ onPaint(dirty rectangles, BGRA buffer)
 ▼
pane copies only those rectangles ──► version++
 │
 ▼
the game: if the version changed ──► upload the texture

pane copies into its own buffer only the rectangles Chromium says changed, and bumps a version number. That way the game doesn't have to compare pixels: every frame it checks whether the version is different, and only then uploads the texture again

terminal
Pane.install(progress);          // the first time: downloads Chromium
Pane.start();
PaneView page = Pane.open("https://ko-fi.com/…");

// every frame
Pane.pump();
if (page.version() != last) upload(page.pixels());   // BGRA

Dropdown menus (a <select>) come separately, as a second buffer with its position, so they can be drawn on top of the page

Input, in the game's language

Minecraft and most Java games read the keyboard and mouse through GLFW. CinemaMod's fork of java-cef already understands those codes, so pane takes them as they are: mouseDown(x, y, button, mods), typed(c, mods), wheel(x, y, notches, mods). pane fills in what's missing: counting double and triple clicks, keeping buttons held while dragging, and the usual shortcuts (Ctrl+R reloads, Alt+← and → navigate, Ctrl with + and - zooms)

And the cursor: if you hand it the GLFW window, pane turns it into a hand over links and a text bar over fields, like any browser

What the native side doesn't tell you

The hardest part wasn't the big pieces, but three details on java-cef's native side that fail silently:

  • New windows vanish. In off-screen mode the native side cancels every new window before telling Java, so a link with target="_blank" just did nothing. pane injects a script that replaces window.open and opens those links in the same page
  • The buttons are swapped. GLFW numbers left 0, right 1 and middle 2; the native side reads 1 as middle and 2 as right. pane swaps them before sending
  • Typed text goes a different way. A typed letter isn't the same as a key going down: the native side expects characters marked as «repeat», and otherwise the text field gets nothing
terminal
window.open = (url) => { if (url) location.href = new URL(url, location.href).href; return window; };

I also chose not to turn off the same-origin policy, which MCEF turns off. It's the rule that keeps one page from reading another's data, and people pay on these pages

Its own installer

Chromium doesn't fit in a mod jar. The first time, pane downloads the build for your system (Linux, Windows or Mac, x64 or ARM), checks it against its SHA-256 and unpacks it with its own tar.gz reader, no dependencies. It keeps the hash, and later runs don't even download

At first the files came from someone else's server. I copied all six to my own R2, at chromium.nuulm.com, and copying them uncovered a bug: the Windows .sha256 doesn't come as hash name, but as PowerShell's table, and pane couldn't read it. Now it looks for the first run of 64 hex characters, whatever the format. That shipped as v0.1.1

ideaIn a library that wraps native code, the hard part isn't the API but what the native side does without telling you. Every one of those details was found by testing by hand inside the game, not by reading the docs

Limits

  • The first run has to download about 120 MB
  • The Chromium builds are CinemaMod's, copied unchanged: I don't build Chromium, and updates depend on them
  • It needs Java 21 and LWJGL on the classpath, because the native side reads GLFW codes

Frequently asked questions

How do you open a web page inside Minecraft?

With a browser in off-screen mode: Chromium paints the page into a pixel buffer and the mod draws it as a texture. pane does that on top of java-cef, without opening windows

What is CEF's off-screen rendering?

A mode where Chromium has no window: it paints the page in memory and reports which rectangles changed, so the app can draw it wherever it wants

Why not use MCEF?

It stops at Minecraft 1.21.4 and it's LGPL. pane is its own code under MIT and works on 1.21.11 and 26.2

Why does a link with target="_blank" do nothing in off-screen java-cef?

The native side cancels new windows before telling Java. The way out is opening those links in the same page, replacing window.open with a script

References

  1. Chromium Embedded Framework (CEF) · off-screen rendering · bitbucket.org/chromiumembedded/cef
  2. java-cef · Java binding for CEF · github.com/chromiumembedded/java-cef
  3. CinemaMod/java-cef · fork that reads GLFW input · github.com/CinemaMod/java-cef
  4. GLFW · input reference (keys, mouse buttons, modifiers) · glfw.org/docs/latest/group__input.html
  5. Nuulz/pane · source code and v0.1.1 · github.com/Nuulz/pane

Anything to fix or add? Write to me →