El README de la receta afirmaba (a) que las extensiones las instala la POLÍTICA porque el sideloading desde `distribution/extensions/` «ya no funciona», y (b) que el chrome de verdad —split view— exige entrar a `omni.ja` y escribirlo nosotros. Las dos son falsas, y ninguna observación del artefacto las distinguía: hubo que romper un mecanismo por vez. scripts/test-atuq-instalacion.py — tres escenarios: as-is nada roto ⇒ las dos extensiones puestas no-policy `policies.json` sin ExtensionSettings ⇒ SIGUEN puestas policy-only los XPI fuera de la carpeta ⇒ NINGUNA, y la home vuelve a about:home ⇒ instala el ESCANEO de la carpeta; `install_url` con `file://` no instala nada. El §7.bis del SDD 26 tenía razón. El control es por construcción: la sonda vive en la carpeta y ninguna política la nombra, así que una corrida muda se declara ROTA en vez de leerse como «no instaló». Y la segunda señal que probé NO discrimina, queda dicho: el `location` de `extensions.json` sale `app-profile` también cuando instala la carpeta. scripts/test-atuq-chrome.py — el chrome se programa desde `atuq.cfg`, sin abrir el zip: observando `browser-delayed-startup-finished` se toca el `gBrowser` de cada ventana. Y la vista dividida ya la trae el motor (fx 154) PRENDIDA de fábrica, ejercitada de verdad —`addTabSplitView` ⇒ `activeSplitView` + 2 navegadores—, no «el fichero está». Dos pendientes que eran de upstream. Control negativo del propio motor: con las pestañas fijadas devuelve null (WRAPPER null, ACTIVA no). Corolario para lo que venga: cada función del chrome que NO entre a `omni.ja` es una que no pelea con el orden del `jarlog` del PGO — que es justo lo que tiene al jarlog aparcado. Queda escrito lo que NO está: ninguno de los `test-atuq-*` corre en el latido (sólo lo hace `vigia-sonames.py`), y meterlos cuesta ~3 min por ciclo más el rootfs hidratado en el hub. Medido sobre atuq b3:fab2fbfb → b3:8f6d09c2 (el README entra en el hash: documentar la medición re-hashea el artefacto medido, y los guardianes se niegan a medir uno que no sea el vigente).
233 lines
11 KiB
Python
Executable File
233 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""¿Corre JS NUESTRO en el chrome de `atuq`, y funciona la vista dividida? (SDD 26 §4)
|
|
|
|
python3 scripts/test-atuq-chrome.py # exige las dos cosas
|
|
python3 scripts/test-atuq-chrome.py --negative-control # con las pestañas FIJADAS: no debe partir
|
|
|
|
── LAS DOS PREGUNTAS, Y POR QUÉ VAN JUNTAS ───────────────────────────────────────────────────
|
|
El README de atuq decía que «el chrome de verdad» —split view, dientes propios— exige entrar a
|
|
`omni.ja` y poner contenido nuestro adentro. Las dos mitades de esa frase resultaron falsas, y esta
|
|
sonda mide cada una:
|
|
|
|
1. **El gancho.** `atuq.cfg` ya corre con privilegios (de ahí sale `nsIStyleSheetService`), así que
|
|
desde ahí se puede observar `browser-delayed-startup-finished` y tocar el `gBrowser` de CADA
|
|
ventana. O sea: **el chrome se programa sin abrir el zip**, que además es lo que hay que hacer,
|
|
porque re-empacar `omni.ja` pelea con el orden del `jarlog` del PGO (por eso `jarlog` está
|
|
aparcado).
|
|
2. **La vista dividida.** Firefox 154 la trae HECHA y PRENDIDA de fábrica:
|
|
`browser/omni.ja` lleva `tabbrowser/tabsplitview.js` (492 líneas, el custom element),
|
|
`opentabs-splitview.mjs` y `split-view-footer.js`, y `defaults/preferences/firefox.js` dice
|
|
`pref("browser.tabs.splitView.enabled", true)`. Eso no es «lo que falta escribir»: es una
|
|
función que ya tenemos y que nadie había ejercitado.
|
|
|
|
Lo que la sonda hace en la ventana real: abre dos pestañas, llama a `gBrowser.addTabSplitView`, y
|
|
después pregunta al motor por `gBrowser.activeSplitView` y `gBrowser.splitViewBrowsers`. Son hechos
|
|
del DOM del chrome, no píxeles — corre headless.
|
|
|
|
── EL CONTROL NEGATIVO SALE DEL PROPIO MOTOR ─────────────────────────────────────────────────
|
|
`addTabSplitView` tiene un `bail out` documentado: si TODAS las pestañas están fijadas, borra el
|
|
wrapper y devuelve `null` (no se pueden agrupar). `--negative-control` fija las dos pestañas y exige
|
|
justamente eso. Sin él, una sonda que dijera «partió» pase lo que pase se vería idéntica a una que
|
|
funciona.
|
|
|
|
REQUISITOS: el rootfs de `scripts/atuq-nested.sh` (que lo hidrata) y `atuq` sellado.
|
|
"""
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
RFS = os.environ.get("RFS", "/mnt/cosecha/escritorios/atuq-rootfs")
|
|
ALPINE = os.path.join(ROOT, ".dev-fs/alpine")
|
|
HAMMER = os.path.join(ROOT, "target/release/takana")
|
|
STORE = os.environ.get("HAMMER_STORE", os.path.join(ROOT, "store"))
|
|
APPDIR = "usr/lib/atuq"
|
|
|
|
# El bloque se APENDA al `atuq.cfg` del artefacto (no lo reemplaza): así se mide el fichero real de
|
|
# atuq con la sonda encima, y no una copia que podría haber divergido. La capa de overlay pisa el
|
|
# fichero entero, por eso hay que reescribirlo completo con el original adentro.
|
|
SONDA = """
|
|
// ── SONDA DE PRUEBA — NO VIAJA EN EL ARTEFACTO (scripts/test-atuq-chrome.py) ───────────────────
|
|
try {
|
|
const S = (typeof Services !== "undefined")
|
|
? Services
|
|
: ChromeUtils.importESModule("resource://gre/modules/Services.sys.mjs").Services;
|
|
const lineas = [];
|
|
const di = function (s) {
|
|
lineas.push(s);
|
|
try { dump("CHROME-TEST " + s + "\\n"); } catch (e) {}
|
|
// El fichero es la vía FIABLE: `dump()` desde el proceso padre depende de prefs y de cómo se
|
|
// compiló. Se usan las dos y el guión lee la que haya.
|
|
try { IOUtils.writeUTF8("/salida/chrome.txt", lineas.join("\\n") + "\\n"); } catch (e) {}
|
|
};
|
|
di("VIVO el JS de atuq.cfg corre");
|
|
S.obs.addObserver(function observador(win) {
|
|
S.obs.removeObserver(observador, "browser-delayed-startup-finished");
|
|
try {
|
|
di("VENTANA " + (win.gBrowser ? "con gBrowser" : "SIN gBrowser"));
|
|
di("PREF splitView.enabled=" +
|
|
S.prefs.getBoolPref("browser.tabs.splitView.enabled", false));
|
|
const principal = S.scriptSecurityManager.getSystemPrincipal();
|
|
const t1 = win.gBrowser.addTab("about:blank", { triggeringPrincipal: principal });
|
|
const t2 = win.gBrowser.addTab("about:blank", { triggeringPrincipal: principal });
|
|
if (FIJADAS) { win.gBrowser.pinTab(t1); win.gBrowser.pinTab(t2); }
|
|
const sv = win.gBrowser.addTabSplitView([t1, t2]);
|
|
di("WRAPPER " + (sv ? ("tabs=" + sv.tabs.length) : "null"));
|
|
if (sv && sv.tabs.length) { win.gBrowser.selectedTab = sv.tabs[0]; }
|
|
win.setTimeout(function () {
|
|
di("ACTIVA " + (win.gBrowser.activeSplitView ? "si" : "no"));
|
|
di("BROWSERS " + win.gBrowser.splitViewBrowsers.length);
|
|
di("FIN");
|
|
}, 2000);
|
|
} catch (e) {
|
|
di("EXCEPCIÓN " + e);
|
|
}
|
|
}, "browser-delayed-startup-finished");
|
|
} catch (e) {
|
|
try { dump("CHROME-TEST EXCEPCIÓN-EXTERIOR " + e + "\\n"); } catch (e2) {}
|
|
}
|
|
"""
|
|
|
|
|
|
def fatal(msg):
|
|
print(f"✗ {msg}", file=sys.stderr)
|
|
raise SystemExit(1)
|
|
|
|
|
|
def artefacto():
|
|
"""El artefacto VIGENTE de atuq, por `takana hash` y nunca por glob.
|
|
|
|
⚠ ESCOTILLA `ATUQ_DIR`, que grita: el corpus es compartido y otro frente puede re-hashear
|
|
`firefox` dejando el atuq vigente sin sellar durante las horas del build. Un resultado obtenido
|
|
con la escotilla NO se cita como «el atuq de hoy pasa».
|
|
"""
|
|
forzado = os.environ.get("ATUQ_DIR")
|
|
if forzado:
|
|
if not os.path.isdir(forzado):
|
|
fatal(f"ATUQ_DIR={forzado} no existe")
|
|
print(f"⚠ ATUQ_DIR: se mide {os.path.basename(forzado)}, que NO es necesariamente el vigente")
|
|
return forzado
|
|
r = subprocess.run([HAMMER, "--store", STORE, "hash", "recipes/atuq.toml"],
|
|
cwd=ROOT, capture_output=True, text=True)
|
|
if r.returncode != 0:
|
|
fatal(f"no pude hashear atuq: {r.stderr.strip()}")
|
|
h = r.stdout.strip().splitlines()[-1].removeprefix("b3:")
|
|
d = os.path.join(STORE, f"{h}-atuq")
|
|
if not os.path.isdir(d):
|
|
fatal(f"atuq b3:{h[:8]} no está sellado — construilo antes")
|
|
return d
|
|
|
|
|
|
def main():
|
|
negativo = "--negative-control" in sys.argv[1:]
|
|
if negativo:
|
|
print("== MODO CONTROL NEGATIVO: las dos pestañas van FIJADAS; el motor tiene que negarse")
|
|
if not os.path.isdir(RFS):
|
|
fatal(f"no está el rootfs en {RFS} — corré antes `scripts/atuq-nested.sh`, que lo hidrata")
|
|
atuq = artefacto()
|
|
print(f"artefacto: {os.path.basename(atuq)}")
|
|
|
|
tmp = tempfile.mkdtemp(prefix="atuq-chrome-")
|
|
salida = os.path.join(tmp, "salida")
|
|
capa_app = os.path.join(tmp, "capa", APPDIR)
|
|
os.makedirs(os.path.join(salida, "perfil"))
|
|
os.makedirs(capa_app)
|
|
try:
|
|
original = open(os.path.join(atuq, APPDIR, "atuq.cfg"), encoding="utf8").read()
|
|
with open(os.path.join(capa_app, "atuq.cfg"), "w", encoding="utf8") as fh:
|
|
fh.write(original)
|
|
fh.write(SONDA.replace("FIJADAS", "true" if negativo else "false"))
|
|
|
|
with open(os.path.join(salida, "perfil", "user.js"), "w") as fh:
|
|
fh.write('user_pref("browser.dom.window.dump.enabled", true);\n')
|
|
fh.write('user_pref("browser.shell.checkDefaultBrowser", false);\n')
|
|
|
|
guion = """
|
|
mkdir -p /root
|
|
[ -s /etc/machine-id ] || tr -d - < /proc/sys/kernel/random/uuid > /etc/machine-id
|
|
timeout 60 /usr/bin/atuq --profile /salida/perfil --no-remote --headless about:blank \
|
|
> /salida/consola.log 2>&1
|
|
exit 0
|
|
"""
|
|
subprocess.run([
|
|
"bwrap",
|
|
"--overlay-src", ALPINE, "--overlay-src", RFS, "--overlay-src", atuq,
|
|
"--overlay-src", os.path.join(tmp, "capa"), "--tmp-overlay", "/",
|
|
"--dev-bind", "/dev", "/dev", "--proc", "/proc", "--bind", "/sys", "/sys",
|
|
"--tmpfs", "/tmp", "--tmpfs", "/run", "--unshare-pid",
|
|
"--bind", salida, "/salida",
|
|
"--uid", "0", "--gid", "0",
|
|
"--setenv", "PATH", "/usr/bin:/bin:/usr/sbin:/sbin",
|
|
"--setenv", "XDG_RUNTIME_DIR", "/run", "--setenv", "HOME", "/root",
|
|
"--setenv", "XDG_DATA_DIRS", "/usr/share", "--setenv", "MOZ_HEADLESS", "1",
|
|
"--setenv", "MOZ_DISABLE_CONTENT_SANDBOX", "1",
|
|
"--setenv", "MOZ_DISABLE_GMP_SANDBOX", "1",
|
|
"--setenv", "MOZ_DISABLE_RDD_SANDBOX", "1",
|
|
"--setenv", "MOZ_DISABLE_SOCKET_PROCESS_SANDBOX", "1",
|
|
"--setenv", "MOZ_DISABLE_UTILITY_SANDBOX", "1",
|
|
"sh", "-c", guion,
|
|
], check=False)
|
|
|
|
log = open(os.path.join(salida, "consola.log"), errors="replace").read()
|
|
lineas = [l[len("CHROME-TEST "):] for l in log.splitlines() if l.startswith("CHROME-TEST ")]
|
|
fichero = os.path.join(salida, "chrome.txt")
|
|
if os.path.exists(fichero):
|
|
del_fichero = open(fichero, errors="replace").read().splitlines()
|
|
# El fichero es la fuente autorizada si dice MÁS que el stdout.
|
|
if len(del_fichero) > len(lineas):
|
|
lineas = del_fichero
|
|
for l in lineas:
|
|
print(" " + l)
|
|
|
|
def valor(clave):
|
|
for l in lineas:
|
|
if l.startswith(clave + " "):
|
|
return l.split(" ", 1)[1]
|
|
return None
|
|
|
|
if not lineas:
|
|
destino = os.path.join(ROOT, "work", "atuq-chrome.log")
|
|
os.makedirs(os.path.dirname(destino), exist_ok=True)
|
|
open(destino, "w").write(log)
|
|
fatal("la sonda no dijo NADA: el JS de `atuq.cfg` no corrió, o no llegó a la ventana."
|
|
f" Log en {destino}")
|
|
if valor("VIVO") is None:
|
|
fatal("no salió la línea VIVO — el bloque de la sonda no se ejecutó")
|
|
if "FIN" not in lineas and valor("EXCEPCIÓN"):
|
|
fatal(f"el JS del chrome se rompió: {valor('EXCEPCIÓN')}")
|
|
|
|
wrapper, activa, browsers = valor("WRAPPER"), valor("ACTIVA"), valor("BROWSERS")
|
|
if negativo:
|
|
if wrapper != "null":
|
|
fatal(f"con las dos pestañas FIJADAS el motor partió igual (WRAPPER={wrapper})"
|
|
" — esta prueba no distingue nada")
|
|
if activa != "no":
|
|
fatal(f"control negativo: ACTIVA={activa}, se esperaba «no»")
|
|
print("✓ control negativo: con las pestañas fijadas NO hay vista dividida"
|
|
" (la sonda sí lo vería)")
|
|
return 0
|
|
|
|
if valor("VENTANA") != "con gBrowser":
|
|
fatal("el observador corrió pero la ventana no traía `gBrowser`")
|
|
if valor("PREF") != "splitView.enabled=true":
|
|
fatal(f"la vista dividida viene APAGADA en este build ({valor('PREF')})"
|
|
" — habría que prenderla en atuq.cfg")
|
|
if wrapper != "tabs=2":
|
|
fatal(f"`addTabSplitView` no armó la vista de dos pestañas (WRAPPER={wrapper})")
|
|
if activa != "si":
|
|
fatal(f"la vista se creó pero el motor no la reporta ACTIVA (ACTIVA={activa})")
|
|
if browsers != "2":
|
|
fatal(f"el motor no reporta dos navegadores en la vista (BROWSERS={browsers})")
|
|
print("✓ el JS de `atuq.cfg` llega al `gBrowser` de la ventana:"
|
|
" el chrome se programa SIN abrir `omni.ja`")
|
|
print("✓ la vista dividida del motor funciona en nuestro build:"
|
|
" dos pestañas, vista ACTIVA y dos navegadores")
|
|
return 0
|
|
finally:
|
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
|
|
|
|
sys.exit(main())
|