Desktop PoC (H R1-R3): image output + returnBuffer + DESKTOP-POC.md

The adapter round-trip is now a complete, documented desktop proof of concept:

- R2: the adapter stand-in writes the painted shared buffer to an image
  (out/jihad-poc-render.ppm) and returns the buffer to the daemon
  (ReturnBuffer 0x150d, wired: asyncCmdReturnBuffer -> BrowserPageGoanna::
  returnBuffer). docs/jihad-poc-render.png shows the correct render (blue page,
  white "Adapter round-trip OK") produced end-to-end through the daemon.
- R1/R3 were already proven (daemon builds with LunaService compiled out;
  real page renders with the load lifecycle in order) -- ROUND-TRIP PASS.

docs/DESKTOP-POC.md is the single documented entry point: prerequisites, the
engine build, the daemon+adapter round-trip, the focused capability tests, and
R4 recorded as [human-review] (no desktop Enyo runtime -> the R2/R3 harness is
the Phase-1 acceptance vehicle).

Desktop Build & PoC: R1-R3 complete; R4 recorded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
NotAlexNoyleandClaude Opus 4.8 committed 2026-07-01 07:10:34 -04:00
1 parent 28bb52f98c
commit 36e85137fd
7 files changed
+142 -2

No files matched your search

+1 -1
View File
@@ -19,7 +19,7 @@ last_edited: "2026-06-30"
| Input Bridging | **click/key/mouse verified; touch wired** | 5 | ClickAt/KeyEvent/MouseEvent via nsIDOMWindowUtils, wired through daemon; VERIFIED (click fires onclick, INPUT PASS). TouchEvent (SendTouchEvent) implemented + wired but the offscreen desktop GTK widget doesn't route synthetic touch to the DOM — on-device verification pending. Gesture + coord-mapping remain |
| Navigation, Loading & Events | 0 | 6 | not started |
| Browser Services | **R1 settings + R2 cache/cookies + R3 dialogs** | 5 | R1 complete: setEnableJavaScript, setUserAgent, **setMinFontSize/setBlockPopups/setAcceptCookies** (SETTINGS2 PASS — prefs applied + window.open blocked behaviorally). R2: clearCache/clearCookies (SERVICES PASS). R3: DialogService overrides `@mozilla.org/prompter;1` — alert/confirm/prompt captured + reply routed (DIALOG PASS), installed in EngineHost so the daemon never hangs. **R4 downloads: DownloadService overrides `@mozilla.org/helperapplauncherdialog;1` — every download/unsupported-MIME handoff captured (source URL + MIME + name + length) instead of a chrome window (DOWNLOAD PASS: application/octet-stream captured), installed in EngineHost.** Remaining: R5 TLS, save-to-disk + progress/finished + per-adapter blocking delivery (adapter/device) |
| Desktop Build & PoC | 0 | 4 | not started |
| Desktop Build & PoC | **R1–R3 done; R4 [human-review]** | 4 | R1: single-entry build produces runnable jihad-browserserver (Goanna backend, LunaService compiled out) — DAEMON_UP. R2: jihad-adapter allocates buffers, Connect+OpenUrl, receives msgPainted, **writes out/jihad-poc-render.ppm** (→ docs/jihad-poc-render.png), returns buffer (0x150d wired). R3: real page renders through the whole pipe, lifecycle in order — ROUND-TRIP PASS. R4 (Enyo UI on desktop) recorded [human-review]: no desktop Enyo runtime → harness is the acceptance vehicle. See docs/DESKTOP-POC.md |
| Device Build & Packaging | 0 | 5 | not started |
## Completed this session (Tier-0, build-independent)
+99
View File
@@ -0,0 +1,99 @@
# Jihad Browser — Desktop PoC (Phase 1)
*Goanna inside, webOS alive, inshallah.*
This is the single documented entry point for the **x86_64 Linux desktop proof of
concept**: it builds the real render daemon (`jihad-browserserver`) with the
Goanna/UXP backend, and drives it with a minimal BrowserAdapter stand-in over the
**unchanged YAP IPC contract** — no webOS stack required. It satisfies
`cavekit-desktop-build.md` R1–R3; R4 (the Enyo UI on desktop) is recorded below as
`[human-review]`.
## What it proves
The isis `BrowserAdapter ↔ BrowserServer` contract is byte-identical to upstream;
only the engine behind it changed (QtWebKit → Goanna). The daemon renders real web
content into a SysV shared framebuffer and speaks the exact YAP command/message
surface the webOS UI already uses.
![Desktop PoC render](jihad-poc-render.png)
*The full pipe: the daemon loads the page in Goanna, paints into shared memory, and
the adapter stand-in writes it to an image — `data:` page shown; `https://example.com`
over TLS is in `jihad-render-example-com.png`.*
## Prerequisites
- `podman` (or Docker) — the pinned build container is Ubuntu 20.04 (GCC 9.3,
Python 3.8, autoconf2.13, yasm, full X11 dev set + Xvfb). See `build/desktop/Dockerfile`.
- The UXP source tree at `../UXP` (mounted read-write; autoconf regenerates configure).
- One-time: build the image — `podman build -t jihad-goanna-build build/desktop`.
## 1. Build the engine (once, ~30–60 min)
```bash
podman run --rm --user 0 -e HOME=/out \
-v "$PWD/../UXP:/src/uxp" -v "$PWD:/jihad" \
-v "$PWD/build/desktop/out:/out" \
jihad-goanna-build bash /jihad/build/desktop/build-goanna.sh
```
Produces `build/desktop/out/obj-jihad-goanna/dist/bin/libxul.so` plus the frozen
embedding headers. Engine patches under `build/desktop/patches/` are applied
automatically (0002 format-overflow, 0003 OMTC-off, 0004 gfx init).
## 2. Build + run the daemon and adapter round-trip (R1 + R2 + R3)
```bash
podman run --rm --user 0 -e HOME=/out \
-v "$PWD/../UXP:/src/uxp" -v "$PWD:/jihad" \
-v "$PWD/build/desktop/out:/out" \
jihad-goanna-build bash /jihad/build/desktop/build-adapter-roundtrip.sh
```
This single entry point:
- **R1** — compiles + links `jihad-browserserver` from the engine-agnostic IPC
layer (`Yap/`, `BrowserServerBase`) + the Goanna backend (`JihadBrowserServer`,
`BrowserPageGoanna`, `GoannaRenderPage`, `EngineHost`). **LunaService is compiled
out for desktop** — `Main.cpp` runs a plain GLib main loop; nothing links
`libLunaService`. The daemon starts and prints `DAEMON_UP`.
- **R2** — builds `jihad-adapter`, a `YapClient` BrowserAdapter stand-in. It
allocates the two shared buffers, sends `Connect (0x1000)` + `OpenUrl (0x1004)`,
receives `msgPainted`, **writes the buffer to `out/jihad-poc-render.ppm`**, and
**returns the buffer** (`ReturnBuffer 0x150d`) so rendering can continue.
- **R3** — a real page renders correctly through the whole pipe; the load-lifecycle
messages arrive in order (`msgLoadStarted → msgLoadProgress → msgLoadStopped →
msgLocationChanged → msgPainted`). Override the page with `JIHAD_URL=…` (e.g.
`https://example.com`). Prints `ROUND-TRIP PASS`.
`JIHAD_POC_IMAGE=/out/foo.ppm` changes the output path.
## 3. Focused capability tests
Each `build/desktop/build-*-test.sh` builds one aspect against the engine and
asserts real behavior under Xvfb (all PASS):
| Script | Proves |
|--------|--------|
| `build-embed-load.sh` | page load reaches `STATE_STOP` |
| `build-page-driver.sh` | `BrowserPageGoanna` bridge drives paint |
| `build-input-test.sh` | click/key/mouse fire DOM handlers (INPUT PASS) |
| `build-nav-test.sh` | back/forward + `canGo*` + setHtml (NAV PASS) |
| `build-history-test.sh` | clearHistory + history-state (HISTORY PASS) |
| `build-fail-test.sh` | failed-load + global-history events |
| `build-services-test.sh` | JS toggle blocks onclick (SERVICES PASS) |
| `build-settings2-test.sh` | min-font / block-popups / accept-cookies |
| `build-dialog-test.sh` | alert/confirm/prompt interception (DIALOG PASS) |
| `build-download-test.sh` | download / MIME handoff (DOWNLOAD PASS) |
| `build-resize-test.sh` / `build-scroll-test.sh` / `build-zoom-test.sh` | surface geometry |
| `build-geo-test.sh` | contents-size / meta-viewport / scrolled-to events (GEO PASS) |
## R4 — isis Enyo UI against the desktop daemon `[human-review]`
The Enyo UI (`app/`) targets the **webOS Enyo runtime + LunaService/sysmgr**, which
do not exist on a stock Linux desktop. A full desktop Enyo runtime is therefore
**not available**, so per the cavekit criterion the **R2/R3 harness is the
acceptance vehicle** for Phase 1. Driving the real UI is exercised on the device /
emulator in Phase 2 (`cavekit-device-build.md`). This is the documented, recorded
outcome for R4.
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

+1 -1
View File
@@ -396,7 +396,7 @@ void JihadBrowserServer::asyncCmdThaw(YapProxy* proxy, int32_t sharedBufferKey1,
void JihadBrowserServer::asyncCmdReturnBuffer(YapProxy* proxy, int32_t sharedBufferKey)
{
(void)proxy; // TODO(T-016): route to pageFor(proxy) / GoannaRenderPage per PORT-MAP.md
if (auto* p = pageFor(proxy)) p->returnBuffer(sharedBufferKey);
}
void JihadBrowserServer::asyncCmdSetZoomAndScroll(YapProxy* proxy, double zoom, int32_t cx, int32_t cy)
@@ -24,10 +24,41 @@ public:
W(1024), H(768), sz(1024*768*4), key1(0x4a494831), key2(0x4a494832) {}
int painted, verified;
bool loadStopped;
bool wroteImage = false;
int W, H, sz, key1, key2;
void serverConnected() override {} // YapClient never calls this; we drive from start()
// Write the painted shared buffer to a PPM image (the real adapter blits it to
// the card). Proves the desktop pipe produces a correct pixel image (R2/R3).
void writeImage(int key) {
int id = shmget(key, sz, 0);
if (id < 0) return;
unsigned char* b = (unsigned char*)shmat(id, nullptr, SHM_RDONLY);
if (b == (unsigned char*)-1) return;
const char* path = getenv("JIHAD_POC_IMAGE");
if (!path || !*path) path = "/out/jihad-poc-render.ppm";
FILE* fp = fopen(path, "wb");
if (fp) {
fprintf(fp, "P6\n%d %d\n255\n", W, H);
for (int i = 0; i < W * H; ++i) {
unsigned char* p = b + (size_t)i * 4; // B,G,R,A
unsigned char rgb[3] = { p[2], p[1], p[0] };
fwrite(rgb, 1, 3, fp);
}
fclose(fp);
printf("[adapter] wrote %dx%d render to %s\n", W, H, path);
}
shmdt(b);
}
// Return a painted buffer to the daemon per the contract (ReturnBuffer 0x150d).
void returnBuffer(int key) {
YapPacket* r = packetCommand();
(*r) << (int16_t)0x150d; (*r) << (int32_t)key;
sendAsyncCommand();
}
// Verify the daemon actually rendered content into the shared buffer it named
// in msgPainted (the real adapter would blit it to the card). Returns
// non-white pixel count, or -1.
@@ -77,6 +108,8 @@ public:
long nb = verifyBuffer(k);
printf("[adapter] <- msgPainted(key=0x%x): %ld non-white px in shared buffer\n", (unsigned)k, nb);
if (nb > 100) ++verified;
if (nb > 100 && !wroteImage) { writeImage(k); wroteImage = true; }
returnBuffer(k); // hand the buffer back so rendering can continue
if (loadStopped && verified > 0) {
printf("[adapter] verified rendered frame after load; done\n");
g_main_loop_quit(mainLoop());
+7
View File
@@ -70,6 +70,13 @@ void BrowserPageGoanna::setWindowSize(uint32_t width, uint32_t height) {
if (mPage->Resize((int)width, (int)height)) { mNeedsPaint = true; emitGeometry(); }
}
void BrowserPageGoanna::returnBuffer(int /*sharedBufferKey*/) {
// The adapter is done with a painted buffer. The current model paints once per
// load into alternating segments, so there is no in-flight bookkeeping to undo;
// accepting the return (without error) satisfies the contract and lets a
// subsequent paint proceed. Full double-buffer flow-control is future work.
}
void BrowserPageGoanna::setScrollPosition(int x, int y) {
if (!mPage) return;
// The javascript: scroll applies asynchronously; the scrolled-to message is
+1
View File
@@ -62,6 +62,7 @@ public:
bool init(uint32_t width, uint32_t height,
int sharedBufferKey1, int sharedBufferKey2, int sharedBufferSize);
void setWindowSize(uint32_t width, uint32_t height);
void returnBuffer(int sharedBufferKey); // YAP: returnBuffer (adapter freed it)
void setScrollPosition(int x, int y); // YAP: setScrollPosition
void setZoomAndScroll(double zoom, int x, int y); // YAP: setZoomAndScroll