diff --git a/context/impl/impl-overview.md b/context/impl/impl-overview.md index 9879f92..3ae1c01 100644 --- a/context/impl/impl-overview.md +++ b/context/impl/impl-overview.md @@ -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) diff --git a/docs/DESKTOP-POC.md b/docs/DESKTOP-POC.md new file mode 100644 index 0000000..5db3c3c --- /dev/null +++ b/docs/DESKTOP-POC.md @@ -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. diff --git a/docs/jihad-poc-render.png b/docs/jihad-poc-render.png new file mode 100644 index 0000000..8978e18 Binary files /dev/null and b/docs/jihad-poc-render.png differ diff --git a/render/browserserver/JihadBrowserServer.cpp b/render/browserserver/JihadBrowserServer.cpp index ccbc7da..477f8fa 100644 --- a/render/browserserver/JihadBrowserServer.cpp +++ b/render/browserserver/JihadBrowserServer.cpp @@ -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) diff --git a/render/browserserver/test/adapter_client.cpp b/render/browserserver/test/adapter_client.cpp index 0192e3b..1bf6605 100644 --- a/render/browserserver/test/adapter_client.cpp +++ b/render/browserserver/test/adapter_client.cpp @@ -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()); diff --git a/render/goanna/BrowserPageGoanna.cpp b/render/goanna/BrowserPageGoanna.cpp index 9691876..0900266 100644 --- a/render/goanna/BrowserPageGoanna.cpp +++ b/render/goanna/BrowserPageGoanna.cpp @@ -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 diff --git a/render/goanna/BrowserPageGoanna.h b/render/goanna/BrowserPageGoanna.h index 2026ade..23faae4 100644 --- a/render/goanna/BrowserPageGoanna.h +++ b/render/goanna/BrowserPageGoanna.h @@ -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