Nob ~/notas/un-navegador-dentro

~/notas

Un navegador que no abre ventanas

Cómo hice pane para abrir páginas de verdad dentro de Minecraft: Chromium sin ventana, píxeles que dibuja el juego y los detalles del nativo que no avisan

publicada 9 oct 20264 min de lecturaNob

resumen

pregunta
¿Se puede abrir una página web de verdad (Ko-fi, con pagos) dentro de Minecraft, en las versiones nuevas, sin depender de MCEF?
método
Escribir una librería propia sobre java-cef en modo off-screen: Chromium pinta en un búfer, el juego lo sube a una textura, y el mouse y el teclado entran con códigos de GLFW
resultado
pane v0.1.1 (MIT, en JitPack). Probado en las tres versiones de Minecraft que soporta jukz con Ko-fi, campos de texto, scroll, enlaces y atrás
límites
Chromium pesa unos 120 MB y se descarga la primera vez. Los builds son los de CinemaMod, copiados sin cambios; no compilo Chromium

Quería que el botón de Ko-fi de jukz abriera la página dentro del juego, no en el navegador del sistema. Lo que existía para eso es MCEF, y el prototipo funcionó con él en Minecraft 1.21.1. Pero MCEF llega solo hasta la 1.21.4 (jukz soporta 1.21.11 y 26.2) y es LGPL. Así que escribí mi propia librería: pane

La idea: Chromium sin ventana

Un navegador normal dibuja en su propia ventana. En un juego eso no sirve: la página tiene que ser una textura más, dibujada donde el juego quiera. CEF (Chromium Embedded Framework) tiene un modo para esto, off-screen: Chromium no abre nada, solo pinta la página en un búfer de memoria y avisa cuando cambió

cómo funciona
Pane.open("https://…")
 │
 ▼
Chromium off-screen (java-cef)
 │ onPaint(rectángulos sucios, búfer BGRA)
 ▼
pane copia solo esos rectángulos ──► version++
 │
 ▼
el juego: si cambió la versión ──► sube la textura

pane copia a su propio búfer solo los rectángulos que Chromium dice que cambiaron, y sube un número de versión. Así el juego no tiene que comparar píxeles: cada cuadro mira si la versión es otra, y solo entonces vuelve a subir la textura

terminal
Pane.install(progreso);          // la primera vez: descarga Chromium
Pane.start();
PaneView page = Pane.open("https://ko-fi.com/…");

// cada cuadro
Pane.pump();
if (page.version() != ultima) subir(page.pixels());   // BGRA

Los menús desplegables (un <select>) llegan aparte, como un segundo búfer con su posición, para dibujarlos encima de la página

La entrada, en el idioma del juego

Minecraft y casi cualquier juego en Java leen el teclado y el mouse con GLFW. El fork de java-cef de CinemaMod ya entiende esos códigos, así que pane los recibe tal cual: mouseDown(x, y, botón, mods), typed(c, mods), wheel(x, y, muescas, mods). Lo que falta lo pone pane: contar doble y triple clic, mantener los botones apretados al arrastrar, y los atajos de siempre (Ctrl+R recarga, Alt+← y → navegan, Ctrl con + y - hace zoom)

Y el cursor: si le das la ventana de GLFW, pane la cambia a una mano sobre los enlaces y a una barra sobre los campos de texto, como en cualquier navegador

Lo que el nativo no avisa

Lo más difícil no fue lo grande, sino tres detalles del lado nativo de java-cef que fallan en silencio:

  • Las ventanas nuevas desaparecen. En modo off-screen el nativo cancela cualquier ventana nueva antes de avisarle a Java, así que un enlace con target="_blank" simplemente no hacía nada. pane inyecta un script que reemplaza window.open y abre esos enlaces en la misma página
  • Los botones están cruzados. GLFW numera izquierdo 0, derecho 1 y medio 2; el nativo lee el 1 como el medio y el 2 como el derecho. pane cambia uno por otro antes de mandarlos
  • El texto escrito va por otro lado. Una letra que se escribe no es lo mismo que una tecla que baja: el nativo espera los caracteres marcados como «repeat», y si no, el campo de texto no recibe nada
terminal
window.open = (url) => { if (url) location.href = new URL(url, location.href).href; return window; };

También decidí no apagar la política de mismo origen, que MCEF apaga. Es la regla que impide que una página lea lo de otra, y en estas páginas la gente paga

Un instalador propio

Chromium no cabe en un jar de mod. La primera vez, pane descarga el build de tu sistema (Linux, Windows o Mac, x64 o ARM), lo compara con su SHA-256 y lo descomprime con un lector de tar.gz propio, sin dependencias. Guarda el hash, y las siguientes veces ni descarga

Al principio los archivos venían del servidor de otro. Los copié los seis a mi propio R2, en chromium.nuulm.com, y copiarlos destapó un bug: el .sha256 de Windows no viene como hash nombre, sino como la tabla de PowerShell, y pane no lo leía. Ahora busca el primer grupo de 64 caracteres hexadecimales, venga en el formato que venga. Eso salió como la v0.1.1

ideaEn una librería que envuelve código nativo, lo que cuesta no es la API sino lo que el nativo hace sin avisar. Cada uno de esos detalles se encontró probando a mano dentro del juego, no leyendo la documentación

Límites

  • La primera vez hay que descargar unos 120 MB
  • Los builds de Chromium son los de CinemaMod, copiados sin cambios: no compilo Chromium, y las actualizaciones dependen de ellos
  • Necesita Java 21 y LWJGL en el classpath, porque el nativo lee los códigos de GLFW

Preguntas frecuentes

¿Cómo abrir una página web dentro de Minecraft?

Con un navegador en modo off-screen: Chromium pinta la página en un búfer de píxeles y el mod la dibuja como una textura. pane hace eso sobre java-cef, sin abrir ventanas

¿Qué es el renderizado off-screen de CEF?

Un modo en el que Chromium no tiene ventana: pinta la página en memoria y avisa qué rectángulos cambiaron, para que la app la dibuje donde quiera

¿Por qué no usar MCEF?

Llega solo hasta Minecraft 1.21.4 y es LGPL. pane es código propio con licencia MIT y funciona en 1.21.11 y 26.2

¿Por qué un enlace con target="_blank" no hace nada en java-cef off-screen?

El lado nativo cancela las ventanas nuevas antes de avisarle a Java. La salida es abrir esos enlaces en la misma página, reemplazando window.open con un script

Referencias

  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

¿Algo para corregir o agregar? Escríbeme →