code wiki / _hdl_build / nx_browser.nx

nx_browser.nx source

↩ module page · 1470 lines · 77361 B

1// nx_browser.nx -- the LIVE, NAVIGABLE sovereign browser WINDOW. Fetches a URL over our own DNS+TLS1.3+ 2// HTTP, renders it with the full upgraded pipeline (chrome/hidden-drop -> complex-selector cascade -> 3// float/grid/table-cell/word-wrap layout), and shows it in a real scrollable X11 window (WSLg). CLICK a 4// link to navigate (own URL resolver), 'b' = back, scroll wheel / arrows, q quits. 100% sovereign -- no 5// Chrome/V8/curl/gcc. Initial URL from _url.txt. license_tier: ORIGINAL 6import "nx_syscalls.nx" 7import "nx_tier.nx" // defines `type nx_size = i64`. br_comp_border casts NX_CSS_COLOR_BYTES to it, 8 // and WITHOUT this import that cast resolves to an UNKNOWN TYPE, which desyncs 9 // the parser and surfaces far away as "unexpected operator token kind=46" (TK_EQ). 10 // Bisected 2026-08-16: adding this import removes that desync outright. 11import "nx_x509_trust_store.nx" 12import "nx_trust_store_load_from_certdata.nx" 13import "nx_https_get.nx" 14import "nx_http_response_parse.nx" 15import "nx_html_tokenizer.nx" 16import "nx_js_eval.nx" // R-JS-DOMWRITE: js_render_page + js_has_inline_script (run page JS, render DOM mutations) 17import "nx_dom_query.nx" 18import "nx_html_entities.nx" 19import "nx_url_resolve.nx" 20import "nx_render_html.nx" 21import "nx_css_color_decode.nx" 22import "nx_layout_box.nx" 23import "nx_layout_default_display.nx" 24import "nx_layout_from_dom.nx" 25import "nx_layout_block.nx" 26import "nx_x11_connect.nx" 27import "nx_x11_window.nx" 28import "nx_x11_paint.nx" 29import "nx_http_get_ua.nx" 30import "nx_dns_resolve_a_record.nx" 31import "nx_paint_solid_rect.nx" 32import "nx_paint_text.nx" 33import "nx_png_write.nx" 34import "nx_signal.nx" 35import "nx_browser_buyguard.nx" // "before you buy" pass: splice an opportunity-cost overlay into the page 36import "nx_intake_profile.nx" // read the operator's own intake numbers (their real $ vs what it costs) 37import "nx_media_harvest.nx" // 'm' key: panel emitter + kind labels for the media source list 38import "nx_media_deepcrawl.nx" // 'm' key: DEEP crawl -- follow the page's css/js (incl. JS-constructed URLs) and harvest their media 39 40struct Page { 41 buf: *u8, 42 tree: *LayoutTree, 43 computed: *CssComputedDecl, 44 ncomp: i64, 45 page_h: i64, 46 bhref_off: *i64, // per box_idx: href byte-offset into buf, or -1 47 bhref_len: *i64, 48 ok: i64, 49 raw: *u8, // fetched response body (kept so resize can re-layout WITHOUT re-fetching) 50 raw_len: i64 51} 52const NX_PAGE_BYTES: i64 = 80 53const NX_CHROME_H: i64 = 44 // browser chrome (toolbar + address bar) height; page viewport is below it 54 55// ---- headless-mode constants, NAMED FOR PURPOSE (rule 11; the magic ratchet refused these as inline 56// literals on 2026-08-27 and it was right to). BR_PATH_MAX is the OS path bound, not a guess: the 57// sweep composes <compare-root>/<domain>/index.html into it and the domain comes from a list whose 58// rows are already shorter than a path. BR_DEFAULT_VW matches the window's own WW so a headless shot 59// lays out at the SAME width the windowed browser would use -- two different numbers there would make 60// the headless render a measurement of something the user never sees. 61const BR_PATH_MAX: i64 = 4096 62const BR_DEFAULT_VW: i64 = 1000 63const BR_ASCII_NL: i64 = 10 64const BR_ASCII_CR: i64 = 13 65const BR_ASCII_HASH: i64 = 35 66const BR_NAME_COL: i64 = 20 // report column width for a domain name 67 68func br_slen(s: *u8) -> i64 { var k: i64=0; while s[k]!=(0 as u8){k=k+1} return k } 69func br_p(fd: i64, s: *u8) -> i64 { sys_write(fd, s, br_slen(s)); return 0 } 70func br_n(fd: i64, v0: i64) -> i64 { var v: i64=v0; if v<0 { let m: *u8=sys_mmap(8); m[0]=45 as u8; sys_write(fd,m,1); v=0-v } let b: *u8=sys_mmap(24); var k: i64=0; if v==0{b[0]=48;k=1} while v>0{b[k]=(48+(v%10)) as u8;v=v/10;k=k+1} let o: *u8=sys_mmap(24); var j: i64=0; while j<k{o[j]=b[k-1-j];j=j+1} sys_write(fd,o,k); return 0 } 71 72// write buf[0..n) to path (O_CREAT|O_TRUNC|O_WRONLY, 0644). returns bytes written or -1. 73func br_write_file(path: *u8, buf: *u8, n: i64) -> i64 { 74 let fd: i64 = __syscall(257, 0-100, path as i64, 577, 420, 0, 0) // openat(AT_FDCWD, path, O_WRONLY|O_CREAT|O_TRUNC, 0644) 75 if fd < 0 { return 0 - 1 } 76 var done: i64 = 0 77 while done < n { let w: i64 = sys_write(fd, ((buf as i64)+done) as *u8, n-done); if w <= 0 { done = n } else { done = done + w } } 78 __syscall(3, fd, 0, 0, 0, 0, 0) // close 79 return n 80} 81 82// 'm' key: DEEP-harvest every image/gif/video/stream/audio SOURCE URL reachable through THIS page's resource 83// graph -- it follows the CSS/JS the page pulls in, INCLUDING asset URLs the JS constructs from string constants 84// (reconstructed by nx_js_urlfold WITHOUT executing anything), so runtime-loaded art on Unity/heavy-JS pages 85// surfaces with no manual network capture. Prints the list to the terminal + (re)writes the shared media panel. 86// Budget-bounded (16 fetches / depth 1) so the window only blocks briefly. Never runs the page's JS. 87func br_media_dump(store: *TrustStore, cur_url: *u8, ulen: i64) -> i64 { 88 br_p(1, "nx_browser: harvesting media (deep crawl -- following this page's css/js)...\n" as *u8) 89 let M: *MediaSet = nx_mediaset_new() 90 let fetched: i64 = nx_media_deepcrawl(cur_url, ulen, store, M, 16, 1, 1, "knowledge/status/media_panel_raw.html" as *u8) 91 br_p(1, "nx_browser: fetched " as *u8); br_n(1, fetched); br_p(1, " resources, media sources = " as *u8); br_n(1, M.cnt); br_p(1, "\n" as *u8) 92 var d: i64 = 0 93 while d < M.cnt { 94 let bp: *u8 = ((M.buf as i64) + M.off[d]) as *u8 95 br_p(1, " [" as *u8); br_p(1, nx_media_kind_label(M.kind[d])); br_p(1, "] " as *u8) 96 sys_write(1, bp, M.len[d]); br_p(1, "\n" as *u8) 97 d = d + 1 98 } 99 let H: *u8 = sys_mmap(12582912) 100 let hlen: i64 = nx_media_emit_panel(H, cur_url, ulen, M.buf, M.off, M.len, M.kind, M.cnt) 101 br_write_file("knowledge/status/media_panel.html" as *u8, H, hlen) 102 br_p(1, "nx_browser: media panel -> knowledge/status/media_panel.html (open in any browser)\n" as *u8) 103 return 0 104} 105 106func br_comp_int(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, prop: *u8, plen: i64, dflt: i64) -> i64 { 107 var j: i64 = ncomp - 1 108 while j >= 0 { 109 let cd: *CssComputedDecl = ((computed as i64) + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 110 if cd.element_idx == box_idx { if cd.prop_len == plen { 111 var ok: i64 = 1; var k: i64 = 0 112 while k < plen { if (src[cd.prop_off+k]&0xff) != (prop[k]&0xff) { ok=0; k=plen } else { k=k+1 } } 113 if ok == 1 { 114 var v: i64 = 0; var any: i64 = 0; var i: i64 = 0 115 while i < cd.val_len { let c: i64=src[cd.val_off+i]&0xff; if c>=48 { if c<=57 { v=v*10+(c-48); any=1 } } i=i+1 } 116 if any == 1 { return v } 117 return dflt 118 } 119 } } 120 j = j - 1 121 } 122 return dflt 123} 124func br_comp_color(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, prop: *u8, plen: i64) -> i64 { 125 var j: i64 = ncomp - 1 126 while j >= 0 { 127 let cd: *CssComputedDecl = ((computed as i64) + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 128 if cd.element_idx == box_idx { if cd.prop_len == plen { 129 var ok: i64 = 1; var k: i64 = 0 130 while k < plen { if (src[cd.prop_off+k]&0xff) != (prop[k]&0xff) { ok=0; k=plen } else { k=k+1 } } 131 if ok == 1 { 132 let col: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor 133 var got: i64 = 0 134 if cd.val_kind == NX_CSS_VAL_HEXCOLOR { got = nx_css_color_decode(src, cd.val_off, cd.val_len, col) } 135 else { if cd.val_kind == NX_CSS_VAL_IDENT { got = nx_css_color_named(src, cd.val_off, cd.val_len, col); if got != 1 { got = nx_css_color_rgb_func(src, cd.val_off, cd.val_len, col) } } } 136 if got == 1 { if col.a != 0 { return (col.r << 16) | (col.g << 8) | col.b } } 137 return 0 - 1 138 } 139 } } 140 j = j - 1 141 } 142 return 0 - 1 143} 144 145// Extract elements (tag/id/class) AND per-element href, mirroring rh_extract's chrome-skip EXACTLY (so the 146// element order == the layout box order). ehref_off/ehref_len get the <a href> span, or -1. 147func br_extract(html: *u8, hlen: i64, elements: *CssElement, ehref_off: *i64, ehref_len: *i64, maxn: i64) -> i64 { 148 let c: *HtmlCursor=sys_mmap(24) as *HtmlCursor; nx_html_cursor_init(c,html,hlen) 149 let tok: *HtmlToken=sys_mmap(56) as *HtmlToken 150 let ao: *i64=sys_mmap(8) as *i64; let al: *i64=sys_mmap(8) as *i64; let bo: *i64=sys_mmap(8) as *i64; let bl: *i64=sys_mmap(8) as *i64 151 let ho: *i64=sys_mmap(8) as *i64; let hl: *i64=sys_mmap(8) as *i64 152 var n: i64=0 153 var cskip: i64=0 154 while 1==1 { 155 nx_html_next_token(c,tok) 156 if tok.kind==NX_HTML_TOK_EOF { return n } 157 if n>=maxn { return n } 158 var ie: i64=0 159 if cskip > 0 { 160 if tok.kind==NX_HTML_TOK_START_TAG { cskip = cskip + 1 } 161 if tok.kind==NX_HTML_TOK_END_TAG { cskip = cskip - 1 } 162 } else { 163 if tok.kind==NX_HTML_TOK_START_TAG { 164 if _lfd_is_chrome_start(html, tok.name_off, tok.name_len, tok.src_off, tok.src_len) == 1 { cskip = 1 } 165 else { ie = 1 } 166 } 167 if tok.kind==NX_HTML_TOK_SELF_CLOSING { ie = 1 } 168 } 169 if ie==1 { 170 let e: *CssElement=((elements as i64)+n*NX_CSS_ELEMENT_BYTES) as *CssElement 171 e.src=html; e.tag_off=tok.name_off; e.tag_len=tok.name_len 172 if nx_dom_find_attr(html,tok.src_off,tok.src_len,"id\x00" as *u8,ao,al)==1 { e.id_off=ao[0]; e.id_len=al[0] } else { e.id_off=0; e.id_len=0 } 173 if nx_dom_find_attr(html,tok.src_off,tok.src_len,"class\x00" as *u8,bo,bl)==1 { e.class_off=bo[0]; e.class_len=bl[0] } else { e.class_off=0; e.class_len=0 } 174 ehref_off[n] = 0 - 1; ehref_len[n] = 0 - 1 175 if tok.name_len==1 { if (html[tok.name_off]&0xff)==97 { // <a> 176 if nx_dom_find_attr(html,tok.src_off,tok.src_len,"href\x00" as *u8,ho,hl)==1 { ehref_off[n]=ho[0]; ehref_len[n]=hl[0] } 177 } } 178 n=n+1 179 } 180 } 181 return n 182} 183 184// Fetch `url` (own DNS+TLS+HTTP), run the full pipeline, fill `page`. page.ok=1 on success. 185func br_isdigit(c: i64) -> i64 { if c >= 48 { if c <= 57 { return 1 } } return 0 } 186 187// "http://" (plain) but NOT "https://" -> 1 188func br_is_plain_http(url: *u8, ulen: i64) -> i64 { 189 if ulen < 7 { return 0 } 190 if (url[0] & 0xff) != 104 { return 0 } // h 191 if (url[1] & 0xff) != 116 { return 0 } // t 192 if (url[2] & 0xff) != 116 { return 0 } // t 193 if (url[3] & 0xff) != 112 { return 0 } // p 194 if (url[4] & 0xff) != 58 { return 0 } // : 195 if (url[5] & 0xff) != 47 { return 0 } // / 196 if (url[6] & 0xff) != 47 { return 0 } // / 197 return 1 198} 199 200// dotted-quad "a.b.c.d" -> packed (a<<24|b<<16|c<<8|d), or -1 if not a literal IPv4 201func br_parse_ipv4(s: *u8, len: i64) -> i64 { 202 var parts: i64 = 0 203 var cur: i64 = 0 204 var any: i64 = 0 205 var packed: i64 = 0 206 var i: i64 = 0 207 while i < len { 208 let c: i64 = s[i] & 0xff 209 if c == 46 { 210 if any == 0 { return 0 - 1 } 211 if cur > 255 { return 0 - 1 } 212 packed = (packed << 8) | cur 213 parts = parts + 1; cur = 0; any = 0 214 } else { 215 if br_isdigit(c) == 0 { return 0 - 1 } 216 cur = cur * 10 + (c - 48); any = 1 217 } 218 i = i + 1 219 } 220 if any == 0 { return 0 - 1 } 221 if cur > 255 { return 0 - 1 } 222 packed = (packed << 8) | cur 223 parts = parts + 1 224 if parts != 4 { return 0 - 1 } 225 return packed 226} 227 228// Sovereign plain-HTTP/1.1 fetch of an http:// URL into out (<=cap). IP-literal 229// hosts (localhost daemons) parsed directly; hostnames resolved via our own DNS. 230// Returns bytes read (full raw response, headers+body) or -1. NO TLS, NO 3rd party. 231func br_http_fetch(url: *u8, ulen: i64, out: *u8, cap: i64) -> i64 { 232 var p: i64 = 7 // skip "http://" 233 let host: *u8 = sys_mmap(512) 234 var hlen: i64 = 0 235 var go: i64 = 1 236 while go == 1 { 237 if p >= ulen { go = 0 } 238 else { 239 let c: i64 = url[p] & 0xff 240 if c == 58 { go = 0 } else { if c == 47 { go = 0 } else { host[hlen] = url[p]; hlen = hlen + 1; p = p + 1 } } 241 } 242 } 243 host[hlen] = 0 as u8 244 if hlen == 0 { return 0 - 1 } 245 246 var port: i64 = 80 247 if p < ulen { if (url[p] & 0xff) == 58 { 248 p = p + 1; port = 0 249 var g2: i64 = 1 250 while g2 == 1 { 251 if p >= ulen { g2 = 0 } 252 else { 253 let c: i64 = url[p] & 0xff 254 if br_isdigit(c) == 1 { port = port * 10 + (c - 48); p = p + 1 } else { g2 = 0 } 255 } 256 } 257 } } 258 259 let path: *u8 = sys_mmap(2048) 260 var plen: i64 = 0 261 while p < ulen { path[plen] = url[p]; plen = plen + 1; p = p + 1 } 262 if plen == 0 { path[0] = 47 as u8; plen = 1 } // "/" 263 path[plen] = 0 as u8 264 265 var ipv4: i64 = br_parse_ipv4(host, hlen) 266 if ipv4 < 0 { 267 let dns: *DnsResolveResult = nx_dns_resolve_default(host, hlen, 305419896) 268 if dns.verdict != NX_DNS_R_OK { return 0 - 1 } 269 if dns.ipv4_packed == 0 { return 0 - 1 } 270 ipv4 = dns.ipv4_packed 271 } 272 let sa: *u8 = sys_mmap(16) 273 nx_https_build_sockaddr(sa, ipv4, port) 274 let status: *i64 = sys_mmap(8) as *i64 275 let ua: *u8 = "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0\x00" 276 return nx_http_get_ua(sa, path, plen, host, hlen, ua, br_slen(ua), out, cap, status) 277} 278 279// layout-only pass: (re)build the style buffer + lay out page.raw at viewport width `vw`. 280// Split from the fetch so a window RESIZE re-lays-out at the new width with NO network round-trip. 281func br_layout(page: *Page, vw: i64) -> i64 { 282 page.ok = 0 283 let body: *u8 = page.raw 284 let body_len: i64 = page.raw_len 285 286 let css: *u8 = "h1{background-color:#a8c8ff;margin-top:24px;margin-bottom:10px;font-size:40px}h2{background-color:#d0d0d0;margin-top:20px;margin-bottom:8px;font-size:28px}h3{background-color:#e4e4e4;margin-top:16px;margin-bottom:6px;font-size:24px}body{background-color:#ffffff;padding-left:40px;padding-right:40px;padding-top:14px;max-width:900px}p{margin-top:12px;margin-bottom:12px}ul{padding-left:16px;margin-top:3px;margin-bottom:3px}ol{padding-left:24px;margin-top:3px;margin-bottom:3px}li{margin-bottom:2px}tr{display:flex}th{font-weight:bold;padding-left:4px;padding-right:4px}td{padding-left:4px;padding-right:4px}a{color:#3366cc}b{font-weight:bold}strong{font-weight:bold}i{font-style:italic}em{font-style:italic}.vector-menu-content-list{display:none}.vector-dropdown{display:none}.mw-interlanguage-selector{display:none}.wbc-editpage{display:none}.infobox{float:right;width:300px;margin-left:8px;background-color:#f8f8f8}.nishi-buyguard{background-color:#fff3cd;color:#332701;padding-left:14px;padding-right:14px;padding-top:8px;padding-bottom:8px;margin-bottom:10px;font-size:18px}\x00" 287 let clen: i64 = br_slen(css) 288 let STYCAP: i64 = 262144 289 let buf: *u8 = sys_mmap(body_len + STYCAP + clen + 16) 290 var bi: i64 = 0 291 while bi < body_len { buf[bi]=body[bi]; bi=bi+1 } 292 // CASCADE ORIGIN ORDER (07-09 root fix, twin of nx_browser_render.nx): UA defaults FIRST, author 293 // <style> AFTER -- the last-match resolvers now let author rules override the defaults (correct CSS 294 // origin order); before this the defaults silently beat every author color on styled pages. 295 var ci: i64 = 0 296 while ci < clen { buf[body_len + ci] = css[ci]; ci=ci+1 } 297 let rawsty: *u8 = sys_mmap(STYCAP) 298 let rawstylen: i64 = rh_extract_styles(body, body_len, rawsty, STYCAP) 299 // VIEWPORT-CORRECT @media (2026-08-25): VW is now computed BEFORE the media filter, so @media rules are 300 // selected at the SAME width the page is laid out at. This call used to pass a literal 1000 while VW was 301 // derived two lines BELOW it, so every responsive page was STYLED for a 1000px viewport and LAID OUT at 302 // the real window width -- and because br_layout is re-run on resize (see the docstring), a resize 303 // re-laid-out without ever re-selecting the styles. The sibling render core already passed the real vw 304 // (nx_browser_render.nx:567); this is that half of the same fix landing in the shipped browser. 305 var VW: i64 = vw 306 if VW < 200 { VW = 200 } 307 let stylen: i64 = rh_filter_media(rawsty, 0, rawstylen, ((buf as i64)+body_len+clen) as *u8, STYCAP, VW) 308 let total: i64 = body_len + clen + stylen 309 let boxes: *LayoutBox = (sys_mmap(NX_LAYOUT_BOX_BYTES*16384+16)) as *LayoutBox 310 let tree: *LayoutTree = (sys_mmap(64)) as *LayoutTree 311 nx_layout_tree_init(tree, boxes, 16384) 312 let stk: *LayoutFromDomStack = (sys_mmap(NX_LAYOUT_FROM_DOM_STACK_BYTES+8)) as *LayoutFromDomStack 313 let sidx: *i64 = (sys_mmap(8*128)) as *i64 314 nx_layout_from_dom_stack_init(stk, sidx, 128) 315 nx_layout_from_dom(buf, body_len, tree, stk) 316 317 let ext: *CssElement = (sys_mmap(NX_CSS_ELEMENT_BYTES*16384 as nx_size)) as *CssElement 318 let ehoff: *i64 = sys_mmap(8*16384) as *i64 319 let ehlen: *i64 = sys_mmap(8*16384) as *i64 320 let n_ext: i64 = br_extract(buf, body_len, ext, ehoff, ehlen, 16384) 321 let boxel: *CssElement = (sys_mmap((NX_CSS_ELEMENT_BYTES*(tree.count+1)) as nx_size)) as *CssElement 322 let bhoff: *i64 = sys_mmap(8*(tree.count+1)) as *i64 323 let bhlen: *i64 = sys_mmap(8*(tree.count+1)) as *i64 324 var k: i64 = 0 325 var ii: i64 = 0 326 while ii < tree.count { 327 let b: *LayoutBox = ((tree.boxes as i64)+ii*NX_LAYOUT_BOX_BYTES) as *LayoutBox 328 let e: *CssElement = ((boxel as i64)+ii*NX_CSS_ELEMENT_BYTES) as *CssElement 329 e.src = buf 330 bhoff[ii] = 0 - 1; bhlen[ii] = 0 - 1 331 var mapped: i64 = 0 332 if rh_is_elem(b.kind)==1 { if b.parent_idx != (0-1) { if k < n_ext { 333 let s: *CssElement = ((ext as i64)+k*NX_CSS_ELEMENT_BYTES) as *CssElement 334 e.tag_off=s.tag_off; e.tag_len=s.tag_len; e.id_off=s.id_off; e.id_len=s.id_len; e.class_off=s.class_off; e.class_len=s.class_len 335 bhoff[ii] = ehoff[k]; bhlen[ii] = ehlen[k] 336 k=k+1; mapped=1 337 } } } 338 if mapped==0 { e.tag_off=0; e.tag_len=0; e.id_off=0; e.id_len=0; e.class_off=0; e.class_len=0 } 339 ii = ii + 1 340 } 341 342 let cur: *CssCursor = (sys_mmap(NX_CSS_CURSOR_BYTES) as nx_size) as *CssCursor 343 nx_css_cursor_init(cur, buf, total); cur.pos = body_len 344 let tok: *CssToken = (sys_mmap(NX_CSS_TOKEN_BYTES) as nx_size) as *CssToken 345 let rules: *CssRule = (sys_mmap(NX_CSS_RULE_BYTES*8192 as nx_size)) as *CssRule 346 let decls: *CssDeclaration = (sys_mmap(NX_CSS_DECLARATION_BYTES*32768 as nx_size)) as *CssDeclaration 347 let st: *CssParseState = (sys_mmap(128) as nx_size) as *CssParseState 348 nx_css_parse_state_init(st, cur, tok, rules, 8192, decls, 32768) 349 nx_css_parse(st) 350 let computed: *CssComputedDecl = (sys_mmap(NX_CSS_COMPUTED_DECL_BYTES*262144 as nx_size)) as *CssComputedDecl 351 let cb: *i64 = (sys_mmap(8)) as *i64 352 rh_cascade(buf, rules, st.rule_count, decls, st.decl_count, boxel, tree.count, tree, computed, 262144, cb) 353 let ncomp: i64 = cb[0] 354 rh_promote_display_kinds(tree, computed, ncomp, buf) // inline + display:flex/grid/block -> BLOCK (sized + painted) 355 356 let pbuf: *u8 = sys_mmap(256) 357 let ltable: *LayoutPropTable = (sys_mmap(NX_LAYOUT_PROP_TABLE_BYTES)) as *LayoutPropTable 358 nx_layout_prop_table_init(ltable, pbuf) 359 let resolve: *CssResolveCtx = (sys_mmap(NX_CSS_RESOLVE_CTX_BYTES)) as *CssResolveCtx 360 resolve.root_font_size_px = 16; resolve.parent_font_size_px = 16; resolve.parent_dimension_px = VW 361 let lctx: *LayoutCtx = (sys_mmap(NX_LAYOUT_CTX_BYTES)) as *LayoutCtx 362 nx_layout_ctx_init(lctx, tree, computed, ncomp, buf, VW, resolve) 363 let page_h: i64 = nx_layout_block_layout(lctx, ltable, 0) 364 365 page.buf = buf; page.tree = tree; page.computed = computed; page.ncomp = ncomp 366 page.page_h = page_h; page.bhref_off = bhoff; page.bhref_len = bhlen; page.ok = 1 367 return 0 368} 369 370// load the operator's OWN financial profile (from their intake survey) into the buyguard profile array. 371// This is the local browser on the operator's machine -> respondent "operator"; ip_get_pos falls back to 372// safe defaults for any field they have not entered yet (so the guard works before the survey is filled). 373func bg_load_profile(prof: *i64) -> i64 { 374 let P: *u8 = "knowledge/store/intake-\x00" as *u8 375 let who: *u8 = "operator\x00" as *u8 376 prof[BGW_AVAIL] = ip_get_pos(P, who, "available_cash\x00" as *u8, 50000) 377 prof[BGW_BAL] = ip_get_pos(P, who, "debt_balance\x00" as *u8, 2000000) 378 prof[BGW_APR] = mny_rate_from_pct(ip_get_pos(P, who, "debt_apr_pct\x00" as *u8, 24), 1) 379 prof[BGW_PAY] = ip_get_pos(P, who, "monthly_payment\x00" as *u8, 60000) 380 prof[BGW_WAGE] = ip_get_pos(P, who, "hourly_wage\x00" as *u8, 2500) 381 prof[BGW_GOAL] = ip_get_pos(P, who, "buffer_goal\x00" as *u8, 100000) 382 prof[BGW_THRESH] = ip_get_pos(P, who, "pause_threshold\x00" as *u8, 10000) 383 return 0 384} 385 386// ---- DOCUMENT OMNITOOL: the browser natively opens documents (.docx/.md/.csv/...) it navigates to ---- 387// A registered document extension diverts the fetched bytes through the nx_doc_view dispatch (data-driven 388// registry knowledge/doc_viewers.conf -> focused sovereign renderer organ), and the browser paints the 389// renderer's 0-JS HTML preview inline instead of dead-ending on a download. The renderers stay OUT of this 390// binary (fork-exec'd on demand) -- supporting more formats never grows the browser core. 391 392// lowercased extension of the URL's LAST path segment (after the host, before ?/#) into ext (NUL-term). 393// Returns 0 for: no path, no dot in the last segment, ext>12 chars, or html/htm (normal HTML pipeline). 394func br_doc_ext(url: *u8, ulen: i64, ext: *u8) -> i64 { 395 // end of path = first '?' or '#' 396 var pend: i64 = ulen 397 var i: i64 = 0 398 while i < ulen { 399 let c: i64 = url[i] & 0xff 400 if c == 63 { pend = i; i = ulen } else { if c == 35 { pend = i; i = ulen } else { i = i + 1 } } 401 } 402 // host starts after "://" (when present); a REAL path needs a '/' at/after hstart (bare hosts opt out) 403 var hstart: i64 = 0 404 var scanning: i64 = 1 405 i = 0 406 while scanning == 1 { 407 if i + 2 >= pend { scanning = 0 } else { 408 if url[i] == (58 as u8) { if url[i+1] == (47 as u8) { if url[i+2] == (47 as u8) { hstart = i + 3; scanning = 0 } } } 409 if scanning == 1 { i = i + 1 } 410 } 411 } 412 var slash: i64 = 0 - 1 413 var dot: i64 = 0 - 1 414 i = hstart 415 while i < pend { 416 let c: i64 = url[i] & 0xff 417 if c == 47 { slash = i; dot = 0 - 1 } else { if c == 46 { dot = i } } 418 i = i + 1 419 } 420 if slash < 0 { return 0 } 421 if dot < 0 { return 0 } 422 if dot <= slash { return 0 } 423 var k: i64 = 0 424 i = dot + 1 425 while i < pend { 426 if k >= 12 { return 0 } 427 var c2: i64 = url[i] & 0xff 428 if c2 >= 65 { if c2 <= 90 { c2 = c2 + 32 } } 429 ext[k] = c2 as u8 430 k = k + 1 431 i = i + 1 432 } 433 ext[k] = 0 as u8 434 if k == 0 { return 0 } 435 if k == 4 { if ext[0] == (104 as u8) { if ext[1] == (116 as u8) { if ext[2] == (109 as u8) { if ext[3] == (108 as u8) { return 0 } } } } } 436 if k == 3 { if ext[0] == (104 as u8) { if ext[1] == (116 as u8) { if ext[2] == (109 as u8) { return 0 } } } } 437 return k 438} 439 440// build the staging paths: pin = "/tmp/nx_bdoc_in.<ext>", pout = "/tmp/nx_bdoc_<ext>.html" 441func br_doc_paths(ext: *u8, extn: i64, pin: *u8, pout: *u8) -> i64 { 442 let a: *u8 = "/tmp/nx_bdoc_in.\x00" as *u8 443 var o: i64 = 0 444 while a[o] != 0 as u8 { pin[o] = a[o]; o = o + 1 } 445 var k: i64 = 0 446 while k < extn { pin[o + k] = ext[k]; k = k + 1 } 447 pin[o + k] = 0 as u8 448 let b: *u8 = "/tmp/nx_bdoc_\x00" as *u8 449 o = 0 450 while b[o] != 0 as u8 { pout[o] = b[o]; o = o + 1 } 451 k = 0 452 while k < extn { pout[o + k] = ext[k]; k = k + 1 } 453 o = o + k 454 let s: *u8 = ".html\x00" as *u8 455 k = 0 456 while s[k] != 0 as u8 { pout[o + k] = s[k]; k = k + 1 } 457 pout[o + k] = 0 as u8 458 return 0 459} 460 461// stage the fetched bytes and run the dispatch: 0 = converted (/tmp/nx_bdoc_<ext>.html written); 462// 3 = extension not registered; anything else = converter failure. All nonzero -> caller falls through. 463func br_doc_conv(ext: *u8, extn: i64, body: *u8, blen: i64, url: *u8) -> i64 { 464 let pin: *u8 = sys_mmap(64) 465 let pout: *u8 = sys_mmap(64) 466 br_doc_paths(ext, extn, pin, pout) 467 let fd: i64 = sys_openat_wr(pin, 0x1a4) 468 if fd < 0 { return 0 - 1 } 469 sys_write(fd, body, blen) 470 sys_close(fd) 471 let pid: i64 = sys_fork() 472 if pid == 0 { 473 let lfd: i64 = sys_openat_wr("/tmp/nx_bdoc.log\x00" as *u8, 0x1a4) 474 if lfd >= 0 { sys_dup3(lfd, 1, 0); sys_dup3(lfd, 2, 0) } 475 let argv: *i64 = sys_mmap(64) as *i64 476 argv[0] = "_offc/nx_doc_view.elf\x00" as *u8 as i64 477 argv[1] = pin as i64 478 argv[2] = pout as i64 479 argv[3] = url as i64 480 argv[4] = 0 481 let envp: *i64 = sys_mmap(16) as *i64 482 envp[0] = 0 483 sys_execve("_offc/nx_doc_view.elf\x00" as *u8, argv, envp) 484 sys_exit(127) 485 } 486 let st: *i64 = sys_mmap(16) as *i64 487 sys_wait4(pid, st, 0) 488 let sig: i64 = st[0] & 0x7f 489 if sig != 0 { return 128 + sig } 490 return (st[0] >> 8) & 0xff 491} 492 493// fetch <url> over our own DNS/TLS/HTTP, store the raw body in `page`, then lay it out at width `vw`. 494func load_page(url: *u8, ulen: i64, store: *TrustStore, cr: *u8, priv: *u8, page: *Page, vw: i64) -> i64 { 495 page.ok = 0 496 let now: i64 = sys_now_realtime_sec() 497 let cap: i64 = 8388608 498 let rbuf: *u8 = sys_mmap(cap) 499 // mutable current URL so we can FOLLOW REDIRECTS (apex->www, http->https, 30x Location) like a browser 500 let cururl: *u8 = sys_mmap(4096) 501 var curlen: i64 = ulen 502 if curlen > 4094 { curlen = 4094 } 503 var ci: i64 = 0 504 while ci < curlen { cururl[ci] = url[ci]; ci = ci + 1 } 505 cururl[curlen] = 0 as u8 506 let r: *i64 = nx_http_resp_alloc() 507 var n: i64 = 0 - 1 508 var pv: i64 = 0 - 1 509 var hops: i64 = 0 510 var go: i64 = 1 511 while go == 1 { 512 go = 0 513 n = 0 - 1 514 if br_is_plain_http(cururl, curlen) == 1 { n = br_http_fetch(cururl, curlen, rbuf, cap) } 515 else { n = nx_https_get(cururl, cr, priv, store, now, rbuf, cap) } 516 if n < 0 { return n } // propagate the specific failure code (-2 url, -3 connect/DNS, -4 handshake, -5 fetch) 517 pv = nx_http_response_parse(rbuf, n, r) 518 if pv == NX_HTTP_RESP_OK { if r[1] >= 300 { if r[1] < 400 { if hops < 6 { 519 let vo: *i64 = sys_mmap(16) as *i64 520 let vl: *i64 = sys_mmap(16) as *i64 521 if nx_http_response_header(rbuf, n, r, "location" as *u8, 8, vo, vl) == 1 { 522 let nb: *u8 = sys_mmap(4096) 523 let nl: *i64 = sys_mmap(16) as *i64 524 if nx_url_resolve(cururl, curlen, ((rbuf as i64) + vo[0]) as *u8, vl[0], nb, 4096, nl) == NX_URL_RESOLVE_OK { 525 var z: i64 = 0 526 while z < nl[0] { cururl[z] = nb[z]; z = z + 1 } 527 cururl[nl[0]] = 0 as u8; curlen = nl[0]; hops = hops + 1; go = 1 528 br_p(2, "nx_browser: redirect -> \x00" as *u8); sys_write(2, cururl, curlen); br_p(2, "\n\x00" as *u8) 529 } 530 } 531 } } } } 532 } 533 var body: *u8 = rbuf 534 var body_len: i64 = n 535 if pv == NX_HTTP_RESP_OK { 536 let bo: i64 = r[6]; let kind: i64 = r[8] 537 body = ((rbuf as i64) + bo) as *u8 538 body_len = n - bo 539 if kind == NX_HTTP_BODY_CONTENT_LENGTH { body_len = r[7]; if body_len > n - bo { body_len = n - bo } } 540 if kind == NX_HTTP_BODY_CHUNKED { let dcap: i64 = 8388608; let dbuf: *u8 = sys_mmap(dcap); let dl: i64 = nx_http_dechunk(body, n - bo, dbuf, dcap); if dl > 0 { body = dbuf; body_len = dl } } 541 } 542 // DOCUMENT OMNITOOL: registered doc extension -> convert via the nx_doc_view dispatch and paint the 543 // HTML preview inline (the browser opens .docx/.md/.csv/... natively). html/htm, unregistered exts and 544 // any converter failure fall through to the normal pipeline untouched -- graceful by construction. 545 let dext: *u8 = sys_mmap(32) 546 let dextn: i64 = br_doc_ext(cururl, curlen, dext) 547 if dextn > 0 { 548 let drc: i64 = br_doc_conv(dext, dextn, body, body_len, cururl) 549 if drc == 0 { 550 let pin2: *u8 = sys_mmap(64) 551 let pout2: *u8 = sys_mmap(64) 552 br_doc_paths(dext, dextn, pin2, pout2) 553 let dlp: *i64 = sys_mmap(16) as *i64 554 let dhtml: *u8 = sys_read_file(pout2, dlp) 555 if dlp[0] > 0 { 556 body = dhtml 557 body_len = dlp[0] 558 br_p(1, "nx_browser: doc omnitool rendered .\x00" as *u8) 559 sys_write(1, dext, dextn) 560 br_p(1, " inline\n\x00" as *u8) 561 } 562 } 563 } 564 // R-JS-DOMWRITE: if the page has an INLINE <script>, EXECUTE it against a mutable DOM and lay out the 565 // MUTATED html -- so JS-built content renders. RCDATA-safe round-trip; script-less pages skip this. 566 var rbody: *u8 = body 567 var rblen: i64 = body_len 568 if js_has_inline_script(body, body_len) == 1 { 569 let jscap: i64 = body_len + 1048576 570 let jsbuf: *u8 = sys_mmap(jscap) 571 let jslen: i64 = js_render_page(body, body_len, jsbuf, jscap) 572 if jslen > 0 { rbody = jsbuf; rblen = jslen } 573 } 574 // BUYGUARD: run the before-you-buy pass on the fetched page and splice the opportunity-cost overlay in 575 // as a top-of-page box (no price detected -> body returned untouched). Uses the operator's real numbers. 576 let bgprof: *i64 = sys_mmap(8 * 8) as *i64 577 bg_load_profile(bgprof) 578 let injcap: i64 = rblen + 65536 579 let injbuf: *u8 = sys_mmap(injcap) 580 let injlen: i64 = bgw_inject(rbody, rblen, bgprof, injbuf, injcap) 581 page.raw = injbuf; page.raw_len = injlen 582 return br_layout(page, vw) 583} 584 585// one toolbar button: a bordered face with a centered single-char label 586func br_button(c: *X11Conn, wid: i64, gc: i64, x: i64, label: *u8, fbody: i64) -> i64 { 587 nx_x11_set_gc_foreground(c, gc, 0x00B0B4BA); nx_x11_fill_rect(c, wid, gc, x, 8, 30, 28) // border 588 nx_x11_set_gc_foreground(c, gc, 0x00F6F8FA); nx_x11_fill_rect(c, wid, gc, x + 1, 9, 28, 26) // face 589 nx_x11_set_gc_font(c, gc, fbody); nx_x11_set_gc_foreground(c, gc, 0x00202124) 590 nx_x11_poly_text8(c, wid, gc, x + 11, 27, label, 1) 591 return 0 592} 593 594// the browser chrome: toolbar background + Back/Forward/Reload buttons + the address bar (URL display) 595func br_chrome(c: *X11Conn, wid: i64, gc: i64, win_w: i64, cur_url: *u8, ulen: i64, fbody: i64) -> i64 { 596 nx_x11_set_gc_foreground(c, gc, 0x00DDE1E6); nx_x11_fill_rect(c, wid, gc, 0, 0, win_w, NX_CHROME_H) 597 nx_x11_set_gc_foreground(c, gc, 0x00B0B4BA); nx_x11_fill_rect(c, wid, gc, 0, NX_CHROME_H - 1, win_w, 1) 598 br_button(c, wid, gc, 8, "<\x00" as *u8, fbody) 599 br_button(c, wid, gc, 44, ">\x00" as *u8, fbody) 600 br_button(c, wid, gc, 80, "R\x00" as *u8, fbody) 601 // address bar field (bordered white box) + the current URL 602 let bx: i64 = 118 603 let bw: i64 = win_w - bx - 10 604 nx_x11_set_gc_foreground(c, gc, 0x00B0B4BA); nx_x11_fill_rect(c, wid, gc, bx, 8, bw, 28) 605 nx_x11_set_gc_foreground(c, gc, 0x00FFFFFF); nx_x11_fill_rect(c, wid, gc, bx + 1, 9, bw - 2, 26) 606 var maxc: i64 = (bw - 14) / 7 607 if maxc < 0 { maxc = 0 } 608 var n: i64 = ulen 609 if n > maxc { n = maxc } 610 if n > 250 { n = 250 } 611 nx_x11_set_gc_font(c, gc, fbody); nx_x11_set_gc_foreground(c, gc, 0x00202124) 612 if n > 0 { nx_x11_poly_text8(c, wid, gc, bx + 7, 27, cur_url, n) } 613 return 0 614} 615 616// chrome hit-test: 1=Back, 2=Forward, 3=Reload, 0=none (call only when click y < NX_CHROME_H) 617func br_chrome_click(ex: i64) -> i64 { 618 if ex >= 8 { if ex < 38 { return 1 } } 619 if ex >= 44 { if ex < 74 { return 2 } } 620 if ex >= 80 { if ex < 110 { return 3 } } 621 return 0 622} 623 624// INHERITED color resolve (CSS: `color` inherits) -- twin of nx_browser_render.nx's helper; walk up the 625// parent chain to the nearest computed color decl (author body/.doc colors now reach p/li/td text). 626func br_comp_color_inh(src: *u8, computed: *CssComputedDecl, ncomp: i64, tree: *LayoutTree, box_idx: i64) -> i64 { 627 var cur: i64 = box_idx 628 var guard: i64 = 0 629 while cur >= 0 { 630 if guard > 64 { return 0 - 1 } 631 guard = guard + 1 632 let c: i64 = br_comp_color(src, computed, ncomp, cur, "color\x00" as *u8, 5) 633 if c >= 0 { return c } 634 let b: *LayoutBox = ((tree.boxes as i64) + cur * NX_LAYOUT_BOX_BYTES) as *LayoutBox 635 cur = b.parent_idx 636 } 637 return 0 - 1 638} 639 640// background-color, else the `background` shorthand's single-hex value (twin of nx_browser_render.nx). 641func br_comp_bg(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64) -> i64 { 642 let c: i64 = br_comp_color(src, computed, ncomp, box_idx, "background-color\x00" as *u8, 16) 643 if c >= 0 { return c } 644 return br_comp_color(src, computed, ncomp, box_idx, "background\x00" as *u8, 10) 645} 646 647// `border` shorthand: parse "<w>px ... #rrggbb" from the swept value span (twin of nx_browser_render.nx). 648func br_comp_border(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, wout: *i64) -> i64 { 649 var j: i64 = ncomp - 1 650 while j >= 0 { 651 let cd: *CssComputedDecl = ((computed as i64) + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 652 if cd.element_idx == box_idx { if cd.prop_len == 6 { 653 let P: *u8 = "border\x00" as *u8 654 var ok: i64 = 1 655 var k: i64 = 0 656 while k < 6 { if (src[cd.prop_off+k]&0xff) != (P[k]&0xff) { ok=0; k=6 } else { k=k+1 } } 657 if ok == 1 { 658 var wv: i64 = 0 659 var i2: i64 = cd.val_off 660 let e2: i64 = cd.val_off + cd.val_len 661 while i2 < e2 { let ch: i64 = src[i2]&0xff; if ch>=48 { if ch<=57 { wv=wv*10+(ch-48); i2=i2+1 } else { i2=e2 } } else { i2=e2 } } 662 if wv <= 0 { wv = 1 } 663 if wv > 8 { wv = 8 } 664 var hp: i64 = 0 - 1 665 var i3: i64 = cd.val_off 666 while i3 < e2 { if src[i3]==(35 as u8) { hp=i3; i3=e2 } else { i3=i3+1 } } 667 if hp >= 0 { 668 let col: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor 669 if nx_css_color_decode(src, hp+1, 6, col) == 1 { 670 wout[0] = wv 671 return (col.r << 16) | (col.g << 8) | col.b 672 } 673 } 674 return 0 - 1 675 } 676 } } 677 j = j - 1 678 } 679 return 0 - 1 680} 681 682func br_draw(c: *X11Conn, wid: i64, gc: i64, paper: i64, page: *Page, win_w: i64, win_h: i64, scroll_y: i64, 683 fbody: i64, fh2: i64, fh1: i64, cur_url: *u8, ulen: i64) -> i64 { 684 let tree: *LayoutTree = page.tree 685 let src: *u8 = page.buf 686 let computed: *CssComputedDecl = page.computed 687 let ncomp: i64 = page.ncomp 688 let decbuf: *u8 = sys_mmap(1100) // reused per text box: decode HTML entities before painting 689 nx_x11_fill_rect(c, wid, paper, 0, 0, win_w, win_h) 690 var i: i64 = 0 691 let bwp: *i64 = sys_mmap(16) as *i64 // border width out-slot (hoisted: no mmap in the loop) 692 while i < tree.count { 693 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 694 if b.kind == NX_LAYOUT_BOX_BLOCK { if b.w > 0 { if b.h > 0 { 695 let bg: i64 = br_comp_bg(src, computed, ncomp, i) 696 if bg >= 0 { if bg != 0x00FFFFFF { 697 let sy: i64 = b.y - scroll_y + NX_CHROME_H 698 if sy + b.h >= NX_CHROME_H { if sy < win_h { nx_x11_set_gc_foreground(c, gc, bg); nx_x11_fill_rect(c, wid, gc, b.x, sy, b.w, b.h) } } 699 } } 700 // CSS borders (the `border` shorthand): stroke the 4 edges 701 let bc: i64 = br_comp_border(src, computed, ncomp, i, bwp) 702 if bc >= 0 { 703 let sy3: i64 = b.y - scroll_y + NX_CHROME_H 704 if sy3 + b.h >= NX_CHROME_H { if sy3 < win_h { 705 let bw2: i64 = bwp[0] 706 nx_x11_set_gc_foreground(c, gc, bc) 707 nx_x11_fill_rect(c, wid, gc, b.x, sy3, b.w, bw2) 708 nx_x11_fill_rect(c, wid, gc, b.x, sy3 + b.h - bw2, b.w, bw2) 709 nx_x11_fill_rect(c, wid, gc, b.x, sy3, bw2, b.h) 710 nx_x11_fill_rect(c, wid, gc, b.x + b.w - bw2, sy3, bw2, b.h) 711 } } 712 } 713 } } } 714 i = i + 1 715 } 716 i = 0 717 while i < tree.count { 718 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 719 if b.kind == NX_LAYOUT_BOX_TEXT { if b.text_len > 0 { 720 let sy: i64 = b.y - scroll_y + NX_CHROME_H 721 if sy >= NX_CHROME_H - 40 { if sy < win_h { 722 let par: i64 = b.parent_idx 723 let fs: i64 = br_comp_int(src, computed, ncomp, par, "font-size\x00" as *u8, 9, 16) 724 var fid: i64 = fbody; var asc: i64 = 12 725 if fs > 18 { fid = fh2; asc = 16 } 726 if fs > 28 { fid = fh1; asc = 19 } 727 var col: i64 = br_comp_color_inh(src, computed, ncomp, tree, par) // CSS-inherited color 728 if col < 0 { col = 0 } 729 nx_x11_set_gc_font(c, gc, fid); nx_x11_set_gc_foreground(c, gc, col) 730 var nn: i64 = b.text_len 731 if nn > 200 { nn = 200 } 732 let dn: i64 = nx_html_decode_entities(src, b.text_off, nn, decbuf, 1024) 733 nx_x11_poly_text8(c, wid, gc, b.x + 4, sy + asc, decbuf, dn) 734 if col == 0x3366cc { nx_x11_fill_rect(c, wid, gc, b.x + 4, sy + asc + 2, b.w, 1) } // underline links 735 } } 736 } } 737 i = i + 1 738 } 739 br_chrome(c, wid, gc, win_w, cur_url, ulen, fbody) 740 nx_x11_flush_via_get_input_focus(c) 741 return 0 742} 743 744func br_color_from_int(c: i64) -> *CssColor { 745 let col: *CssColor = sys_mmap(NX_CSS_COLOR_BYTES) as *CssColor 746 col.r = (c >> 16) & 0xff 747 col.g = (c >> 8) & 0xff 748 col.b = c & 0xff 749 col.a = 255 750 return col 751} 752 753// 2x-scaled 5x7 text for the SHOT framebuffer (each glyph pixel -> 2x2 block = 10x14 glyph, 9px advance) 754// so the capture ~matches the live window's 9x15 font + the 9px layout metric (faithful self-verify). 755func br_paint_text2x(fb: *Framebuffer, x: i64, y: i64, text: *u8, text_len: i64, color: *CssColor) -> i64 { 756 var i: i64 = 0 757 while i < text_len { 758 let ch: i64 = text[i] & 0xff 759 let cx: i64 = x + i * 9 760 var row: i64 = 0 761 while row < 7 { 762 let bits: i64 = nx_font_5x7_glyph_row(ch, row) 763 if bits >= 0 { 764 var col: i64 = 0 765 while col < 5 { 766 if ((bits >> (4 - col)) & 1) == 1 { nx_paint_solid_rect(fb, cx + col * 2, y + row * 2, 2, 2, color) } 767 col = col + 1 768 } 769 } 770 row = row + 1 771 } 772 i = i + 1 773 } 774 return 0 775} 776 777// OFF-SCREEN render: rasterize the same page into an in-memory RGBA framebuffer (no X server) so the 778// render can be captured to a PNG and self-reviewed. Mirrors br_draw; uses the 5x7 bitmap-font paint 779// primitives (so headings aren't scaled here -- this is for verifying colors/layout/chrome, not pixel-parity). 780func br_draw_fb(fb: *Framebuffer, page: *Page, win_w: i64, cur_url: *u8, ulen: i64) -> i64 { 781 let tree: *LayoutTree = page.tree 782 let src: *u8 = page.buf 783 let computed: *CssComputedDecl = page.computed 784 let ncomp: i64 = page.ncomp 785 let fdec: *u8 = sys_mmap(1100) // reused per text box: decode HTML entities before painting 786 let ffold: *u8 = sys_mmap(1100) // reused: fold decoded UTF-8 -> ASCII (accents+punct) for the 5x7 font 787 nx_paint_solid_rect(fb, 0, 0, win_w, fb.height, br_color_from_int(0x00FFFFFF)) 788 var i: i64 = 0 789 let bwp2: *i64 = sys_mmap(16) as *i64 // border width out-slot (hoisted: no mmap in the loop) 790 while i < tree.count { 791 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 792 if b.kind == NX_LAYOUT_BOX_BLOCK { if b.w > 0 { if b.h > 0 { 793 let bg: i64 = br_comp_bg(src, computed, ncomp, i) 794 if bg >= 0 { if bg != 0x00FFFFFF { nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H, b.w, b.h, br_color_from_int(bg)) } } 795 // CSS borders (the `border` shorthand): stroke the 4 edges 796 let bc: i64 = br_comp_border(src, computed, ncomp, i, bwp2) 797 if bc >= 0 { 798 let bw2: i64 = bwp2[0] 799 nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H, b.w, bw2, br_color_from_int(bc)) 800 nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H + b.h - bw2, b.w, bw2, br_color_from_int(bc)) 801 nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H, bw2, b.h, br_color_from_int(bc)) 802 nx_paint_solid_rect(fb, b.x + b.w - bw2, b.y + NX_CHROME_H, bw2, b.h, br_color_from_int(bc)) 803 } 804 } } } 805 i = i + 1 806 } 807 i = 0 808 while i < tree.count { 809 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 810 if b.kind == NX_LAYOUT_BOX_TEXT { if b.text_len > 0 { 811 let par: i64 = b.parent_idx 812 var col: i64 = br_comp_color_inh(src, computed, ncomp, tree, par) // CSS-inherited color 813 if col < 0 { col = 0 } 814 var nn: i64 = b.text_len 815 if nn > 200 { nn = 200 } 816 let dn: i64 = nx_html_decode_entities(src, b.text_off, nn, fdec, 1024) 817 let fdn: i64 = nx_fold_ascii(fdec, 0, dn, ffold, 1100) // UTF-8 -> ASCII: punctuation + accents render, non-Latin skips cleanly (no blank gaps) 818 br_paint_text2x(fb, b.x + 4, b.y + NX_CHROME_H + 1, ffold, fdn, br_color_from_int(col)) 819 if col == 0x3366cc { nx_paint_solid_rect(fb, b.x + 4, b.y + NX_CHROME_H + 16, b.w, 1, br_color_from_int(col)) } // underline links 820 } } 821 i = i + 1 822 } 823 // chrome 824 nx_paint_solid_rect(fb, 0, 0, win_w, NX_CHROME_H, br_color_from_int(0x00DDE1E6)) 825 nx_paint_solid_rect(fb, 0, NX_CHROME_H - 1, win_w, 1, br_color_from_int(0x00B0B4BA)) 826 nx_paint_solid_rect(fb, 8, 8, 30, 28, br_color_from_int(0x00F6F8FA)) 827 nx_paint_solid_rect(fb, 44, 8, 30, 28, br_color_from_int(0x00F6F8FA)) 828 nx_paint_solid_rect(fb, 80, 8, 30, 28, br_color_from_int(0x00F6F8FA)) 829 br_paint_text2x(fb, 18, 16, "<\x00" as *u8, 1, br_color_from_int(0x00202124)) 830 br_paint_text2x(fb, 54, 16, ">\x00" as *u8, 1, br_color_from_int(0x00202124)) 831 br_paint_text2x(fb, 90, 16, "R\x00" as *u8, 1, br_color_from_int(0x00202124)) 832 let bx: i64 = 118 833 nx_paint_solid_rect(fb, bx, 8, win_w - bx - 10, 28, br_color_from_int(0x00FFFFFF)) 834 var un: i64 = ulen 835 if un > 96 { un = 96 } 836 br_paint_text2x(fb, bx + 6, 14, cur_url, un, br_color_from_int(0x00202124)) 837 return 0 838} 839 840// render the laid-out page to a 1000xSH RGBA framebuffer and write it as a PNG at `path`. SH tracks the FULL 841// page height (chrome + laid-out content) so a self-verify shot captures the whole page, not a fixed top crop 842// (the 1000px crop made tall pages -- e.g. /office at 2765px -- look empty below the fold). Clamped for safety. 843func br_shot_png(page: *Page, cur_url: *u8, ulen: i64, path: *u8) -> i64 { 844 let SW: i64 = 1000 845 var SH: i64 = page.page_h + NX_CHROME_H + 40 846 if SH < 400 { SH = 400 } 847 if SH > 6000 { SH = 6000 } 848 let fb: *Framebuffer = sys_mmap(NX_FRAMEBUFFER_BYTES) as *Framebuffer 849 let px: *u8 = sys_mmap(SW * SH * 4 + 64) 850 nx_framebuffer_init(fb, px, SW, SH) 851 br_draw_fb(fb, page, SW, cur_url, ulen) 852 let rgb: *u8 = sys_mmap(SW * SH * 3 + 64) 853 var p: i64 = 0 854 while p < SW * SH { 855 rgb[p * 3 + 0] = px[p * 4 + 0] 856 rgb[p * 3 + 1] = px[p * 4 + 1] 857 rgb[p * 3 + 2] = px[p * 4 + 2] 858 p = p + 1 859 } 860 nx_png_write_rgb(path, rgb, SW, SH) 861 return 0 862} 863 864// hit-test: find a box with an href whose rect contains doc-point (dx,dy). 1 + sets off/len, else 0. 865// CONTAINER ANCHORS (2026-07-03): an <a> wrapping block children lays out with a ZERO own-rect -- 866// hit-test the UNION of its descendants' rects (mirrors nx_browser_render.nx; found by 867// nx_aw_browser_gate on the real andelinwest card links). Purely additive. 868func br_hit_link(page: *Page, dx: i64, dy: i64, off: *i64, len: *i64) -> i64 { 869 let tree: *LayoutTree = page.tree 870 var i: i64 = 0 871 while i < tree.count { 872 if page.bhref_off[i] >= 0 { 873 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 874 var bx: i64 = b.x 875 var by: i64 = b.y 876 var bw: i64 = b.w 877 var bh: i64 = b.h 878 if bh <= 0 { 879 // SIBLING-SPAN effective rect (mirrors nx_browser_render.nx): union the rects of the 880 // boxes AFTER i while they stay inside i's parent's subtree, stopping at the next 881 // href box -- the layout flattens container-anchor children to siblings. 882 let par: i64 = b.parent_idx 883 var x0: i64 = 0 884 var y0: i64 = 0 885 var x1: i64 = 0 886 var y1: i64 = 0 887 var any: i64 = 0 888 var j: i64 = i + 1 889 var span: i64 = 1 890 while span == 1 { 891 if j >= tree.count { span = 0 } 892 else { if page.bhref_off[j] >= 0 { span = 0 } 893 else { 894 let cb: *LayoutBox = ((tree.boxes as i64) + j * NX_LAYOUT_BOX_BYTES) as *LayoutBox 895 var p: i64 = cb.parent_idx 896 var hop: i64 = 0 897 var inside: i64 = 0 898 while hop < 64 { 899 if p == (0 - 1) { hop = 64 } 900 else { if p == par { inside = 1; hop = 64 } 901 else { let pb: *LayoutBox = ((tree.boxes as i64) + p * NX_LAYOUT_BOX_BYTES) as *LayoutBox; p = pb.parent_idx; hop = hop + 1 } } 902 } 903 if inside == 0 { span = 0 } 904 else { 905 if cb.w > 0 { if cb.h > 0 { 906 if any == 0 { x0 = cb.x; y0 = cb.y; x1 = cb.x + cb.w; y1 = cb.y + cb.h; any = 1 } 907 else { 908 if cb.x < x0 { x0 = cb.x } 909 if cb.y < y0 { y0 = cb.y } 910 if cb.x + cb.w > x1 { x1 = cb.x + cb.w } 911 if cb.y + cb.h > y1 { y1 = cb.y + cb.h } 912 } 913 } } 914 j = j + 1 915 } 916 } } 917 } 918 if any == 1 { bx = x0; by = y0; bw = x1 - x0; bh = y1 - y0 } 919 } 920 var hitw: i64 = bw // layout metric now matches the 9px render font, so b.w is the true width 921 if hitw < 12 { hitw = 12 } 922 if dx >= bx { if dx < bx + hitw { if dy >= by { if dy < by + bh { 923 off[0] = page.bhref_off[i]; len[0] = page.bhref_len[i]; return 1 924 } } } } 925 } 926 i = i + 1 927 } 928 return 0 929} 930 931// ---- sovereign-game pages (nishi-game marker) -> run the NATIVE client, zero wasm ---- 932// Extract the `<meta name="nishi-game" content="...">` organ name from raw page bytes. 933// SECURITY (rule 12): the value is PAGE-CONTROLLED and becomes an exec path, so this is strict 934// fail-closed validation, not parsing: the name must be nx_[a-z0-9_]{1,60} and anything else -- 935// a slash, a dot, an uppercase byte, overlength -- REJECTS the whole marker (no truncate-accept). 936func br_game_organ(raw: *u8, n: i64, out: *u8) -> i64 { 937 let pat: *u8 = "name=\"nishi-game\"\x00" as *u8 938 let cpat: *u8 = "content=\"\x00" as *u8 939 var i: i64 = 0 940 while i < n - 17 { 941 var m: i64 = 0 942 while m < 17 { if (raw[i+m] & 0xff) != (pat[m] & 0xff) { m = 99 } else { m = m + 1 } } 943 if m == 17 { 944 // find content=" within the next 80 bytes 945 var j: i64 = i + 17 946 let jend: i64 = i + 97 947 while j < jend { 948 if j < n - 9 { 949 var c: i64 = 0 950 while c < 9 { if (raw[j+c] & 0xff) != (cpat[c] & 0xff) { c = 99 } else { c = c + 1 } } 951 if c == 9 { 952 var k: i64 = 0 953 var p: i64 = j + 9 954 var badc: i64 = 0 955 var closed: i64 = 0 956 while closed == 0 { 957 if p >= n { badc = 1; closed = 1 } 958 if closed == 0 { 959 let ch: i64 = raw[p] & 0xff 960 if ch == 34 { closed = 1 } 961 if closed == 0 { 962 var okc: i64 = 0 963 if ch >= 97 { if ch <= 122 { okc = 1 } } 964 if ch >= 48 { if ch <= 57 { okc = 1 } } 965 if ch == 95 { okc = 1 } 966 if okc == 0 { badc = 1 } 967 if k >= 60 { badc = 1 } 968 if badc == 0 { out[k] = ch as u8; k = k + 1 } 969 if badc == 1 { closed = 1 } 970 p = p + 1 971 } 972 } 973 } 974 if badc == 1 { return 0 } 975 if k < 4 { return 0 } 976 if (out[0] & 0xff) != 110 { return 0 } // n 977 if (out[1] & 0xff) != 120 { return 0 } // x 978 if (out[2] & 0xff) != 95 { return 0 } // _ 979 out[k] = 0 as u8 980 return k 981 } 982 } 983 j = j + 1 984 } 985 return 0 986 } 987 i = i + 1 988 } 989 return 0 990} 991 992// fork+exec the native client `_build/<name>.sov.elf` (name pre-validated by br_game_organ). 993// selftest=1: pass "selftest" and WAIT, returning the child's exit code (the chain proof). 994// selftest=0: fire-and-forget (the game gets its own window; zombie reaped at browser exit). 995func br_launch_game(name: *u8, nlen: i64, selftest: i64) -> i64 { 996 let path: *u8 = sys_mmap(128) 997 let pre: *u8 = "_build/\x00" as *u8 998 var k: i64 = 0 999 while k < 7 { path[k] = pre[k]; k = k + 1 } 1000 var q: i64 = 0 1001 while q < nlen { path[7+q] = name[q]; q = q + 1 } 1002 let suf: *u8 = ".sov.elf\x00" as *u8 1003 var t: i64 = 0 1004 while t < 9 { path[7+nlen+t] = suf[t]; t = t + 1 } 1005 let av: *i64 = sys_mmap(32) as *i64 1006 av[0] = path as i64 1007 av[1] = 0 1008 av[2] = 0 1009 if selftest == 1 { av[1] = ("selftest\x00" as *u8) as i64 } 1010 let ep: *i64 = sys_mmap(16) as *i64 1011 ep[0] = 0 1012 let pid: i64 = sys_fork() 1013 if pid == 0 { 1014 sys_execve(path, av, ep) 1015 sys_exit(127) 1016 } 1017 if pid < 0 { return 0 - 1 } 1018 if selftest == 1 { 1019 let st: *i64 = sys_mmap(16) as *i64 1020 sys_wait4(pid, st, 0) 1021 return (st[0] >> 8) & 0xff 1022 } 1023 return 0 1024} 1025 1026func br_seq(a: *u8, b: *u8) -> i64 { 1027 var i: i64 = 0 1028 while 1 == 1 { 1029 let ca: i64 = a[i] & 0xff 1030 let cb: i64 = b[i] & 0xff 1031 if ca != cb { return 0 } 1032 if ca == 0 { return 1 } 1033 i = i + 1 1034 } 1035 return 1 1036} 1037 1038func main(argc: i64, argv: *i64) -> i64 { 1039 nx_signal_ignore(NX_SIGPIPE) // a server reset mid-write must NOT kill us (was exit 141); fetch fails gracefully 1040 1041 // ---- HEADLESS SHOT, ADDED 2026-08-26: nx_browser shot <html-file> <png-out> [viewport-px] ---- 1042 // Renders a LOCAL FILE through the FULL pipeline -- UA defaults + author <style> + @media filtered 1043 // at the REAL viewport width -> DOM -> layout tree -> cascade -> block layout -> painted 1044 // framebuffer -> PNG. It returns BEFORE _url.txt, the CA bundle and X11 are touched, so it runs on 1045 // a headless NAS where the windowed browser structurally cannot. 1046 // 1047 // WHY THIS AND NOT THE TEXT RENDER TEST: nx_pagerender_lib proves text extraction and paints ONE 1048 // line -- useful, and honest about its scope. It does NOT run the cascade or lay out a single box, 1049 // so it cannot see a stylesheet that fails to apply, a box tree that collapses, or a page that lays 1050 // out to zero height. The render test's own header flagged full CSS-cascade layout as "the NEXT 1051 // rung -- flagged, not claimed". This is that rung, and it REPORTS THE LAYOUT NUMBERS (boxes, 1052 // computed declarations, laid-out page height) rather than a pass bit, because a boolean cannot 1053 // tell you that a page rendered to 40 boxes when it used to render to 400. 1054 // Called with no arguments the browser behaves EXACTLY as before (rule 19: add, never break). 1055 if argc >= 4 { 1056 if br_seq(argv[1] as *u8, "shot\x00" as *u8) == 1 { 1057 let hp: *u8 = argv[2] as *u8 1058 let op: *u8 = argv[3] as *u8 1059 var vw: i64 = 1000 1060 if argc >= 5 { 1061 let vs: *u8 = argv[4] as *u8 1062 var vv: i64 = 0 1063 var vi: i64 = 0 1064 var vbad: i64 = 0 1065 while vs[vi] != (0 as u8) { 1066 let c: i64 = vs[vi] & 0xff 1067 if c < 48 { vbad = 1 } 1068 if c > 57 { vbad = 1 } 1069 if vbad == 0 { vv = vv * 10 + (c - 48) } 1070 vi = vi + 1 1071 } 1072 // A NON-NUMERIC VIEWPORT IS REFUSED, NOT SILENTLY READ AS 1000. Quietly defaulting would 1073 // lay the page out at a width nobody asked for and report it as if it were the request. 1074 if vbad == 1 { br_p(2, "nx_browser shot: viewport must be numeric\n\x00" as *u8); return 2 } 1075 vw = vv 1076 } 1077 let flp: *i64 = sys_mmap(16) as *i64 1078 let fbody: *u8 = sys_read_file(hp, flp) 1079 if (fbody as i64) == 0 { 1080 br_p(2, "nx_browser shot: cannot read \x00" as *u8); br_p(2, hp); br_p(2, "\n\x00" as *u8) 1081 return 4 1082 } 1083 let spage: *Page = sys_mmap(NX_PAGE_BYTES) as *Page 1084 spage.raw = fbody 1085 spage.raw_len = flp[0] 1086 br_layout(spage, vw) 1087 if spage.ok == 0 { br_p(2, "nx_browser shot: LAYOUT FAILED -- no PNG written\n\x00" as *u8); return 5 } 1088 let stree: *LayoutTree = spage.tree 1089 // A PAGE THAT LAYS OUT TO ZERO HEIGHT OR ZERO BOXES IS A FAILED RENDER THAT WOULD OTHERWISE 1090 // WRITE A CLEAN-LOOKING BLANK PNG. Refuse before writing, so a blank artifact can never be 1091 // mistaken for a successful screenshot. 1092 if stree.count <= 0 { br_p(2, "nx_browser shot: ZERO BOXES -- refusing to write a blank PNG\n\x00" as *u8); return 6 } 1093 if spage.page_h <= 0 { br_p(2, "nx_browser shot: ZERO page height -- refusing to write a blank PNG\n\x00" as *u8); return 7 } 1094 // A PNG OUT-PATH OF "-" MEANS METRICS ONLY, AND IT IS NOT AN OPTIMISATION -- IT IS A 1095 // RESOURCE FACT. nx_png_write_rgb emits essentially UNCOMPRESSED PNG: this page produced 1096 // 18,007,438 bytes for one 1000x6000 shot (measured 2026-08-26), so a sweep over the 84 1097 // published boards would write roughly 1.5 GB to the array for a regression check whose 1098 // ACTUAL signal is three integers. Hammering the disk is a defect even when the feature 1099 // works, so the fleet path takes the numbers and the image stays on demand. 1100 // The layout still runs IN FULL either way -- nothing is skipped except the write. 1101 if br_seq(op, "-\x00" as *u8) == 0 { br_shot_png(spage, hp, br_slen(hp), op) } 1102 br_p(1, "BROWSER-SHOT ok html=\x00" as *u8); br_n(1, spage.raw_len) 1103 br_p(1, "B boxes=\x00" as *u8); br_n(1, stree.count) 1104 br_p(1, " computed_decls=\x00" as *u8); br_n(1, spage.ncomp) 1105 br_p(1, " page_h=\x00" as *u8); br_n(1, spage.page_h) 1106 br_p(1, "px vw=\x00" as *u8); br_n(1, vw) 1107 br_p(1, " -> \x00" as *u8); br_p(1, op); br_p(1, "\n\x00" as *u8) 1108 return 0 1109 } 1110 } 1111 1112 // ---- LAYOUT SWEEP, ADDED 2026-08-27: nx_browser layoutsweep <domain-list> ---- 1113 // Lays out EVERY published /compare board through the same br_layout the windowed browser uses -- 1114 // full cascade, real box tree, real block layout -- and prints the NUMBERS per board plus a 1115 // partition that SUMS. Two boards laid out is a spot check; this is the population. 1116 // NO PNG IS WRITTEN: nx_png_write_rgb emits essentially uncompressed PNG (18,007,438 bytes for one 1117 // 1000x6000 shot, measured 2026-08-26), so imaging 84 boards would put ~1.5 GB on the array for a 1118 // check whose real signal is three integers per row. 1119 // ALLOCATION PROFILE, NAMED RATHER THAN DISCOVERED AS AN OOM: br_layout mmaps a fresh 16384-box 1120 // tree, a 256 KB style buffer and a computed-decl arena PER CALL and frees none of it, so this loop 1121 // accumulates address space linearly in the number of boards. mmap faults pages in on demand so the 1122 // RESIDENT cost is far below the address-space cost, and the SITETEST batch in this same organ 1123 // already loops layouts in one process -- but if this ever sweeps thousands of pages rather than 1124 // dozens, the fix is a per-iteration arena reset, not a bigger box. 1125 if argc >= 3 { 1126 if br_seq(argv[1] as *u8, "layoutsweep\x00" as *u8) == 1 { 1127 let lpath: *u8 = argv[2] as *u8 1128 let llp: *i64 = sys_mmap(16) as *i64 1129 let lbuf: *u8 = sys_read_file(lpath, llp) 1130 if (lbuf as i64) == 0 { 1131 br_p(2, "nx_browser layoutsweep: cannot read \x00" as *u8); br_p(2, lpath); br_p(2, "\n\x00" as *u8) 1132 return 4 1133 } 1134 let ln: i64 = llp[0] 1135 br_p(1, "=== NX-BROWSER LAYOUT SWEEP -- every published board through the real cascade ===\n\x00" as *u8) 1136 let dpath: *u8 = sys_mmap(BR_PATH_MAX) 1137 let dname: *u8 = sys_mmap(BR_PATH_MAX) 1138 var listed: i64 = 0 1139 var okc: i64 = 0 1140 var absent: i64 = 0 1141 var badc: i64 = 0 1142 var min_boxes: i64 = 0 - 1 1143 var min_h: i64 = 0 - 1 1144 var sum_boxes: i64 = 0 1145 var sum_decls: i64 = 0 1146 var lp2: i64 = 0 1147 while lp2 < ln { 1148 var lq: i64 = lp2 1149 var lsc: i64 = 1 1150 while lsc == 1 { 1151 if lq >= ln { lsc = 0 } else { if lbuf[lq] == (BR_ASCII_NL as u8) { lsc = 0 } else { lq = lq + 1 } } 1152 } 1153 var dl: i64 = 0 1154 var di: i64 = lp2 1155 while di < lq { 1156 if lbuf[di] != (BR_ASCII_CR as u8) { if dl < (BR_PATH_MAX - 1) { dname[dl] = lbuf[di]; dl = dl + 1 } } 1157 di = di + 1 1158 } 1159 dname[dl] = 0 as u8 1160 var use: i64 = 1 1161 if dl == 0 { use = 0 } 1162 if dl > 0 { if dname[0] == (BR_ASCII_HASH as u8) { use = 0 } } 1163 if use == 1 { 1164 listed = listed + 1 1165 var dp: i64 = 0 1166 let pre: *u8 = "sites/nishifamily/compare/\x00" as *u8 1167 var pi2: i64 = 0 1168 while pre[pi2] != (0 as u8) { dpath[dp] = pre[pi2]; dp = dp + 1; pi2 = pi2 + 1 } 1169 var di2: i64 = 0 1170 while dname[di2] != (0 as u8) { dpath[dp] = dname[di2]; dp = dp + 1; di2 = di2 + 1 } 1171 let suf: *u8 = "/index.html\x00" as *u8 1172 var si2: i64 = 0 1173 while suf[si2] != (0 as u8) { dpath[dp] = suf[si2]; dp = dp + 1; si2 = si2 + 1 } 1174 dpath[dp] = 0 as u8 1175 let dlp: *i64 = sys_mmap(16) as *i64 1176 let dbody: *u8 = sys_read_file(dpath, dlp) 1177 br_p(1, " \x00" as *u8); br_p(1, dname) 1178 var padn: i64 = dl 1179 while padn < BR_NAME_COL { br_p(1, " \x00" as *u8); padn = padn + 1 } 1180 if (dbody as i64) == 0 { 1181 absent = absent + 1 1182 br_p(1, "SKIP-absent\n\x00" as *u8) 1183 } else { 1184 let dpg: *Page = sys_mmap(NX_PAGE_BYTES) as *Page 1185 dpg.raw = dbody 1186 dpg.raw_len = dlp[0] 1187 br_layout(dpg, BR_DEFAULT_VW) 1188 if dpg.ok == 0 { 1189 badc = badc + 1 1190 br_p(1, "FAIL-layout\n\x00" as *u8) 1191 } else { 1192 let dt: *LayoutTree = dpg.tree 1193 if dt.count <= 0 { badc = badc + 1; br_p(1, "FAIL-zero-boxes\n\x00" as *u8) } 1194 else { 1195 if dpg.page_h <= 0 { badc = badc + 1; br_p(1, "FAIL-zero-height\n\x00" as *u8) } 1196 else { 1197 okc = okc + 1 1198 sum_boxes = sum_boxes + dt.count 1199 sum_decls = sum_decls + dpg.ncomp 1200 if min_boxes < 0 { min_boxes = dt.count } 1201 if dt.count < min_boxes { min_boxes = dt.count } 1202 if min_h < 0 { min_h = dpg.page_h } 1203 if dpg.page_h < min_h { min_h = dpg.page_h } 1204 br_p(1, "OK boxes=\x00" as *u8); br_n(1, dt.count) 1205 br_p(1, " decls=\x00" as *u8); br_n(1, dpg.ncomp) 1206 br_p(1, " page_h=\x00" as *u8); br_n(1, dpg.page_h) 1207 br_p(1, "px\n\x00" as *u8) 1208 } 1209 } 1210 } 1211 } 1212 } 1213 lp2 = lq + 1 1214 } 1215 let tot: i64 = okc + absent + badc 1216 br_p(1, "\nboards_listed=\x00" as *u8); br_n(1, listed) 1217 br_p(1, " laid_out=\x00" as *u8); br_n(1, okc) 1218 br_p(1, " skipped=\x00" as *u8); br_n(1, absent) 1219 br_p(1, " failed=\x00" as *u8); br_n(1, badc) 1220 br_p(1, " sum=\x00" as *u8); br_n(1, tot) 1221 if tot == listed { br_p(1, " partition=RECONCILES\n\x00" as *u8) } else { br_p(1, " partition=LEAK\n\x00" as *u8) } 1222 br_p(1, "total_boxes=\x00" as *u8); br_n(1, sum_boxes) 1223 br_p(1, " total_computed_decls=\x00" as *u8); br_n(1, sum_decls) 1224 br_p(1, " WORST_boxes=\x00" as *u8); br_n(1, min_boxes) 1225 br_p(1, " WORST_page_h=\x00" as *u8); br_n(1, min_h) 1226 br_p(1, "px\n\nWORST IS PRINTED, NEVER THE MEAN: an average hides the one board that laid out to almost nothing.\n\x00" as *u8) 1227 if tot != listed { return 1 } 1228 if badc > 0 { return 1 } 1229 return 0 1230 } 1231 } 1232 1233 let lp: *i64 = sys_mmap(16) as *i64 1234 let urlraw: *u8 = sys_read_file("_url.txt\x00" as *u8, lp) 1235 var ulen: i64 = *lp 1236 if ulen <= 0 { br_p(2, "nx_browser: no _url.txt\n\x00" as *u8); return 1 } 1237 var trimming: i64 = 1 1238 while trimming == 1 { if ulen <= 0 { trimming = 0 } else { let ch: i64 = urlraw[ulen-1]&0xff; if ch==10 { ulen=ulen-1 } else { if ch==13 { ulen=ulen-1 } else { if ch==32 { ulen=ulen-1 } else { if ch==9 { ulen=ulen-1 } else { trimming=0 } } } } } } 1239 let URLCAP: i64 = 4096 1240 let cur_url: *u8 = sys_mmap(URLCAP) 1241 var u: i64 = 0 1242 while u < ulen { cur_url[u]=urlraw[u]; u=u+1 } 1243 cur_url[ulen] = 0 as u8 1244 1245 var tr: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt\x00" as *u8, 300, 4194304) 1246 if tr <= 0 { tr = nx_trust_store_load_from_certdata("/tmp/mozilla_certdata.txt\x00" as *u8, 300, 4194304) } 1247 if tr <= 0 { br_p(2, "nx_browser: CA bundle load failed\n\x00" as *u8); return 3 } 1248 let store: *TrustStore = tr as *TrustStore 1249 let cr: *u8 = sys_mmap(32); let priv: *u8 = sys_mmap(32) 1250 let ufd: i64 = sys_openat_rd("/dev/urandom\x00" as *u8) 1251 if ufd >= 0 { sys_read(ufd, cr, 32); sys_read(ufd, priv, 32); sys_close(ufd) } else { var z: i64=0; while z<32 { cr[z]=(0xC0+z) as u8; priv[z]=(0xA0+z) as u8; z=z+1 } } 1252 1253 let page: *Page = sys_mmap(NX_PAGE_BYTES) as *Page 1254 var WW: i64 = 1000 1255 var WH: i64 = 760 1256 1257 // SITE-TEST BATCH: if /tmp/nxsites exists (one URL per line), fetch+layout each through the REAL 1258 // pipeline and print per-site metrics (box count = did real content render; FETCH-FAIL = TLS/network/ 1259 // redirect), then exit. This is how we measure whether the render fixes generalize across the web. 1260 let stfd: i64 = sys_openat_rd("/tmp/nxsites\x00" as *u8) 1261 if stfd >= 0 { 1262 sys_close(stfd) 1263 let slp: *i64 = sys_mmap(16) as *i64 1264 let sbuf: *u8 = sys_read_file("/tmp/nxsites\x00" as *u8, slp) 1265 let sblen: i64 = slp[0] 1266 let one: *u8 = sys_mmap(URLCAP) 1267 var si: i64 = 0 1268 var idx: i64 = 0 1269 while si < sblen { 1270 var k: i64 = 0 1271 var go: i64 = 1 1272 while go == 1 { 1273 if si >= sblen { go = 0 } 1274 else { 1275 let ch: i64 = sbuf[si] & 0xff 1276 si = si + 1 1277 if ch == 10 { go = 0 } else { if ch != 13 { if k < URLCAP - 1 { one[k] = ch as u8; k = k + 1 } } } 1278 } 1279 } 1280 one[k] = 0 as u8 1281 if k > 0 { 1282 idx = idx + 1 1283 let rcL: i64 = load_page(one, k, store, cr, priv, page, WW) 1284 br_p(1, "SITETEST n=\x00" as *u8); br_n(1, idx) 1285 if rcL == 0 { br_p(1, " boxes=\x00" as *u8); br_n(1, page.tree.count); br_p(1, " page_h=\x00" as *u8); br_n(1, page.page_h) } 1286 else { br_p(1, " FETCH-FAIL rc=\x00" as *u8); br_n(1, rcL) } 1287 br_p(1, " url=\x00" as *u8); sys_write(1, one, k); br_p(1, "\n\x00" as *u8) 1288 } 1289 } 1290 br_p(1, "SITETEST done\n\x00" as *u8) 1291 return 0 1292 } 1293 1294 br_p(1, "nx_browser: fetching \x00" as *u8); sys_write(1, cur_url, ulen); br_p(1, "\n\x00" as *u8) 1295 if load_page(cur_url, ulen, store, cr, priv, page, WW) < 0 { br_p(2, "nx_browser: initial fetch failed\n\x00" as *u8); return 4 } 1296 if page.ok != 1 { return 4 } 1297 br_p(1, "nx_browser: laid out \x00" as *u8); br_n(1, page.tree.count); br_p(1, " boxes, page_h=\x00" as *u8); br_n(1, page.page_h); br_p(1, "\n\x00" as *u8) 1298 1299 // sovereign-game page? (nishi-game marker) -> the NATIVE client replaces the wasm entirely 1300 let gname: *u8 = sys_mmap(80) 1301 var glen: i64 = br_game_organ(page.raw, page.raw_len, gname) 1302 if glen > 0 { 1303 br_p(1, "nx_browser: SOVEREIGN GAME page -- press g to play NATIVE (\x00" as *u8) 1304 sys_write(1, gname, glen); br_p(1, "), zero wasm\n\x00" as *u8) 1305 } 1306 // chain-proof mode: knowledge/status/nxgame_chain present -> fetch->detect->exec selftest->exit 1307 let chfd: i64 = sys_openat_rd("knowledge/status/nxgame_chain\x00" as *u8) 1308 if chfd >= 0 { 1309 sys_close(chfd) 1310 if glen <= 0 { br_p(1, "NATIVE-CHAIN no-marker rc=90\n\x00" as *u8); return 90 } 1311 let crc: i64 = br_launch_game(gname, glen, 1) 1312 br_p(1, "NATIVE-CHAIN organ=\x00" as *u8); sys_write(1, gname, glen) 1313 br_p(1, " rc=\x00" as *u8); br_n(1, crc); br_p(1, "\n\x00" as *u8) 1314 return crc 1315 } 1316 1317 // SHOT MODE (self-verify, no X): if /tmp/nxshot exists, rasterize the page to an in-memory 1318 // framebuffer and write a PNG, then exit. Works headless -- sidesteps WSLg's GetImage limits. 1319 let shotfd: i64 = sys_openat_rd("/tmp/nxshot\x00" as *u8) 1320 if shotfd >= 0 { 1321 sys_close(shotfd) 1322 br_shot_png(page, cur_url, br_slen(cur_url), "/tmp/nxbrowser.png\x00" as *u8) 1323 br_p(1, "nx_browser: shot -> /tmp/nxbrowser.png\n\x00" as *u8) 1324 return 0 1325 } 1326 1327 let rc: i64 = nx_x11_connect_local(0) 1328 if rc < 0 { br_p(2, "nx_browser: no X display\n\x00" as *u8); return 6 } 1329 let cc: *X11Conn = rc as *X11Conn 1330 let mask: i64 = NX_X11_EVENT_EXPOSURE | NX_X11_EVENT_KEY_PRESS | NX_X11_EVENT_BUTTON_PRESS | NX_X11_EVENT_STRUCTURE 1331 let wid: i64 = nx_x11_create_window(cc, 40, 40, WW, WH, 0x00FFFFFF, mask) 1332 if wid < 0 { return 7 } 1333 let title: *u8 = "Nishi Browser (LIVE, sovereign) -- click links, b=back, m=media, q=quit\x00" 1334 nx_x11_set_wm_name(cc, wid, title, br_slen(title)) 1335 let gc: i64 = nx_x11_create_gc(cc, wid, 0x00000000) 1336 let paper: i64 = nx_x11_create_gc(cc, wid, 0x00FFFFFF) 1337 let fbody: i64 = nx_x11_open_font(cc, "9x15\x00" as *u8, 4) 1338 let fh2: i64 = nx_x11_open_font(cc, "10x20\x00" as *u8, 5) 1339 let fh1: i64 = nx_x11_open_font(cc, "12x24\x00" as *u8, 5) 1340 if nx_x11_map_window(cc, wid) < 0 { return 8 } 1341 nx_x11_flush_via_get_input_focus(cc) 1342 br_p(1, "nx_browser: WINDOW OPEN -- click a link to navigate, b=back, m=media sources, scroll wheel/arrows, q quits\n\x00" as *u8) 1343 1344 // history stacks of URLs (each a fixed URLCAP slot): back + forward 1345 let HISTMAX: i64 = 64 1346 let hist: *u8 = sys_mmap(URLCAP * HISTMAX) 1347 let histlen: *i64 = sys_mmap(8 * HISTMAX) as *i64 1348 var histn: i64 = 0 1349 let fhist: *u8 = sys_mmap(URLCAP * HISTMAX) 1350 let fhistlen: *i64 = sys_mmap(8 * HISTMAX) as *i64 1351 var fhistn: i64 = 0 1352 1353 var scroll_y: i64 = 0 1354 var max_scroll: i64 = page.page_h - (WH - NX_CHROME_H) 1355 if max_scroll < 0 { max_scroll = 0 } 1356 br_draw(cc, wid, gc, paper, page, WW, WH, scroll_y, fbody, fh2, fh1, cur_url, br_slen(cur_url)) 1357 let ev: *u8 = sys_mmap(64) 1358 let hoff: *i64 = sys_mmap(8) as *i64; let hlen2: *i64 = sys_mmap(8) as *i64 1359 let absbuf: *u8 = sys_mmap(URLCAP); let abslen: *i64 = sys_mmap(8) as *i64 1360 var running: i64 = 1 1361 while running == 1 { 1362 if nx_x11_read_event(cc, ev) != 0 { running = 0 } 1363 else { 1364 let kind: i64 = (ev[0] & 0x7f) as i64 1365 var dirty: i64 = 0 1366 var navurl: i64 = 0 // 1 if cur_url changed -> reload 1367 if kind == 12 { dirty = 1 } 1368 if kind == 22 { // ConfigureNotify: window resized -> reflow 1369 let nw: i64 = ((ev[20]&0xff) | ((ev[21]&0xff)<<8)) as i64 1370 let nh: i64 = ((ev[22]&0xff) | ((ev[23]&0xff)<<8)) as i64 1371 if nw > 0 { if nh > 0 { 1372 WH = nh 1373 if nw != WW { 1374 WW = nw 1375 br_layout(page, WW) // re-lay-out at the new width (NO refetch) 1376 max_scroll = page.page_h - (WH - NX_CHROME_H) 1377 if max_scroll < 0 { max_scroll = 0 } 1378 } 1379 dirty = 1 1380 } } 1381 } 1382 if kind == 4 { 1383 let btn: i64 = (ev[1] & 0xff) as i64 1384 if btn == 5 { scroll_y = scroll_y + 120; dirty = 1 } 1385 if btn == 4 { scroll_y = scroll_y - 120; dirty = 1 } 1386 if btn == 1 { 1387 // left click: window coords in ev[24..27]=x, ev[26..]=y per X event layout (event-x at 24, event-y at 26) 1388 let ex: i64 = ((ev[24]&0xff) | ((ev[25]&0xff)<<8)) as i64 1389 let ey: i64 = ((ev[26]&0xff) | ((ev[27]&0xff)<<8)) as i64 1390 if ey < NX_CHROME_H { 1391 // chrome click: Back / Forward / Reload 1392 let act: i64 = br_chrome_click(ex) 1393 if act == 1 { if histn > 0 { 1394 if fhistn < HISTMAX { var z: i64=0; let cl: i64=br_slen(cur_url); while z<cl { fhist[fhistn*URLCAP+z]=cur_url[z]; z=z+1 } fhistlen[fhistn]=cl; fhistn=fhistn+1 } 1395 histn = histn - 1; let cl2: i64=histlen[histn]; var z2: i64=0; while z2<cl2 { cur_url[z2]=hist[histn*URLCAP+z2]; z2=z2+1 } cur_url[cl2]=0 as u8; navurl = 1 1396 } } 1397 if act == 2 { if fhistn > 0 { 1398 if histn < HISTMAX { var z: i64=0; let cl: i64=br_slen(cur_url); while z<cl { hist[histn*URLCAP+z]=cur_url[z]; z=z+1 } histlen[histn]=cl; histn=histn+1 } 1399 fhistn = fhistn - 1; let cl2: i64=fhistlen[fhistn]; var z2: i64=0; while z2<cl2 { cur_url[z2]=fhist[fhistn*URLCAP+z2]; z2=z2+1 } cur_url[cl2]=0 as u8; navurl = 1 1400 } } 1401 if act == 3 { navurl = 1 } 1402 } else { 1403 let dx: i64 = ex 1404 let dy: i64 = (ey - NX_CHROME_H) + scroll_y 1405 if br_hit_link(page, dx, dy, hoff, hlen2) == 1 { 1406 if nx_url_resolve(cur_url, br_slen(cur_url), ((page.buf as i64)+hoff[0]) as *u8, hlen2[0], absbuf, URLCAP, abslen) == NX_URL_RESOLVE_OK { 1407 // only follow http(s) links 1408 if (absbuf[0]&0xff)==104 { // 'h' 1409 br_p(1, "nx_browser: -> \x00" as *u8); sys_write(1, absbuf, abslen[0]); br_p(1, "\n\x00" as *u8) 1410 // push current to back history; a NEW navigation clears the forward stack 1411 if histn < HISTMAX { var z: i64=0; let cl: i64=br_slen(cur_url); while z<cl { hist[histn*URLCAP+z]=cur_url[z]; z=z+1 } histlen[histn]=cl; histn=histn+1 } 1412 fhistn = 0 1413 var z2: i64=0; while z2<abslen[0] { cur_url[z2]=absbuf[z2]; z2=z2+1 } cur_url[abslen[0]]=0 as u8 1414 navurl = 1 1415 } 1416 } 1417 } 1418 } 1419 } 1420 } 1421 if kind == 2 { 1422 let kc: i64 = (ev[1] & 0xff) as i64 1423 if kc == 116 { scroll_y = scroll_y + 120; dirty = 1 } 1424 if kc == 111 { scroll_y = scroll_y - 120; dirty = 1 } 1425 if kc == 117 { scroll_y = scroll_y + 600; dirty = 1 } 1426 if kc == 112 { scroll_y = scroll_y - 600; dirty = 1 } 1427 if kc == 65 { scroll_y = scroll_y + 600; dirty = 1 } 1428 if kc == 56 { // 'b' = back 1429 if histn > 0 { 1430 if fhistn < HISTMAX { var zf: i64=0; let clf: i64=br_slen(cur_url); while zf<clf { fhist[fhistn*URLCAP+zf]=cur_url[zf]; zf=zf+1 } fhistlen[fhistn]=clf; fhistn=fhistn+1 } 1431 histn = histn - 1; let cl: i64=histlen[histn]; var z: i64=0; while z<cl { cur_url[z]=hist[histn*URLCAP+z]; z=z+1 } cur_url[cl]=0 as u8; navurl = 1 1432 } 1433 } 1434 if kc == 58 { br_media_dump(store, cur_url, br_slen(cur_url)) } // 'm' = deep-harvest + panel the gif/vid/image sources 1435 if kc == 42 { // 'g' = play this page's game NATIVELY 1436 if glen > 0 { 1437 br_p(1, "nx_browser: launching native \x00" as *u8); sys_write(1, gname, glen); br_p(1, "\n\x00" as *u8) 1438 br_launch_game(gname, glen, 0) 1439 } 1440 if glen <= 0 { br_p(1, "nx_browser: no sovereign game on this page\n\x00" as *u8) } 1441 } 1442 if kc == 24 { running = 0 } // 'q' 1443 if kc == 9 { running = 0 } // Esc 1444 } 1445 if kind == 17 { running = 0 } 1446 if navurl == 1 { 1447 br_p(1, "nx_browser: loading...\n\x00" as *u8) 1448 if load_page(cur_url, br_slen(cur_url), store, cr, priv, page, WW) == 0 { if page.ok == 1 { 1449 scroll_y = 0 1450 max_scroll = page.page_h - (WH - NX_CHROME_H) 1451 if max_scroll < 0 { max_scroll = 0 } 1452 dirty = 1 1453 glen = br_game_organ(page.raw, page.raw_len, gname) 1454 if glen > 0 { 1455 br_p(1, "nx_browser: SOVEREIGN GAME page -- press g to play NATIVE (\x00" as *u8) 1456 sys_write(1, gname, glen); br_p(1, ")\n\x00" as *u8) 1457 } 1458 } } 1459 } 1460 if dirty == 1 { 1461 if scroll_y < 0 { scroll_y = 0 } 1462 if scroll_y > max_scroll { scroll_y = max_scroll } 1463 br_draw(cc, wid, gc, paper, page, WW, WH, scroll_y, fbody, fh2, fh1, cur_url, br_slen(cur_url)) 1464 } 1465 } 1466 } 1467 sys_close(cc.fd) 1468 br_p(1, "nx_browser: closed\n\x00" as *u8) 1469 return 0 1470}