code wiki / _hdl_build / nx_browser.nx
nx_browser.nx source
↩ module page · 1243 lines · 63716 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_x509_trust_store.nx"
8import "nx_trust_store_load_from_certdata.nx"
9import "nx_https_get.nx"
10import "nx_http_response_parse.nx"
11import "nx_html_tokenizer.nx"
12import "nx_js_eval.nx" // R-JS-DOMWRITE: js_render_page + js_has_inline_script (run page JS, render DOM mutations)
13import "nx_dom_query.nx"
14import "nx_html_entities.nx"
15import "nx_url_resolve.nx"
16import "nx_render_html.nx"
17import "nx_css_color_decode.nx"
18import "nx_layout_box.nx"
19import "nx_layout_default_display.nx"
20import "nx_layout_from_dom.nx"
21import "nx_layout_block.nx"
22import "nx_x11_connect.nx"
23import "nx_x11_window.nx"
24import "nx_x11_paint.nx"
25import "nx_http_get_ua.nx"
26import "nx_dns_resolve_a_record.nx"
27import "nx_paint_solid_rect.nx"
28import "nx_paint_text.nx"
29import "nx_png_write.nx"
30import "nx_signal.nx"
31import "nx_browser_buyguard.nx" // "before you buy" pass: splice an opportunity-cost overlay into the page
32import "nx_intake_profile.nx" // read the operator's own intake numbers (their real $ vs what it costs)
33import "nx_media_harvest.nx" // 'm' key: panel emitter + kind labels for the media source list
34import "nx_media_deepcrawl.nx" // 'm' key: DEEP crawl -- follow the page's css/js (incl. JS-constructed URLs) and harvest their media
35
36struct Page {
37 buf: *u8,
38 tree: *LayoutTree,
39 computed: *CssComputedDecl,
40 ncomp: i64,
41 page_h: i64,
42 bhref_off: *i64, // per box_idx: href byte-offset into buf, or -1
43 bhref_len: *i64,
44 ok: i64,
45 raw: *u8, // fetched response body (kept so resize can re-layout WITHOUT re-fetching)
46 raw_len: i64
47}
48const NX_PAGE_BYTES: i64 = 80
49const NX_CHROME_H: i64 = 44 // browser chrome (toolbar + address bar) height; page viewport is below it
50
51func br_slen(s: *u8) -> i64 { var k: i64=0; while s[k]!=(0 as u8){k=k+1} return k }
52func br_p(fd: i64, s: *u8) -> i64 { sys_write(fd, s, br_slen(s)); return 0 }
53func 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 }
54
55// write buf[0..n) to path (O_CREAT|O_TRUNC|O_WRONLY, 0644). returns bytes written or -1.
56func br_write_file(path: *u8, buf: *u8, n: i64) -> i64 {
57 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)
58 if fd < 0 { return 0 - 1 }
59 var done: i64 = 0
60 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 } }
61 __syscall(3, fd, 0, 0, 0, 0, 0) // close
62 return n
63}
64
65// 'm' key: DEEP-harvest every image/gif/video/stream/audio SOURCE URL reachable through THIS page's resource
66// graph -- it follows the CSS/JS the page pulls in, INCLUDING asset URLs the JS constructs from string constants
67// (reconstructed by nx_js_urlfold WITHOUT executing anything), so runtime-loaded art on Unity/heavy-JS pages
68// surfaces with no manual network capture. Prints the list to the terminal + (re)writes the shared media panel.
69// Budget-bounded (16 fetches / depth 1) so the window only blocks briefly. Never runs the page's JS.
70func br_media_dump(store: *TrustStore, cur_url: *u8, ulen: i64) -> i64 {
71 br_p(1, "nx_browser: harvesting media (deep crawl -- following this page's css/js)...\n" as *u8)
72 let M: *MediaSet = nx_mediaset_new()
73 let fetched: i64 = nx_media_deepcrawl(cur_url, ulen, store, M, 16, 1, 1, "knowledge/status/media_panel_raw.html" as *u8)
74 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)
75 var d: i64 = 0
76 while d < M.cnt {
77 let bp: *u8 = ((M.buf as i64) + M.off[d]) as *u8
78 br_p(1, " [" as *u8); br_p(1, nx_media_kind_label(M.kind[d])); br_p(1, "] " as *u8)
79 sys_write(1, bp, M.len[d]); br_p(1, "\n" as *u8)
80 d = d + 1
81 }
82 let H: *u8 = sys_mmap(12582912)
83 let hlen: i64 = nx_media_emit_panel(H, cur_url, ulen, M.buf, M.off, M.len, M.kind, M.cnt)
84 br_write_file("knowledge/status/media_panel.html" as *u8, H, hlen)
85 br_p(1, "nx_browser: media panel -> knowledge/status/media_panel.html (open in any browser)\n" as *u8)
86 return 0
87}
88
89func br_comp_int(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, prop: *u8, plen: i64, dflt: i64) -> i64 {
90 var j: i64 = ncomp - 1
91 while j >= 0 {
92 let cd: *CssComputedDecl = ((computed as i64) + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl
93 if cd.element_idx == box_idx { if cd.prop_len == plen {
94 var ok: i64 = 1; var k: i64 = 0
95 while k < plen { if (src[cd.prop_off+k]&0xff) != (prop[k]&0xff) { ok=0; k=plen } else { k=k+1 } }
96 if ok == 1 {
97 var v: i64 = 0; var any: i64 = 0; var i: i64 = 0
98 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 }
99 if any == 1 { return v }
100 return dflt
101 }
102 } }
103 j = j - 1
104 }
105 return dflt
106}
107func br_comp_color(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, prop: *u8, plen: i64) -> i64 {
108 var j: i64 = ncomp - 1
109 while j >= 0 {
110 let cd: *CssComputedDecl = ((computed as i64) + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl
111 if cd.element_idx == box_idx { if cd.prop_len == plen {
112 var ok: i64 = 1; var k: i64 = 0
113 while k < plen { if (src[cd.prop_off+k]&0xff) != (prop[k]&0xff) { ok=0; k=plen } else { k=k+1 } }
114 if ok == 1 {
115 let col: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor
116 var got: i64 = 0
117 if cd.val_kind == NX_CSS_VAL_HEXCOLOR { got = nx_css_color_decode(src, cd.val_off, cd.val_len, col) }
118 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) } } }
119 if got == 1 { if col.a != 0 { return (col.r << 16) | (col.g << 8) | col.b } }
120 return 0 - 1
121 }
122 } }
123 j = j - 1
124 }
125 return 0 - 1
126}
127
128// Extract elements (tag/id/class) AND per-element href, mirroring rh_extract's chrome-skip EXACTLY (so the
129// element order == the layout box order). ehref_off/ehref_len get the <a href> span, or -1.
130func br_extract(html: *u8, hlen: i64, elements: *CssElement, ehref_off: *i64, ehref_len: *i64, maxn: i64) -> i64 {
131 let c: *HtmlCursor=sys_mmap(24) as *HtmlCursor; nx_html_cursor_init(c,html,hlen)
132 let tok: *HtmlToken=sys_mmap(56) as *HtmlToken
133 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
134 let ho: *i64=sys_mmap(8) as *i64; let hl: *i64=sys_mmap(8) as *i64
135 var n: i64=0
136 var cskip: i64=0
137 while 1==1 {
138 nx_html_next_token(c,tok)
139 if tok.kind==NX_HTML_TOK_EOF { return n }
140 if n>=maxn { return n }
141 var ie: i64=0
142 if cskip > 0 {
143 if tok.kind==NX_HTML_TOK_START_TAG { cskip = cskip + 1 }
144 if tok.kind==NX_HTML_TOK_END_TAG { cskip = cskip - 1 }
145 } else {
146 if tok.kind==NX_HTML_TOK_START_TAG {
147 if _lfd_is_chrome_start(html, tok.name_off, tok.name_len, tok.src_off, tok.src_len) == 1 { cskip = 1 }
148 else { ie = 1 }
149 }
150 if tok.kind==NX_HTML_TOK_SELF_CLOSING { ie = 1 }
151 }
152 if ie==1 {
153 let e: *CssElement=((elements as i64)+n*NX_CSS_ELEMENT_BYTES) as *CssElement
154 e.src=html; e.tag_off=tok.name_off; e.tag_len=tok.name_len
155 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 }
156 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 }
157 ehref_off[n] = 0 - 1; ehref_len[n] = 0 - 1
158 if tok.name_len==1 { if (html[tok.name_off]&0xff)==97 { // <a>
159 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] }
160 } }
161 n=n+1
162 }
163 }
164 return n
165}
166
167// Fetch `url` (own DNS+TLS+HTTP), run the full pipeline, fill `page`. page.ok=1 on success.
168func br_isdigit(c: i64) -> i64 { if c >= 48 { if c <= 57 { return 1 } } return 0 }
169
170// "http://" (plain) but NOT "https://" -> 1
171func br_is_plain_http(url: *u8, ulen: i64) -> i64 {
172 if ulen < 7 { return 0 }
173 if (url[0] & 0xff) != 104 { return 0 } // h
174 if (url[1] & 0xff) != 116 { return 0 } // t
175 if (url[2] & 0xff) != 116 { return 0 } // t
176 if (url[3] & 0xff) != 112 { return 0 } // p
177 if (url[4] & 0xff) != 58 { return 0 } // :
178 if (url[5] & 0xff) != 47 { return 0 } // /
179 if (url[6] & 0xff) != 47 { return 0 } // /
180 return 1
181}
182
183// dotted-quad "a.b.c.d" -> packed (a<<24|b<<16|c<<8|d), or -1 if not a literal IPv4
184func br_parse_ipv4(s: *u8, len: i64) -> i64 {
185 var parts: i64 = 0
186 var cur: i64 = 0
187 var any: i64 = 0
188 var packed: i64 = 0
189 var i: i64 = 0
190 while i < len {
191 let c: i64 = s[i] & 0xff
192 if c == 46 {
193 if any == 0 { return 0 - 1 }
194 if cur > 255 { return 0 - 1 }
195 packed = (packed << 8) | cur
196 parts = parts + 1; cur = 0; any = 0
197 } else {
198 if br_isdigit(c) == 0 { return 0 - 1 }
199 cur = cur * 10 + (c - 48); any = 1
200 }
201 i = i + 1
202 }
203 if any == 0 { return 0 - 1 }
204 if cur > 255 { return 0 - 1 }
205 packed = (packed << 8) | cur
206 parts = parts + 1
207 if parts != 4 { return 0 - 1 }
208 return packed
209}
210
211// Sovereign plain-HTTP/1.1 fetch of an http:// URL into out (<=cap). IP-literal
212// hosts (localhost daemons) parsed directly; hostnames resolved via our own DNS.
213// Returns bytes read (full raw response, headers+body) or -1. NO TLS, NO 3rd party.
214func br_http_fetch(url: *u8, ulen: i64, out: *u8, cap: i64) -> i64 {
215 var p: i64 = 7 // skip "http://"
216 let host: *u8 = sys_mmap(512)
217 var hlen: i64 = 0
218 var go: i64 = 1
219 while go == 1 {
220 if p >= ulen { go = 0 }
221 else {
222 let c: i64 = url[p] & 0xff
223 if c == 58 { go = 0 } else { if c == 47 { go = 0 } else { host[hlen] = url[p]; hlen = hlen + 1; p = p + 1 } }
224 }
225 }
226 host[hlen] = 0 as u8
227 if hlen == 0 { return 0 - 1 }
228
229 var port: i64 = 80
230 if p < ulen { if (url[p] & 0xff) == 58 {
231 p = p + 1; port = 0
232 var g2: i64 = 1
233 while g2 == 1 {
234 if p >= ulen { g2 = 0 }
235 else {
236 let c: i64 = url[p] & 0xff
237 if br_isdigit(c) == 1 { port = port * 10 + (c - 48); p = p + 1 } else { g2 = 0 }
238 }
239 }
240 } }
241
242 let path: *u8 = sys_mmap(2048)
243 var plen: i64 = 0
244 while p < ulen { path[plen] = url[p]; plen = plen + 1; p = p + 1 }
245 if plen == 0 { path[0] = 47 as u8; plen = 1 } // "/"
246 path[plen] = 0 as u8
247
248 var ipv4: i64 = br_parse_ipv4(host, hlen)
249 if ipv4 < 0 {
250 let dns: *DnsResolveResult = nx_dns_resolve_default(host, hlen, 305419896)
251 if dns.verdict != NX_DNS_R_OK { return 0 - 1 }
252 if dns.ipv4_packed == 0 { return 0 - 1 }
253 ipv4 = dns.ipv4_packed
254 }
255 let sa: *u8 = sys_mmap(16)
256 nx_https_build_sockaddr(sa, ipv4, port)
257 let status: *i64 = sys_mmap(8) as *i64
258 let ua: *u8 = "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0\x00"
259 return nx_http_get_ua(sa, path, plen, host, hlen, ua, br_slen(ua), out, cap, status)
260}
261
262// layout-only pass: (re)build the style buffer + lay out page.raw at viewport width `vw`.
263// Split from the fetch so a window RESIZE re-lays-out at the new width with NO network round-trip.
264func br_layout(page: *Page, vw: i64) -> i64 {
265 page.ok = 0
266 let body: *u8 = page.raw
267 let body_len: i64 = page.raw_len
268
269 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"
270 let clen: i64 = br_slen(css)
271 let STYCAP: i64 = 262144
272 let buf: *u8 = sys_mmap(body_len + STYCAP + clen + 16)
273 var bi: i64 = 0
274 while bi < body_len { buf[bi]=body[bi]; bi=bi+1 }
275 // CASCADE ORIGIN ORDER (07-09 root fix, twin of nx_browser_render.nx): UA defaults FIRST, author
276 // <style> AFTER -- the last-match resolvers now let author rules override the defaults (correct CSS
277 // origin order); before this the defaults silently beat every author color on styled pages.
278 var ci: i64 = 0
279 while ci < clen { buf[body_len + ci] = css[ci]; ci=ci+1 }
280 let rawsty: *u8 = sys_mmap(STYCAP)
281 let rawstylen: i64 = rh_extract_styles(body, body_len, rawsty, STYCAP)
282 let stylen: i64 = rh_filter_media(rawsty, 0, rawstylen, ((buf as i64)+body_len+clen) as *u8, STYCAP, 1000)
283 let total: i64 = body_len + clen + stylen
284
285 var VW: i64 = vw
286 if VW < 200 { VW = 200 }
287 let boxes: *LayoutBox = (sys_mmap(NX_LAYOUT_BOX_BYTES*16384+16)) as *LayoutBox
288 let tree: *LayoutTree = (sys_mmap(64)) as *LayoutTree
289 nx_layout_tree_init(tree, boxes, 16384)
290 let stk: *LayoutFromDomStack = (sys_mmap(NX_LAYOUT_FROM_DOM_STACK_BYTES+8)) as *LayoutFromDomStack
291 let sidx: *i64 = (sys_mmap(8*128)) as *i64
292 nx_layout_from_dom_stack_init(stk, sidx, 128)
293 nx_layout_from_dom(buf, body_len, tree, stk)
294
295 let ext: *CssElement = (sys_mmap(NX_CSS_ELEMENT_BYTES*16384 as nx_size)) as *CssElement
296 let ehoff: *i64 = sys_mmap(8*16384) as *i64
297 let ehlen: *i64 = sys_mmap(8*16384) as *i64
298 let n_ext: i64 = br_extract(buf, body_len, ext, ehoff, ehlen, 16384)
299 let boxel: *CssElement = (sys_mmap((NX_CSS_ELEMENT_BYTES*(tree.count+1)) as nx_size)) as *CssElement
300 let bhoff: *i64 = sys_mmap(8*(tree.count+1)) as *i64
301 let bhlen: *i64 = sys_mmap(8*(tree.count+1)) as *i64
302 var k: i64 = 0
303 var ii: i64 = 0
304 while ii < tree.count {
305 let b: *LayoutBox = ((tree.boxes as i64)+ii*NX_LAYOUT_BOX_BYTES) as *LayoutBox
306 let e: *CssElement = ((boxel as i64)+ii*NX_CSS_ELEMENT_BYTES) as *CssElement
307 e.src = buf
308 bhoff[ii] = 0 - 1; bhlen[ii] = 0 - 1
309 var mapped: i64 = 0
310 if rh_is_elem(b.kind)==1 { if b.parent_idx != (0-1) { if k < n_ext {
311 let s: *CssElement = ((ext as i64)+k*NX_CSS_ELEMENT_BYTES) as *CssElement
312 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
313 bhoff[ii] = ehoff[k]; bhlen[ii] = ehlen[k]
314 k=k+1; mapped=1
315 } } }
316 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 }
317 ii = ii + 1
318 }
319
320 let cur: *CssCursor = (sys_mmap(NX_CSS_CURSOR_BYTES) as nx_size) as *CssCursor
321 nx_css_cursor_init(cur, buf, total); cur.pos = body_len
322 let tok: *CssToken = (sys_mmap(NX_CSS_TOKEN_BYTES) as nx_size) as *CssToken
323 let rules: *CssRule = (sys_mmap(NX_CSS_RULE_BYTES*8192 as nx_size)) as *CssRule
324 let decls: *CssDeclaration = (sys_mmap(NX_CSS_DECLARATION_BYTES*32768 as nx_size)) as *CssDeclaration
325 let st: *CssParseState = (sys_mmap(128) as nx_size) as *CssParseState
326 nx_css_parse_state_init(st, cur, tok, rules, 8192, decls, 32768)
327 nx_css_parse(st)
328 let computed: *CssComputedDecl = (sys_mmap(NX_CSS_COMPUTED_DECL_BYTES*262144 as nx_size)) as *CssComputedDecl
329 let cb: *i64 = (sys_mmap(8)) as *i64
330 rh_cascade(buf, rules, st.rule_count, decls, st.decl_count, boxel, tree.count, tree, computed, 262144, cb)
331 let ncomp: i64 = cb[0]
332 rh_promote_display_kinds(tree, computed, ncomp, buf) // inline + display:flex/grid/block -> BLOCK (sized + painted)
333
334 let pbuf: *u8 = sys_mmap(256)
335 let ltable: *LayoutPropTable = (sys_mmap(NX_LAYOUT_PROP_TABLE_BYTES)) as *LayoutPropTable
336 nx_layout_prop_table_init(ltable, pbuf)
337 let resolve: *CssResolveCtx = (sys_mmap(NX_CSS_RESOLVE_CTX_BYTES)) as *CssResolveCtx
338 resolve.root_font_size_px = 16; resolve.parent_font_size_px = 16; resolve.parent_dimension_px = VW
339 let lctx: *LayoutCtx = (sys_mmap(NX_LAYOUT_CTX_BYTES)) as *LayoutCtx
340 nx_layout_ctx_init(lctx, tree, computed, ncomp, buf, VW, resolve)
341 let page_h: i64 = nx_layout_block_layout(lctx, ltable, 0)
342
343 page.buf = buf; page.tree = tree; page.computed = computed; page.ncomp = ncomp
344 page.page_h = page_h; page.bhref_off = bhoff; page.bhref_len = bhlen; page.ok = 1
345 return 0
346}
347
348// load the operator's OWN financial profile (from their intake survey) into the buyguard profile array.
349// This is the local browser on the operator's machine -> respondent "operator"; ip_get_pos falls back to
350// safe defaults for any field they have not entered yet (so the guard works before the survey is filled).
351func bg_load_profile(prof: *i64) -> i64 {
352 let P: *u8 = "knowledge/store/intake-\x00" as *u8
353 let who: *u8 = "operator\x00" as *u8
354 prof[BGW_AVAIL] = ip_get_pos(P, who, "available_cash\x00" as *u8, 50000)
355 prof[BGW_BAL] = ip_get_pos(P, who, "debt_balance\x00" as *u8, 2000000)
356 prof[BGW_APR] = mny_rate_from_pct(ip_get_pos(P, who, "debt_apr_pct\x00" as *u8, 24), 1)
357 prof[BGW_PAY] = ip_get_pos(P, who, "monthly_payment\x00" as *u8, 60000)
358 prof[BGW_WAGE] = ip_get_pos(P, who, "hourly_wage\x00" as *u8, 2500)
359 prof[BGW_GOAL] = ip_get_pos(P, who, "buffer_goal\x00" as *u8, 100000)
360 prof[BGW_THRESH] = ip_get_pos(P, who, "pause_threshold\x00" as *u8, 10000)
361 return 0
362}
363
364// ---- DOCUMENT OMNITOOL: the browser natively opens documents (.docx/.md/.csv/...) it navigates to ----
365// A registered document extension diverts the fetched bytes through the nx_doc_view dispatch (data-driven
366// registry knowledge/doc_viewers.conf -> focused sovereign renderer organ), and the browser paints the
367// renderer's 0-JS HTML preview inline instead of dead-ending on a download. The renderers stay OUT of this
368// binary (fork-exec'd on demand) -- supporting more formats never grows the browser core.
369
370// lowercased extension of the URL's LAST path segment (after the host, before ?/#) into ext (NUL-term).
371// Returns 0 for: no path, no dot in the last segment, ext>12 chars, or html/htm (normal HTML pipeline).
372func br_doc_ext(url: *u8, ulen: i64, ext: *u8) -> i64 {
373 // end of path = first '?' or '#'
374 var pend: i64 = ulen
375 var i: i64 = 0
376 while i < ulen {
377 let c: i64 = url[i] & 0xff
378 if c == 63 { pend = i; i = ulen } else { if c == 35 { pend = i; i = ulen } else { i = i + 1 } }
379 }
380 // host starts after "://" (when present); a REAL path needs a '/' at/after hstart (bare hosts opt out)
381 var hstart: i64 = 0
382 var scanning: i64 = 1
383 i = 0
384 while scanning == 1 {
385 if i + 2 >= pend { scanning = 0 } else {
386 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 } } }
387 if scanning == 1 { i = i + 1 }
388 }
389 }
390 var slash: i64 = 0 - 1
391 var dot: i64 = 0 - 1
392 i = hstart
393 while i < pend {
394 let c: i64 = url[i] & 0xff
395 if c == 47 { slash = i; dot = 0 - 1 } else { if c == 46 { dot = i } }
396 i = i + 1
397 }
398 if slash < 0 { return 0 }
399 if dot < 0 { return 0 }
400 if dot <= slash { return 0 }
401 var k: i64 = 0
402 i = dot + 1
403 while i < pend {
404 if k >= 12 { return 0 }
405 var c2: i64 = url[i] & 0xff
406 if c2 >= 65 { if c2 <= 90 { c2 = c2 + 32 } }
407 ext[k] = c2 as u8
408 k = k + 1
409 i = i + 1
410 }
411 ext[k] = 0 as u8
412 if k == 0 { return 0 }
413 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 } } } } }
414 if k == 3 { if ext[0] == (104 as u8) { if ext[1] == (116 as u8) { if ext[2] == (109 as u8) { return 0 } } } }
415 return k
416}
417
418// build the staging paths: pin = "/tmp/nx_bdoc_in.<ext>", pout = "/tmp/nx_bdoc_<ext>.html"
419func br_doc_paths(ext: *u8, extn: i64, pin: *u8, pout: *u8) -> i64 {
420 let a: *u8 = "/tmp/nx_bdoc_in.\x00" as *u8
421 var o: i64 = 0
422 while a[o] != 0 as u8 { pin[o] = a[o]; o = o + 1 }
423 var k: i64 = 0
424 while k < extn { pin[o + k] = ext[k]; k = k + 1 }
425 pin[o + k] = 0 as u8
426 let b: *u8 = "/tmp/nx_bdoc_\x00" as *u8
427 o = 0
428 while b[o] != 0 as u8 { pout[o] = b[o]; o = o + 1 }
429 k = 0
430 while k < extn { pout[o + k] = ext[k]; k = k + 1 }
431 o = o + k
432 let s: *u8 = ".html\x00" as *u8
433 k = 0
434 while s[k] != 0 as u8 { pout[o + k] = s[k]; k = k + 1 }
435 pout[o + k] = 0 as u8
436 return 0
437}
438
439// stage the fetched bytes and run the dispatch: 0 = converted (/tmp/nx_bdoc_<ext>.html written);
440// 3 = extension not registered; anything else = converter failure. All nonzero -> caller falls through.
441func br_doc_conv(ext: *u8, extn: i64, body: *u8, blen: i64, url: *u8) -> i64 {
442 let pin: *u8 = sys_mmap(64)
443 let pout: *u8 = sys_mmap(64)
444 br_doc_paths(ext, extn, pin, pout)
445 let fd: i64 = sys_openat_wr(pin, 0x1a4)
446 if fd < 0 { return 0 - 1 }
447 sys_write(fd, body, blen)
448 sys_close(fd)
449 let pid: i64 = sys_fork()
450 if pid == 0 {
451 let lfd: i64 = sys_openat_wr("/tmp/nx_bdoc.log\x00" as *u8, 0x1a4)
452 if lfd >= 0 { sys_dup3(lfd, 1, 0); sys_dup3(lfd, 2, 0) }
453 let argv: *i64 = sys_mmap(64) as *i64
454 argv[0] = "_offc/nx_doc_view.elf\x00" as *u8 as i64
455 argv[1] = pin as i64
456 argv[2] = pout as i64
457 argv[3] = url as i64
458 argv[4] = 0
459 let envp: *i64 = sys_mmap(16) as *i64
460 envp[0] = 0
461 sys_execve("_offc/nx_doc_view.elf\x00" as *u8, argv, envp)
462 sys_exit(127)
463 }
464 let st: *i64 = sys_mmap(16) as *i64
465 sys_wait4(pid, st, 0)
466 let sig: i64 = st[0] & 0x7f
467 if sig != 0 { return 128 + sig }
468 return (st[0] >> 8) & 0xff
469}
470
471// fetch <url> over our own DNS/TLS/HTTP, store the raw body in `page`, then lay it out at width `vw`.
472func load_page(url: *u8, ulen: i64, store: *TrustStore, cr: *u8, priv: *u8, page: *Page, vw: i64) -> i64 {
473 page.ok = 0
474 let now: i64 = sys_now_realtime_sec()
475 let cap: i64 = 8388608
476 let rbuf: *u8 = sys_mmap(cap)
477 // mutable current URL so we can FOLLOW REDIRECTS (apex->www, http->https, 30x Location) like a browser
478 let cururl: *u8 = sys_mmap(4096)
479 var curlen: i64 = ulen
480 if curlen > 4094 { curlen = 4094 }
481 var ci: i64 = 0
482 while ci < curlen { cururl[ci] = url[ci]; ci = ci + 1 }
483 cururl[curlen] = 0 as u8
484 let r: *i64 = nx_http_resp_alloc()
485 var n: i64 = 0 - 1
486 var pv: i64 = 0 - 1
487 var hops: i64 = 0
488 var go: i64 = 1
489 while go == 1 {
490 go = 0
491 n = 0 - 1
492 if br_is_plain_http(cururl, curlen) == 1 { n = br_http_fetch(cururl, curlen, rbuf, cap) }
493 else { n = nx_https_get(cururl, cr, priv, store, now, rbuf, cap) }
494 if n < 0 { return n } // propagate the specific failure code (-2 url, -3 connect/DNS, -4 handshake, -5 fetch)
495 pv = nx_http_response_parse(rbuf, n, r)
496 if pv == NX_HTTP_RESP_OK { if r[1] >= 300 { if r[1] < 400 { if hops < 6 {
497 let vo: *i64 = sys_mmap(16) as *i64
498 let vl: *i64 = sys_mmap(16) as *i64
499 if nx_http_response_header(rbuf, n, r, "location" as *u8, 8, vo, vl) == 1 {
500 let nb: *u8 = sys_mmap(4096)
501 let nl: *i64 = sys_mmap(16) as *i64
502 if nx_url_resolve(cururl, curlen, ((rbuf as i64) + vo[0]) as *u8, vl[0], nb, 4096, nl) == NX_URL_RESOLVE_OK {
503 var z: i64 = 0
504 while z < nl[0] { cururl[z] = nb[z]; z = z + 1 }
505 cururl[nl[0]] = 0 as u8; curlen = nl[0]; hops = hops + 1; go = 1
506 br_p(2, "nx_browser: redirect -> \x00" as *u8); sys_write(2, cururl, curlen); br_p(2, "\n\x00" as *u8)
507 }
508 }
509 } } } }
510 }
511 var body: *u8 = rbuf
512 var body_len: i64 = n
513 if pv == NX_HTTP_RESP_OK {
514 let bo: i64 = r[6]; let kind: i64 = r[8]
515 body = ((rbuf as i64) + bo) as *u8
516 body_len = n - bo
517 if kind == NX_HTTP_BODY_CONTENT_LENGTH { body_len = r[7]; if body_len > n - bo { body_len = n - bo } }
518 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 } }
519 }
520 // DOCUMENT OMNITOOL: registered doc extension -> convert via the nx_doc_view dispatch and paint the
521 // HTML preview inline (the browser opens .docx/.md/.csv/... natively). html/htm, unregistered exts and
522 // any converter failure fall through to the normal pipeline untouched -- graceful by construction.
523 let dext: *u8 = sys_mmap(32)
524 let dextn: i64 = br_doc_ext(cururl, curlen, dext)
525 if dextn > 0 {
526 let drc: i64 = br_doc_conv(dext, dextn, body, body_len, cururl)
527 if drc == 0 {
528 let pin2: *u8 = sys_mmap(64)
529 let pout2: *u8 = sys_mmap(64)
530 br_doc_paths(dext, dextn, pin2, pout2)
531 let dlp: *i64 = sys_mmap(16) as *i64
532 let dhtml: *u8 = sys_read_file(pout2, dlp)
533 if dlp[0] > 0 {
534 body = dhtml
535 body_len = dlp[0]
536 br_p(1, "nx_browser: doc omnitool rendered .\x00" as *u8)
537 sys_write(1, dext, dextn)
538 br_p(1, " inline\n\x00" as *u8)
539 }
540 }
541 }
542 // R-JS-DOMWRITE: if the page has an INLINE <script>, EXECUTE it against a mutable DOM and lay out the
543 // MUTATED html -- so JS-built content renders. RCDATA-safe round-trip; script-less pages skip this.
544 var rbody: *u8 = body
545 var rblen: i64 = body_len
546 if js_has_inline_script(body, body_len) == 1 {
547 let jscap: i64 = body_len + 1048576
548 let jsbuf: *u8 = sys_mmap(jscap)
549 let jslen: i64 = js_render_page(body, body_len, jsbuf, jscap)
550 if jslen > 0 { rbody = jsbuf; rblen = jslen }
551 }
552 // BUYGUARD: run the before-you-buy pass on the fetched page and splice the opportunity-cost overlay in
553 // as a top-of-page box (no price detected -> body returned untouched). Uses the operator's real numbers.
554 let bgprof: *i64 = sys_mmap(8 * 8) as *i64
555 bg_load_profile(bgprof)
556 let injcap: i64 = rblen + 65536
557 let injbuf: *u8 = sys_mmap(injcap)
558 let injlen: i64 = bgw_inject(rbody, rblen, bgprof, injbuf, injcap)
559 page.raw = injbuf; page.raw_len = injlen
560 return br_layout(page, vw)
561}
562
563// one toolbar button: a bordered face with a centered single-char label
564func br_button(c: *X11Conn, wid: i64, gc: i64, x: i64, label: *u8, fbody: i64) -> i64 {
565 nx_x11_set_gc_foreground(c, gc, 0x00B0B4BA); nx_x11_fill_rect(c, wid, gc, x, 8, 30, 28) // border
566 nx_x11_set_gc_foreground(c, gc, 0x00F6F8FA); nx_x11_fill_rect(c, wid, gc, x + 1, 9, 28, 26) // face
567 nx_x11_set_gc_font(c, gc, fbody); nx_x11_set_gc_foreground(c, gc, 0x00202124)
568 nx_x11_poly_text8(c, wid, gc, x + 11, 27, label, 1)
569 return 0
570}
571
572// the browser chrome: toolbar background + Back/Forward/Reload buttons + the address bar (URL display)
573func br_chrome(c: *X11Conn, wid: i64, gc: i64, win_w: i64, cur_url: *u8, ulen: i64, fbody: i64) -> i64 {
574 nx_x11_set_gc_foreground(c, gc, 0x00DDE1E6); nx_x11_fill_rect(c, wid, gc, 0, 0, win_w, NX_CHROME_H)
575 nx_x11_set_gc_foreground(c, gc, 0x00B0B4BA); nx_x11_fill_rect(c, wid, gc, 0, NX_CHROME_H - 1, win_w, 1)
576 br_button(c, wid, gc, 8, "<\x00" as *u8, fbody)
577 br_button(c, wid, gc, 44, ">\x00" as *u8, fbody)
578 br_button(c, wid, gc, 80, "R\x00" as *u8, fbody)
579 // address bar field (bordered white box) + the current URL
580 let bx: i64 = 118
581 let bw: i64 = win_w - bx - 10
582 nx_x11_set_gc_foreground(c, gc, 0x00B0B4BA); nx_x11_fill_rect(c, wid, gc, bx, 8, bw, 28)
583 nx_x11_set_gc_foreground(c, gc, 0x00FFFFFF); nx_x11_fill_rect(c, wid, gc, bx + 1, 9, bw - 2, 26)
584 var maxc: i64 = (bw - 14) / 7
585 if maxc < 0 { maxc = 0 }
586 var n: i64 = ulen
587 if n > maxc { n = maxc }
588 if n > 250 { n = 250 }
589 nx_x11_set_gc_font(c, gc, fbody); nx_x11_set_gc_foreground(c, gc, 0x00202124)
590 if n > 0 { nx_x11_poly_text8(c, wid, gc, bx + 7, 27, cur_url, n) }
591 return 0
592}
593
594// chrome hit-test: 1=Back, 2=Forward, 3=Reload, 0=none (call only when click y < NX_CHROME_H)
595func br_chrome_click(ex: i64) -> i64 {
596 if ex >= 8 { if ex < 38 { return 1 } }
597 if ex >= 44 { if ex < 74 { return 2 } }
598 if ex >= 80 { if ex < 110 { return 3 } }
599 return 0
600}
601
602// INHERITED color resolve (CSS: `color` inherits) -- twin of nx_browser_render.nx's helper; walk up the
603// parent chain to the nearest computed color decl (author body/.doc colors now reach p/li/td text).
604func br_comp_color_inh(src: *u8, computed: *CssComputedDecl, ncomp: i64, tree: *LayoutTree, box_idx: i64) -> i64 {
605 var cur: i64 = box_idx
606 var guard: i64 = 0
607 while cur >= 0 {
608 if guard > 64 { return 0 - 1 }
609 guard = guard + 1
610 let c: i64 = br_comp_color(src, computed, ncomp, cur, "color\x00" as *u8, 5)
611 if c >= 0 { return c }
612 let b: *LayoutBox = ((tree.boxes as i64) + cur * NX_LAYOUT_BOX_BYTES) as *LayoutBox
613 cur = b.parent_idx
614 }
615 return 0 - 1
616}
617
618// background-color, else the `background` shorthand's single-hex value (twin of nx_browser_render.nx).
619func br_comp_bg(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64) -> i64 {
620 let c: i64 = br_comp_color(src, computed, ncomp, box_idx, "background-color\x00" as *u8, 16)
621 if c >= 0 { return c }
622 return br_comp_color(src, computed, ncomp, box_idx, "background\x00" as *u8, 10)
623}
624
625// `border` shorthand: parse "<w>px ... #rrggbb" from the swept value span (twin of nx_browser_render.nx).
626func br_comp_border(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, wout: *i64) -> i64 {
627 var j: i64 = ncomp - 1
628 while j >= 0 {
629 let cd: *CssComputedDecl = ((computed as i64) + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl
630 if cd.element_idx == box_idx { if cd.prop_len == 6 {
631 let P: *u8 = "border\x00" as *u8
632 var ok: i64 = 1
633 var k: i64 = 0
634 while k < 6 { if (src[cd.prop_off+k]&0xff) != (P[k]&0xff) { ok=0; k=6 } else { k=k+1 } }
635 if ok == 1 {
636 var wv: i64 = 0
637 var i2: i64 = cd.val_off
638 let e2: i64 = cd.val_off + cd.val_len
639 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 } }
640 if wv <= 0 { wv = 1 }
641 if wv > 8 { wv = 8 }
642 var hp: i64 = 0 - 1
643 var i3: i64 = cd.val_off
644 while i3 < e2 { if src[i3]==(35 as u8) { hp=i3; i3=e2 } else { i3=i3+1 } }
645 if hp >= 0 {
646 let col: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor
647 if nx_css_color_decode(src, hp+1, 6, col) == 1 {
648 wout[0] = wv
649 return (col.r << 16) | (col.g << 8) | col.b
650 }
651 }
652 return 0 - 1
653 }
654 } }
655 j = j - 1
656 }
657 return 0 - 1
658}
659
660func br_draw(c: *X11Conn, wid: i64, gc: i64, paper: i64, page: *Page, win_w: i64, win_h: i64, scroll_y: i64,
661 fbody: i64, fh2: i64, fh1: i64, cur_url: *u8, ulen: i64) -> i64 {
662 let tree: *LayoutTree = page.tree
663 let src: *u8 = page.buf
664 let computed: *CssComputedDecl = page.computed
665 let ncomp: i64 = page.ncomp
666 let decbuf: *u8 = sys_mmap(1100) // reused per text box: decode HTML entities before painting
667 nx_x11_fill_rect(c, wid, paper, 0, 0, win_w, win_h)
668 var i: i64 = 0
669 let bwp: *i64 = sys_mmap(16) as *i64 // border width out-slot (hoisted: no mmap in the loop)
670 while i < tree.count {
671 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox
672 if b.kind == NX_LAYOUT_BOX_BLOCK { if b.w > 0 { if b.h > 0 {
673 let bg: i64 = br_comp_bg(src, computed, ncomp, i)
674 if bg >= 0 { if bg != 0x00FFFFFF {
675 let sy: i64 = b.y - scroll_y + NX_CHROME_H
676 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) } }
677 } }
678 // CSS borders (the `border` shorthand): stroke the 4 edges
679 let bc: i64 = br_comp_border(src, computed, ncomp, i, bwp)
680 if bc >= 0 {
681 let sy3: i64 = b.y - scroll_y + NX_CHROME_H
682 if sy3 + b.h >= NX_CHROME_H { if sy3 < win_h {
683 let bw2: i64 = bwp[0]
684 nx_x11_set_gc_foreground(c, gc, bc)
685 nx_x11_fill_rect(c, wid, gc, b.x, sy3, b.w, bw2)
686 nx_x11_fill_rect(c, wid, gc, b.x, sy3 + b.h - bw2, b.w, bw2)
687 nx_x11_fill_rect(c, wid, gc, b.x, sy3, bw2, b.h)
688 nx_x11_fill_rect(c, wid, gc, b.x + b.w - bw2, sy3, bw2, b.h)
689 } }
690 }
691 } } }
692 i = i + 1
693 }
694 i = 0
695 while i < tree.count {
696 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox
697 if b.kind == NX_LAYOUT_BOX_TEXT { if b.text_len > 0 {
698 let sy: i64 = b.y - scroll_y + NX_CHROME_H
699 if sy >= NX_CHROME_H - 40 { if sy < win_h {
700 let par: i64 = b.parent_idx
701 let fs: i64 = br_comp_int(src, computed, ncomp, par, "font-size\x00" as *u8, 9, 16)
702 var fid: i64 = fbody; var asc: i64 = 12
703 if fs > 18 { fid = fh2; asc = 16 }
704 if fs > 28 { fid = fh1; asc = 19 }
705 var col: i64 = br_comp_color_inh(src, computed, ncomp, tree, par) // CSS-inherited color
706 if col < 0 { col = 0 }
707 nx_x11_set_gc_font(c, gc, fid); nx_x11_set_gc_foreground(c, gc, col)
708 var nn: i64 = b.text_len
709 if nn > 200 { nn = 200 }
710 let dn: i64 = nx_html_decode_entities(src, b.text_off, nn, decbuf, 1024)
711 nx_x11_poly_text8(c, wid, gc, b.x + 4, sy + asc, decbuf, dn)
712 if col == 0x3366cc { nx_x11_fill_rect(c, wid, gc, b.x + 4, sy + asc + 2, b.w, 1) } // underline links
713 } }
714 } }
715 i = i + 1
716 }
717 br_chrome(c, wid, gc, win_w, cur_url, ulen, fbody)
718 nx_x11_flush_via_get_input_focus(c)
719 return 0
720}
721
722func br_color_from_int(c: i64) -> *CssColor {
723 let col: *CssColor = sys_mmap(NX_CSS_COLOR_BYTES) as *CssColor
724 col.r = (c >> 16) & 0xff
725 col.g = (c >> 8) & 0xff
726 col.b = c & 0xff
727 col.a = 255
728 return col
729}
730
731// 2x-scaled 5x7 text for the SHOT framebuffer (each glyph pixel -> 2x2 block = 10x14 glyph, 9px advance)
732// so the capture ~matches the live window's 9x15 font + the 9px layout metric (faithful self-verify).
733func br_paint_text2x(fb: *Framebuffer, x: i64, y: i64, text: *u8, text_len: i64, color: *CssColor) -> i64 {
734 var i: i64 = 0
735 while i < text_len {
736 let ch: i64 = text[i] & 0xff
737 let cx: i64 = x + i * 9
738 var row: i64 = 0
739 while row < 7 {
740 let bits: i64 = nx_font_5x7_glyph_row(ch, row)
741 if bits >= 0 {
742 var col: i64 = 0
743 while col < 5 {
744 if ((bits >> (4 - col)) & 1) == 1 { nx_paint_solid_rect(fb, cx + col * 2, y + row * 2, 2, 2, color) }
745 col = col + 1
746 }
747 }
748 row = row + 1
749 }
750 i = i + 1
751 }
752 return 0
753}
754
755// OFF-SCREEN render: rasterize the same page into an in-memory RGBA framebuffer (no X server) so the
756// render can be captured to a PNG and self-reviewed. Mirrors br_draw; uses the 5x7 bitmap-font paint
757// primitives (so headings aren't scaled here -- this is for verifying colors/layout/chrome, not pixel-parity).
758func br_draw_fb(fb: *Framebuffer, page: *Page, win_w: i64, cur_url: *u8, ulen: i64) -> i64 {
759 let tree: *LayoutTree = page.tree
760 let src: *u8 = page.buf
761 let computed: *CssComputedDecl = page.computed
762 let ncomp: i64 = page.ncomp
763 let fdec: *u8 = sys_mmap(1100) // reused per text box: decode HTML entities before painting
764 let ffold: *u8 = sys_mmap(1100) // reused: fold decoded UTF-8 -> ASCII (accents+punct) for the 5x7 font
765 nx_paint_solid_rect(fb, 0, 0, win_w, fb.height, br_color_from_int(0x00FFFFFF))
766 var i: i64 = 0
767 let bwp2: *i64 = sys_mmap(16) as *i64 // border width out-slot (hoisted: no mmap in the loop)
768 while i < tree.count {
769 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox
770 if b.kind == NX_LAYOUT_BOX_BLOCK { if b.w > 0 { if b.h > 0 {
771 let bg: i64 = br_comp_bg(src, computed, ncomp, i)
772 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)) } }
773 // CSS borders (the `border` shorthand): stroke the 4 edges
774 let bc: i64 = br_comp_border(src, computed, ncomp, i, bwp2)
775 if bc >= 0 {
776 let bw2: i64 = bwp2[0]
777 nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H, b.w, bw2, br_color_from_int(bc))
778 nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H + b.h - bw2, b.w, bw2, br_color_from_int(bc))
779 nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H, bw2, b.h, br_color_from_int(bc))
780 nx_paint_solid_rect(fb, b.x + b.w - bw2, b.y + NX_CHROME_H, bw2, b.h, br_color_from_int(bc))
781 }
782 } } }
783 i = i + 1
784 }
785 i = 0
786 while i < tree.count {
787 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox
788 if b.kind == NX_LAYOUT_BOX_TEXT { if b.text_len > 0 {
789 let par: i64 = b.parent_idx
790 var col: i64 = br_comp_color_inh(src, computed, ncomp, tree, par) // CSS-inherited color
791 if col < 0 { col = 0 }
792 var nn: i64 = b.text_len
793 if nn > 200 { nn = 200 }
794 let dn: i64 = nx_html_decode_entities(src, b.text_off, nn, fdec, 1024)
795 let fdn: i64 = nx_fold_ascii(fdec, 0, dn, ffold, 1100) // UTF-8 -> ASCII: punctuation + accents render, non-Latin skips cleanly (no blank gaps)
796 br_paint_text2x(fb, b.x + 4, b.y + NX_CHROME_H + 1, ffold, fdn, br_color_from_int(col))
797 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
798 } }
799 i = i + 1
800 }
801 // chrome
802 nx_paint_solid_rect(fb, 0, 0, win_w, NX_CHROME_H, br_color_from_int(0x00DDE1E6))
803 nx_paint_solid_rect(fb, 0, NX_CHROME_H - 1, win_w, 1, br_color_from_int(0x00B0B4BA))
804 nx_paint_solid_rect(fb, 8, 8, 30, 28, br_color_from_int(0x00F6F8FA))
805 nx_paint_solid_rect(fb, 44, 8, 30, 28, br_color_from_int(0x00F6F8FA))
806 nx_paint_solid_rect(fb, 80, 8, 30, 28, br_color_from_int(0x00F6F8FA))
807 br_paint_text2x(fb, 18, 16, "<\x00" as *u8, 1, br_color_from_int(0x00202124))
808 br_paint_text2x(fb, 54, 16, ">\x00" as *u8, 1, br_color_from_int(0x00202124))
809 br_paint_text2x(fb, 90, 16, "R\x00" as *u8, 1, br_color_from_int(0x00202124))
810 let bx: i64 = 118
811 nx_paint_solid_rect(fb, bx, 8, win_w - bx - 10, 28, br_color_from_int(0x00FFFFFF))
812 var un: i64 = ulen
813 if un > 96 { un = 96 }
814 br_paint_text2x(fb, bx + 6, 14, cur_url, un, br_color_from_int(0x00202124))
815 return 0
816}
817
818// render the laid-out page to a 1000xSH RGBA framebuffer and write it as a PNG at `path`. SH tracks the FULL
819// page height (chrome + laid-out content) so a self-verify shot captures the whole page, not a fixed top crop
820// (the 1000px crop made tall pages -- e.g. /office at 2765px -- look empty below the fold). Clamped for safety.
821func br_shot_png(page: *Page, cur_url: *u8, ulen: i64, path: *u8) -> i64 {
822 let SW: i64 = 1000
823 var SH: i64 = page.page_h + NX_CHROME_H + 40
824 if SH < 400 { SH = 400 }
825 if SH > 6000 { SH = 6000 }
826 let fb: *Framebuffer = sys_mmap(NX_FRAMEBUFFER_BYTES) as *Framebuffer
827 let px: *u8 = sys_mmap(SW * SH * 4 + 64)
828 nx_framebuffer_init(fb, px, SW, SH)
829 br_draw_fb(fb, page, SW, cur_url, ulen)
830 let rgb: *u8 = sys_mmap(SW * SH * 3 + 64)
831 var p: i64 = 0
832 while p < SW * SH {
833 rgb[p * 3 + 0] = px[p * 4 + 0]
834 rgb[p * 3 + 1] = px[p * 4 + 1]
835 rgb[p * 3 + 2] = px[p * 4 + 2]
836 p = p + 1
837 }
838 nx_png_write_rgb(path, rgb, SW, SH)
839 return 0
840}
841
842// hit-test: find a box with an href whose rect contains doc-point (dx,dy). 1 + sets off/len, else 0.
843// CONTAINER ANCHORS (2026-07-03): an <a> wrapping block children lays out with a ZERO own-rect --
844// hit-test the UNION of its descendants' rects (mirrors nx_browser_render.nx; found by
845// nx_aw_browser_gate on the real andelinwest card links). Purely additive.
846func br_hit_link(page: *Page, dx: i64, dy: i64, off: *i64, len: *i64) -> i64 {
847 let tree: *LayoutTree = page.tree
848 var i: i64 = 0
849 while i < tree.count {
850 if page.bhref_off[i] >= 0 {
851 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox
852 var bx: i64 = b.x
853 var by: i64 = b.y
854 var bw: i64 = b.w
855 var bh: i64 = b.h
856 if bh <= 0 {
857 // SIBLING-SPAN effective rect (mirrors nx_browser_render.nx): union the rects of the
858 // boxes AFTER i while they stay inside i's parent's subtree, stopping at the next
859 // href box -- the layout flattens container-anchor children to siblings.
860 let par: i64 = b.parent_idx
861 var x0: i64 = 0
862 var y0: i64 = 0
863 var x1: i64 = 0
864 var y1: i64 = 0
865 var any: i64 = 0
866 var j: i64 = i + 1
867 var span: i64 = 1
868 while span == 1 {
869 if j >= tree.count { span = 0 }
870 else { if page.bhref_off[j] >= 0 { span = 0 }
871 else {
872 let cb: *LayoutBox = ((tree.boxes as i64) + j * NX_LAYOUT_BOX_BYTES) as *LayoutBox
873 var p: i64 = cb.parent_idx
874 var hop: i64 = 0
875 var inside: i64 = 0
876 while hop < 64 {
877 if p == (0 - 1) { hop = 64 }
878 else { if p == par { inside = 1; hop = 64 }
879 else { let pb: *LayoutBox = ((tree.boxes as i64) + p * NX_LAYOUT_BOX_BYTES) as *LayoutBox; p = pb.parent_idx; hop = hop + 1 } }
880 }
881 if inside == 0 { span = 0 }
882 else {
883 if cb.w > 0 { if cb.h > 0 {
884 if any == 0 { x0 = cb.x; y0 = cb.y; x1 = cb.x + cb.w; y1 = cb.y + cb.h; any = 1 }
885 else {
886 if cb.x < x0 { x0 = cb.x }
887 if cb.y < y0 { y0 = cb.y }
888 if cb.x + cb.w > x1 { x1 = cb.x + cb.w }
889 if cb.y + cb.h > y1 { y1 = cb.y + cb.h }
890 }
891 } }
892 j = j + 1
893 }
894 } }
895 }
896 if any == 1 { bx = x0; by = y0; bw = x1 - x0; bh = y1 - y0 }
897 }
898 var hitw: i64 = bw // layout metric now matches the 9px render font, so b.w is the true width
899 if hitw < 12 { hitw = 12 }
900 if dx >= bx { if dx < bx + hitw { if dy >= by { if dy < by + bh {
901 off[0] = page.bhref_off[i]; len[0] = page.bhref_len[i]; return 1
902 } } } }
903 }
904 i = i + 1
905 }
906 return 0
907}
908
909// ---- sovereign-game pages (nishi-game marker) -> run the NATIVE client, zero wasm ----
910// Extract the `<meta name="nishi-game" content="...">` organ name from raw page bytes.
911// SECURITY (rule 12): the value is PAGE-CONTROLLED and becomes an exec path, so this is strict
912// fail-closed validation, not parsing: the name must be nx_[a-z0-9_]{1,60} and anything else --
913// a slash, a dot, an uppercase byte, overlength -- REJECTS the whole marker (no truncate-accept).
914func br_game_organ(raw: *u8, n: i64, out: *u8) -> i64 {
915 let pat: *u8 = "name=\"nishi-game\"\x00" as *u8
916 let cpat: *u8 = "content=\"\x00" as *u8
917 var i: i64 = 0
918 while i < n - 17 {
919 var m: i64 = 0
920 while m < 17 { if (raw[i+m] & 0xff) != (pat[m] & 0xff) { m = 99 } else { m = m + 1 } }
921 if m == 17 {
922 // find content=" within the next 80 bytes
923 var j: i64 = i + 17
924 let jend: i64 = i + 97
925 while j < jend {
926 if j < n - 9 {
927 var c: i64 = 0
928 while c < 9 { if (raw[j+c] & 0xff) != (cpat[c] & 0xff) { c = 99 } else { c = c + 1 } }
929 if c == 9 {
930 var k: i64 = 0
931 var p: i64 = j + 9
932 var badc: i64 = 0
933 var closed: i64 = 0
934 while closed == 0 {
935 if p >= n { badc = 1; closed = 1 }
936 if closed == 0 {
937 let ch: i64 = raw[p] & 0xff
938 if ch == 34 { closed = 1 }
939 if closed == 0 {
940 var okc: i64 = 0
941 if ch >= 97 { if ch <= 122 { okc = 1 } }
942 if ch >= 48 { if ch <= 57 { okc = 1 } }
943 if ch == 95 { okc = 1 }
944 if okc == 0 { badc = 1 }
945 if k >= 60 { badc = 1 }
946 if badc == 0 { out[k] = ch as u8; k = k + 1 }
947 if badc == 1 { closed = 1 }
948 p = p + 1
949 }
950 }
951 }
952 if badc == 1 { return 0 }
953 if k < 4 { return 0 }
954 if (out[0] & 0xff) != 110 { return 0 } // n
955 if (out[1] & 0xff) != 120 { return 0 } // x
956 if (out[2] & 0xff) != 95 { return 0 } // _
957 out[k] = 0 as u8
958 return k
959 }
960 }
961 j = j + 1
962 }
963 return 0
964 }
965 i = i + 1
966 }
967 return 0
968}
969
970// fork+exec the native client `_build/<name>.sov.elf` (name pre-validated by br_game_organ).
971// selftest=1: pass "selftest" and WAIT, returning the child's exit code (the chain proof).
972// selftest=0: fire-and-forget (the game gets its own window; zombie reaped at browser exit).
973func br_launch_game(name: *u8, nlen: i64, selftest: i64) -> i64 {
974 let path: *u8 = sys_mmap(128)
975 let pre: *u8 = "_build/\x00" as *u8
976 var k: i64 = 0
977 while k < 7 { path[k] = pre[k]; k = k + 1 }
978 var q: i64 = 0
979 while q < nlen { path[7+q] = name[q]; q = q + 1 }
980 let suf: *u8 = ".sov.elf\x00" as *u8
981 var t: i64 = 0
982 while t < 9 { path[7+nlen+t] = suf[t]; t = t + 1 }
983 let av: *i64 = sys_mmap(32) as *i64
984 av[0] = path as i64
985 av[1] = 0
986 av[2] = 0
987 if selftest == 1 { av[1] = ("selftest\x00" as *u8) as i64 }
988 let ep: *i64 = sys_mmap(16) as *i64
989 ep[0] = 0
990 let pid: i64 = sys_fork()
991 if pid == 0 {
992 sys_execve(path, av, ep)
993 sys_exit(127)
994 }
995 if pid < 0 { return 0 - 1 }
996 if selftest == 1 {
997 let st: *i64 = sys_mmap(16) as *i64
998 sys_wait4(pid, st, 0)
999 return (st[0] >> 8) & 0xff
1000 }
1001 return 0
1002}
1003
1004func main() -> i64 {
1005 nx_signal_ignore(NX_SIGPIPE) // a server reset mid-write must NOT kill us (was exit 141); fetch fails gracefully
1006 let lp: *i64 = sys_mmap(16) as *i64
1007 let urlraw: *u8 = sys_read_file("_url.txt\x00" as *u8, lp)
1008 var ulen: i64 = *lp
1009 if ulen <= 0 { br_p(2, "nx_browser: no _url.txt\n\x00" as *u8); return 1 }
1010 var trimming: i64 = 1
1011 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 } } } } } }
1012 let URLCAP: i64 = 4096
1013 let cur_url: *u8 = sys_mmap(URLCAP)
1014 var u: i64 = 0
1015 while u < ulen { cur_url[u]=urlraw[u]; u=u+1 }
1016 cur_url[ulen] = 0 as u8
1017
1018 var tr: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt\x00" as *u8, 300, 4194304)
1019 if tr <= 0 { tr = nx_trust_store_load_from_certdata("/tmp/mozilla_certdata.txt\x00" as *u8, 300, 4194304) }
1020 if tr <= 0 { br_p(2, "nx_browser: CA bundle load failed\n\x00" as *u8); return 3 }
1021 let store: *TrustStore = tr as *TrustStore
1022 let cr: *u8 = sys_mmap(32); let priv: *u8 = sys_mmap(32)
1023 let ufd: i64 = sys_openat_rd("/dev/urandom\x00" as *u8)
1024 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 } }
1025
1026 let page: *Page = sys_mmap(NX_PAGE_BYTES) as *Page
1027 var WW: i64 = 1000
1028 var WH: i64 = 760
1029
1030 // SITE-TEST BATCH: if /tmp/nxsites exists (one URL per line), fetch+layout each through the REAL
1031 // pipeline and print per-site metrics (box count = did real content render; FETCH-FAIL = TLS/network/
1032 // redirect), then exit. This is how we measure whether the render fixes generalize across the web.
1033 let stfd: i64 = sys_openat_rd("/tmp/nxsites\x00" as *u8)
1034 if stfd >= 0 {
1035 sys_close(stfd)
1036 let slp: *i64 = sys_mmap(16) as *i64
1037 let sbuf: *u8 = sys_read_file("/tmp/nxsites\x00" as *u8, slp)
1038 let sblen: i64 = slp[0]
1039 let one: *u8 = sys_mmap(URLCAP)
1040 var si: i64 = 0
1041 var idx: i64 = 0
1042 while si < sblen {
1043 var k: i64 = 0
1044 var go: i64 = 1
1045 while go == 1 {
1046 if si >= sblen { go = 0 }
1047 else {
1048 let ch: i64 = sbuf[si] & 0xff
1049 si = si + 1
1050 if ch == 10 { go = 0 } else { if ch != 13 { if k < URLCAP - 1 { one[k] = ch as u8; k = k + 1 } } }
1051 }
1052 }
1053 one[k] = 0 as u8
1054 if k > 0 {
1055 idx = idx + 1
1056 let rcL: i64 = load_page(one, k, store, cr, priv, page, WW)
1057 br_p(1, "SITETEST n=\x00" as *u8); br_n(1, idx)
1058 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) }
1059 else { br_p(1, " FETCH-FAIL rc=\x00" as *u8); br_n(1, rcL) }
1060 br_p(1, " url=\x00" as *u8); sys_write(1, one, k); br_p(1, "\n\x00" as *u8)
1061 }
1062 }
1063 br_p(1, "SITETEST done\n\x00" as *u8)
1064 return 0
1065 }
1066
1067 br_p(1, "nx_browser: fetching \x00" as *u8); sys_write(1, cur_url, ulen); br_p(1, "\n\x00" as *u8)
1068 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 }
1069 if page.ok != 1 { return 4 }
1070 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)
1071
1072 // sovereign-game page? (nishi-game marker) -> the NATIVE client replaces the wasm entirely
1073 let gname: *u8 = sys_mmap(80)
1074 var glen: i64 = br_game_organ(page.raw, page.raw_len, gname)
1075 if glen > 0 {
1076 br_p(1, "nx_browser: SOVEREIGN GAME page -- press g to play NATIVE (\x00" as *u8)
1077 sys_write(1, gname, glen); br_p(1, "), zero wasm\n\x00" as *u8)
1078 }
1079 // chain-proof mode: knowledge/status/nxgame_chain present -> fetch->detect->exec selftest->exit
1080 let chfd: i64 = sys_openat_rd("knowledge/status/nxgame_chain\x00" as *u8)
1081 if chfd >= 0 {
1082 sys_close(chfd)
1083 if glen <= 0 { br_p(1, "NATIVE-CHAIN no-marker rc=90\n\x00" as *u8); return 90 }
1084 let crc: i64 = br_launch_game(gname, glen, 1)
1085 br_p(1, "NATIVE-CHAIN organ=\x00" as *u8); sys_write(1, gname, glen)
1086 br_p(1, " rc=\x00" as *u8); br_n(1, crc); br_p(1, "\n\x00" as *u8)
1087 return crc
1088 }
1089
1090 // SHOT MODE (self-verify, no X): if /tmp/nxshot exists, rasterize the page to an in-memory
1091 // framebuffer and write a PNG, then exit. Works headless -- sidesteps WSLg's GetImage limits.
1092 let shotfd: i64 = sys_openat_rd("/tmp/nxshot\x00" as *u8)
1093 if shotfd >= 0 {
1094 sys_close(shotfd)
1095 br_shot_png(page, cur_url, br_slen(cur_url), "/tmp/nxbrowser.png\x00" as *u8)
1096 br_p(1, "nx_browser: shot -> /tmp/nxbrowser.png\n\x00" as *u8)
1097 return 0
1098 }
1099
1100 let rc: i64 = nx_x11_connect_local(0)
1101 if rc < 0 { br_p(2, "nx_browser: no X display\n\x00" as *u8); return 6 }
1102 let cc: *X11Conn = rc as *X11Conn
1103 let mask: i64 = NX_X11_EVENT_EXPOSURE | NX_X11_EVENT_KEY_PRESS | NX_X11_EVENT_BUTTON_PRESS | NX_X11_EVENT_STRUCTURE
1104 let wid: i64 = nx_x11_create_window(cc, 40, 40, WW, WH, 0x00FFFFFF, mask)
1105 if wid < 0 { return 7 }
1106 let title: *u8 = "Nishi Browser (LIVE, sovereign) -- click links, b=back, m=media, q=quit\x00"
1107 nx_x11_set_wm_name(cc, wid, title, br_slen(title))
1108 let gc: i64 = nx_x11_create_gc(cc, wid, 0x00000000)
1109 let paper: i64 = nx_x11_create_gc(cc, wid, 0x00FFFFFF)
1110 let fbody: i64 = nx_x11_open_font(cc, "9x15\x00" as *u8, 4)
1111 let fh2: i64 = nx_x11_open_font(cc, "10x20\x00" as *u8, 5)
1112 let fh1: i64 = nx_x11_open_font(cc, "12x24\x00" as *u8, 5)
1113 if nx_x11_map_window(cc, wid) < 0 { return 8 }
1114 nx_x11_flush_via_get_input_focus(cc)
1115 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)
1116
1117 // history stacks of URLs (each a fixed URLCAP slot): back + forward
1118 let HISTMAX: i64 = 64
1119 let hist: *u8 = sys_mmap(URLCAP * HISTMAX)
1120 let histlen: *i64 = sys_mmap(8 * HISTMAX) as *i64
1121 var histn: i64 = 0
1122 let fhist: *u8 = sys_mmap(URLCAP * HISTMAX)
1123 let fhistlen: *i64 = sys_mmap(8 * HISTMAX) as *i64
1124 var fhistn: i64 = 0
1125
1126 var scroll_y: i64 = 0
1127 var max_scroll: i64 = page.page_h - (WH - NX_CHROME_H)
1128 if max_scroll < 0 { max_scroll = 0 }
1129 br_draw(cc, wid, gc, paper, page, WW, WH, scroll_y, fbody, fh2, fh1, cur_url, br_slen(cur_url))
1130 let ev: *u8 = sys_mmap(64)
1131 let hoff: *i64 = sys_mmap(8) as *i64; let hlen2: *i64 = sys_mmap(8) as *i64
1132 let absbuf: *u8 = sys_mmap(URLCAP); let abslen: *i64 = sys_mmap(8) as *i64
1133 var running: i64 = 1
1134 while running == 1 {
1135 if nx_x11_read_event(cc, ev) != 0 { running = 0 }
1136 else {
1137 let kind: i64 = (ev[0] & 0x7f) as i64
1138 var dirty: i64 = 0
1139 var navurl: i64 = 0 // 1 if cur_url changed -> reload
1140 if kind == 12 { dirty = 1 }
1141 if kind == 22 { // ConfigureNotify: window resized -> reflow
1142 let nw: i64 = ((ev[20]&0xff) | ((ev[21]&0xff)<<8)) as i64
1143 let nh: i64 = ((ev[22]&0xff) | ((ev[23]&0xff)<<8)) as i64
1144 if nw > 0 { if nh > 0 {
1145 WH = nh
1146 if nw != WW {
1147 WW = nw
1148 br_layout(page, WW) // re-lay-out at the new width (NO refetch)
1149 max_scroll = page.page_h - (WH - NX_CHROME_H)
1150 if max_scroll < 0 { max_scroll = 0 }
1151 }
1152 dirty = 1
1153 } }
1154 }
1155 if kind == 4 {
1156 let btn: i64 = (ev[1] & 0xff) as i64
1157 if btn == 5 { scroll_y = scroll_y + 120; dirty = 1 }
1158 if btn == 4 { scroll_y = scroll_y - 120; dirty = 1 }
1159 if btn == 1 {
1160 // left click: window coords in ev[24..27]=x, ev[26..]=y per X event layout (event-x at 24, event-y at 26)
1161 let ex: i64 = ((ev[24]&0xff) | ((ev[25]&0xff)<<8)) as i64
1162 let ey: i64 = ((ev[26]&0xff) | ((ev[27]&0xff)<<8)) as i64
1163 if ey < NX_CHROME_H {
1164 // chrome click: Back / Forward / Reload
1165 let act: i64 = br_chrome_click(ex)
1166 if act == 1 { if histn > 0 {
1167 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 }
1168 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
1169 } }
1170 if act == 2 { if fhistn > 0 {
1171 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 }
1172 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
1173 } }
1174 if act == 3 { navurl = 1 }
1175 } else {
1176 let dx: i64 = ex
1177 let dy: i64 = (ey - NX_CHROME_H) + scroll_y
1178 if br_hit_link(page, dx, dy, hoff, hlen2) == 1 {
1179 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 {
1180 // only follow http(s) links
1181 if (absbuf[0]&0xff)==104 { // 'h'
1182 br_p(1, "nx_browser: -> \x00" as *u8); sys_write(1, absbuf, abslen[0]); br_p(1, "\n\x00" as *u8)
1183 // push current to back history; a NEW navigation clears the forward stack
1184 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 }
1185 fhistn = 0
1186 var z2: i64=0; while z2<abslen[0] { cur_url[z2]=absbuf[z2]; z2=z2+1 } cur_url[abslen[0]]=0 as u8
1187 navurl = 1
1188 }
1189 }
1190 }
1191 }
1192 }
1193 }
1194 if kind == 2 {
1195 let kc: i64 = (ev[1] & 0xff) as i64
1196 if kc == 116 { scroll_y = scroll_y + 120; dirty = 1 }
1197 if kc == 111 { scroll_y = scroll_y - 120; dirty = 1 }
1198 if kc == 117 { scroll_y = scroll_y + 600; dirty = 1 }
1199 if kc == 112 { scroll_y = scroll_y - 600; dirty = 1 }
1200 if kc == 65 { scroll_y = scroll_y + 600; dirty = 1 }
1201 if kc == 56 { // 'b' = back
1202 if histn > 0 {
1203 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 }
1204 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
1205 }
1206 }
1207 if kc == 58 { br_media_dump(store, cur_url, br_slen(cur_url)) } // 'm' = deep-harvest + panel the gif/vid/image sources
1208 if kc == 42 { // 'g' = play this page's game NATIVELY
1209 if glen > 0 {
1210 br_p(1, "nx_browser: launching native \x00" as *u8); sys_write(1, gname, glen); br_p(1, "\n\x00" as *u8)
1211 br_launch_game(gname, glen, 0)
1212 }
1213 if glen <= 0 { br_p(1, "nx_browser: no sovereign game on this page\n\x00" as *u8) }
1214 }
1215 if kc == 24 { running = 0 } // 'q'
1216 if kc == 9 { running = 0 } // Esc
1217 }
1218 if kind == 17 { running = 0 }
1219 if navurl == 1 {
1220 br_p(1, "nx_browser: loading...\n\x00" as *u8)
1221 if load_page(cur_url, br_slen(cur_url), store, cr, priv, page, WW) == 0 { if page.ok == 1 {
1222 scroll_y = 0
1223 max_scroll = page.page_h - (WH - NX_CHROME_H)
1224 if max_scroll < 0 { max_scroll = 0 }
1225 dirty = 1
1226 glen = br_game_organ(page.raw, page.raw_len, gname)
1227 if glen > 0 {
1228 br_p(1, "nx_browser: SOVEREIGN GAME page -- press g to play NATIVE (\x00" as *u8)
1229 sys_write(1, gname, glen); br_p(1, ")\n\x00" as *u8)
1230 }
1231 } }
1232 }
1233 if dirty == 1 {
1234 if scroll_y < 0 { scroll_y = 0 }
1235 if scroll_y > max_scroll { scroll_y = max_scroll }
1236 br_draw(cc, wid, gc, paper, page, WW, WH, scroll_y, fbody, fh2, fh1, cur_url, br_slen(cur_url))
1237 }
1238 }
1239 }
1240 sys_close(cc.fd)
1241 br_p(1, "nx_browser: closed\n\x00" as *u8)
1242 return 0
1243}