code wiki / _hdl_build / nx_browser_bg_gate.nx

nx_browser_bg_gate.nx source

↩ module page · 271 lines · 20051 B

1// nx_browser_bg_gate.nx -- proves the Nishi Browser box painter fills element backgrounds given as rgb() (and 2// the fleet var(--token) that resolves to rgb()), through the REAL browser render path (br_layout + br_draw_fb). 3// ROOT CAUSE this locks: br_comp_bg's `background` SHORTHAND branch was hex-only, so the office chrome 4// (buttons/.fico badges use `background:var(--acc)` -> `background:rgb(...)`) painted NOTHING natively -- the live 5// office rendered monochrome in our own browser while Waterfox was fine. license_tier: ORIGINAL expect_exit: 0 6import "nx_browser_render.nx" 7 8func p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 9func bg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 10// render hlen bytes at viewport render_w; return the accent X-SPAN (maxx-minx) in the hint's y-band. This is 11// the CORRECT overflow metric: at a MULTI-COLUMN width (>=800px, so minmax(230px) makes real ~250px columns), 12// a hint that WRAPS stays within its ~250px column (small x-span); one that OVERFLOWS spans much wider. (My 13// earlier 400px y-span tests were a CALIBRATION ARTIFACT: minmax(230px)@400px = ONE wide column, never repro.) 14func bg_xspan_at(html: *u8, hlen: i64, render_w: i64) -> i64 { 15 let page: *Page = sys_mmap(NX_PAGE_BYTES) as *Page 16 page.raw = html 17 page.raw_len = hlen 18 page.dark_mode = 0 19 br_layout(page, render_w) 20 page.vec_headings = 0 21 var H: i64 = page.page_h + NX_CHROME_H + 40 22 if H < 120 { H = 120 } 23 if H > 4000 { H = 4000 } 24 let pxb: *u8 = sys_mmap((render_w * H * 4) as nx_size) 25 let fb: *Framebuffer = sys_mmap(NX_FRAMEBUFFER_BYTES as nx_size) as *Framebuffer 26 nx_framebuffer_init(fb, pxb, render_w, H) 27 nx_framebuffer_clear(fb) 28 br_draw_fb(fb, page, render_w, "http://t\x00" as *u8, 8, 1) 29 let pxc: *CssColor = sys_mmap(NX_CSS_COLOR_BYTES as nx_size) as *CssColor 30 var minx: i64 = 0 - 1 31 var maxx: i64 = 0 - 1 32 var y: i64 = 0 33 while y < H { 34 var x: i64 = 0 35 while x < render_w { 36 nx_framebuffer_get_pixel(fb, x, y, pxc) 37 var dr: i64 = (pxc.r as i64) - 41 38 if dr < 0 { dr = 0 - dr } 39 var dg: i64 = (pxc.g as i64) - 84 40 if dg < 0 { dg = 0 - dg } 41 var db: i64 = (pxc.b as i64) - 164 42 if db < 0 { db = 0 - db } 43 if dr <= 14 { if dg <= 14 { if db <= 14 { if minx < 0 { minx = x } if x > maxx { maxx = x } } } } 44 x = x + 2 45 } 46 y = y + 2 47 } 48 if minx < 0 { return 0 - 1 } 49 return maxx - minx 50} 51func bg_accent(html: *u8) -> i64 { return bg_accent_n(html, bg_slen(html)) } 52// render hlen bytes of html through the browser core; return count of ~rgb(41,84,164) accent pixels. 53func bg_accent_n(html: *u8, hlen: i64) -> i64 { 54 let page: *Page = sys_mmap(NX_PAGE_BYTES) as *Page 55 page.raw = html 56 page.raw_len = hlen 57 page.dark_mode = 0 58 br_layout(page, 400) 59 page.vec_headings = 0 60 var H: i64 = page.page_h + NX_CHROME_H + 40 61 if H < 120 { H = 120 } 62 if H > 4000 { H = 4000 } 63 let px: *u8 = sys_mmap((400 * H * 4) as nx_size) 64 let fb: *Framebuffer = sys_mmap(NX_FRAMEBUFFER_BYTES as nx_size) as *Framebuffer 65 nx_framebuffer_init(fb, px, 400, H) 66 nx_framebuffer_clear(fb) 67 br_draw_fb(fb, page, 400, "http://t\x00" as *u8, 8, 1) 68 let pxc: *CssColor = sys_mmap(NX_CSS_COLOR_BYTES as nx_size) as *CssColor 69 var acc: i64 = 0 70 var y: i64 = 0 71 while y < H { 72 var x: i64 = 0 73 while x < 400 { 74 nx_framebuffer_get_pixel(fb, x, y, pxc) 75 var dr: i64 = (pxc.r as i64) - 41 76 if dr < 0 { dr = 0 - dr } 77 var dg: i64 = (pxc.g as i64) - 84 78 if dg < 0 { dg = 0 - dg } 79 var db: i64 = (pxc.b as i64) - 164 80 if db < 0 { db = 0 - db } 81 if dr <= 14 { if dg <= 14 { if db <= 14 { acc = acc + 1 } } } 82 x = x + 2 83 } 84 y = y + 2 85 } 86 return acc 87} 88// render html; return the accent-pixel Y-SPAN (max_y - min_y). Children laid SIDE-BY-SIDE share a row -> small 89// span; STACKED children span many rows -> large span. Discriminates real grid/flex row layout from stacking. 90func bg_yspan(html: *u8) -> i64 { 91 let hlen: i64 = bg_slen(html) 92 let page: *Page = sys_mmap(NX_PAGE_BYTES) as *Page 93 page.raw = html 94 page.raw_len = hlen 95 page.dark_mode = 0 96 br_layout(page, 400) 97 page.vec_headings = 0 98 var H: i64 = page.page_h + NX_CHROME_H + 40 99 if H < 120 { H = 120 } 100 if H > 4000 { H = 4000 } 101 let pxb: *u8 = sys_mmap((400 * H * 4) as nx_size) 102 let fb: *Framebuffer = sys_mmap(NX_FRAMEBUFFER_BYTES as nx_size) as *Framebuffer 103 nx_framebuffer_init(fb, pxb, 400, H) 104 nx_framebuffer_clear(fb) 105 br_draw_fb(fb, page, 400, "http://t\x00" as *u8, 8, 1) 106 let pxc: *CssColor = sys_mmap(NX_CSS_COLOR_BYTES as nx_size) as *CssColor 107 var miny: i64 = 0 - 1 108 var maxy: i64 = 0 - 1 109 var y: i64 = 0 110 while y < H { 111 var x: i64 = 0 112 while x < 400 { 113 nx_framebuffer_get_pixel(fb, x, y, pxc) 114 var dr: i64 = (pxc.r as i64) - 41 115 if dr < 0 { dr = 0 - dr } 116 var dg: i64 = (pxc.g as i64) - 84 117 if dg < 0 { dg = 0 - dg } 118 var db: i64 = (pxc.b as i64) - 164 119 if db < 0 { db = 0 - db } 120 if dr <= 14 { if dg <= 14 { if db <= 14 { if miny < 0 { miny = y } maxy = y } } } 121 x = x + 2 122 } 123 y = y + 2 124 } 125 if miny < 0 { return 0 - 1 } 126 return maxy - miny 127} 128func bg_ck(name: *u8, cond: i64, cnt: i64, pass: *i64, fail: *i64) -> i64 { 129 if cond == 1 { p(" PASS " as *u8) } else { p(" FAIL " as *u8) } 130 p(name) 131 p(" (accent-px=" as *u8) 132 var v: i64 = cnt 133 let t: *u8 = sys_mmap(16) 134 var k: i64 = 0 135 if v == 0 { t[0] = 48 as u8; k = 1 } 136 while v > 0 { t[k] = (48 + (v % 10)) as u8; v = v / 10; k = k + 1 } 137 var z: i64 = 0 138 while z < k { let c: *u8 = sys_mmap(2); c[0] = t[k - 1 - z]; sys_write(1, c, 1); z = z + 1 } 139 p(")\n" as *u8) 140 if cond == 1 { pass[0] = pass[0] + 1; return 1 } 141 fail[0] = fail[0] + 1 142 return 0 143} 144func main() -> i64 { 145 p("=== NX-BROWSER-BG GATE (native element-background painting, rgb() + fleet var tokens) ===\n" as *u8) 146 let pass: *i64 = sys_mmap(16) as *i64 147 let fail: *i64 = sys_mmap(16) as *i64 148 pass[0] = 0 149 fail[0] = 0 150 151 let a1: i64 = bg_accent("<html><head><style>p{background-color:rgb(41,84,164)}</style></head><body><p>accent fill line accent fill line accent fill line accent</p></body></html>" as *u8) 152 bg_ck("T1 background-color:rgb() fills (baseline)" as *u8, (a1 > 0) as i64, a1, pass, fail) 153 154 let a2: i64 = bg_accent("<html><head><style>p{background:rgb(41,84,164)}</style></head><body><p>accent fill line accent fill line accent fill line accent</p></body></html>" as *u8) 155 bg_ck("T2 background:rgb() SHORTHAND fills (office chrome)" as *u8, (a2 > 0) as i64, a2, pass, fail) 156 157 let a3: i64 = bg_accent("<html><head><style>:root{--acc:rgb(41,84,164)}p{background:var(--acc)}</style></head><body><p>accent fill line accent fill line accent fill line accent</p></body></html>" as *u8) 158 bg_ck("T3 background:var(--acc) SHORTHAND+token fills (the exact office pattern)" as *u8, (a3 > 0) as i64, a3, pass, fail) 159 160 // T4/T5: is the office monochrome render a LAYOUT collapse? accent child inside display:grid / display:flex. 161 let a4: i64 = bg_accent("<html><head><style>.g{display:grid}.b{background:rgb(41,84,164);padding-top:20px;padding-bottom:20px}</style></head><body><div class='g'><div class='b'>accent grid child accent grid child accent</div></div></body></html>" as *u8) 162 bg_ck("T4 accent child inside display:GRID still fills (office .newrow/.fgrid)" as *u8, (a4 > 0) as i64, a4, pass, fail) 163 164 let a5: i64 = bg_accent("<html><head><style>.f{display:flex}.b{background:rgb(41,84,164);padding-top:20px;padding-bottom:20px}</style></head><body><div class='f'><div class='b'>accent flex child accent flex child accent</div></div></body></html>" as *u8) 165 bg_ck("T5 accent child inside display:FLEX still fills (office .top/button rows)" as *u8, (a5 > 0) as i64, a5, pass, fail) 166 167 // T8 DIAGNOSTIC: display:none on a <textarea> (raw-text el) must hide it + its content -> 0 accent. The 168 // office .starter{display:none} textareas were LEAKING their spec text in the native render. 169 let a8: i64 = bg_accent("<html><head><style>.starter{display:none;background:rgb(41,84,164);padding-top:20px;padding-bottom:20px}</style></head><body><p>before</p><textarea class='starter'>H hidden spec P should not show</textarea><p>after</p></body></html>" as *u8) 170 bg_ck("T8 display:none hides a <textarea> + its raw content (0 accent)" as *u8, (a8 == 0) as i64, a8, pass, fail) 171 172 // T8b LOCALIZE: office-exact textarea = class BEFORE a second attr (name), content with a newline. 173 let a8b: i64 = bg_accent("<html><head><style>.starter{display:none;background:rgb(41,84,164);padding-top:20px;padding-bottom:20px}</style></head><body><p>before</p><textarea class='starter' name='spec'>H Untitled\nP Start here</textarea><p>after</p></body></html>" as *u8) 174 bg_ck("T8b display:none hides textarea with class+name attrs (0 accent)" as *u8, (a8b == 0) as i64, a8b, pass, fail) 175 176 // T7 GRID ROW LAYOUT: 4 accent children (~30px tall) in repeat(auto-fit,minmax(80px,1fr)) at 400px = 4 177 // columns -> ONE row (y-span ~ one child). The old parser read auto-fit as 1 col -> 4 STACKED rows (y-span 178 // ~4x). So a SMALL y-span proves side-by-side. (b children each 30px: side-by-side<=60, stacked>=120.) 179 let ys: i64 = bg_yspan("<html><head><style>.g{display:grid;grid-template-columns:repeat(auto-fit,minmax(80px,1fr))}.b{background:rgb(41,84,164);padding-top:14px;padding-bottom:14px}</style></head><body><div class='g'><div class='b'>a</div><div class='b'>b</div><div class='b'>c</div><div class='b'>d</div></div></body></html>" as *u8) 180 bg_ck("T7 repeat(auto-fit,minmax()) lays children SIDE-BY-SIDE (small accent y-span)" as *u8, (ys >= 0) as i64 & (ys < 90) as i64, ys, pass, fail) 181 182 // T10 DIAGNOSTIC (characterize the tile-hint overflow): a LONG accent-bg paragraph in a ~90px grid column. 183 // If text WRAPS to the column width, the bg box is TALL (many lines -> big y-span); if it does NOT wrap, the 184 // box is one short line that overflows sideways (small y-span). This measures whether narrow-column block 185 // text wraps in the native engine. (~55 chars at ~9px = ~500px; at 90px that's ~6 wrapped lines if wrapping.) 186 let yw: i64 = bg_yspan("<html><head><style>.g{display:grid;grid-template-columns:repeat(auto-fit,minmax(90px,1fr))}.b{background:rgb(41,84,164)}</style></head><body><div class='g'><div><p class='b'>headlines bold tables opens in word real formulas sum average</p></div><div>x</div><div>y</div><div>z</div></div></body></html>" as *u8) 187 bg_ck("T10 DIAG narrow-column block text wraps (tall bg box, y-span>40)" as *u8, (yw > 40) as i64, yw, pass, fail) 188 189 // T10b PIN: office-exact nesting = grid item > <form> > <p> (T10 had the p DIRECTLY in the item). If this 190 // does NOT wrap (small y-span) while T10 does, width propagation breaks through the <form> wrapper. 191 let ywf: i64 = bg_yspan("<html><head><style>.g{display:grid;grid-template-columns:repeat(auto-fit,minmax(90px,1fr))}.b{background:rgb(41,84,164)}</style></head><body><div class='g'><div><form><p class='b'>headlines bold tables opens in word real formulas sum average</p></form></div><div>x</div><div>y</div><div>z</div></div></body></html>" as *u8) 192 bg_ck("T10b DIAG same but nested in <form> wraps too (isolates the form wrapper)" as *u8, (ywf > 40) as i64, ywf, pass, fail) 193 194 // (An earlier T10c-T10j maze bisected at 400px and produced a CALIBRATION ARTIFACT: minmax(230px)@400px 195 // collapses to ONE wide column, so "office-shape" hints there measured a spurious small y-span. Retired -- 196 // the CORRECT-width locks are T11/T12/T14/T15 below. The lesson: match the render width to the layout.) 197 198 // T11 THE REAL TEST (multi-column width): office tile at 1000px where minmax(230px,1fr) makes a ~250px 199 // column. A WRAPPED hint's accent bg stays within its column (x-span < ~260); an OVERFLOWING one spans much 200 // wider. This is what my 400px tests structurally could NOT see. (accent-bg on the hint <p>.) 201 let of1: *u8 = "<html><head><style>.g{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr))}.newt{border:1px solid rgb(200,200,200);padding-left:14px;padding-right:14px}.newt h3{display:flex;align-items:center;gap:8px}.b{background:rgb(41,84,164);margin:0}</style></head><body><div class='g'><div class='newt'><h3><span>W</span>New doc</h3><p class='b'>Headlines bold tables opens in word real formulas averages</p></div><div class='newt'>x</div><div class='newt'>y</div></div></body></html>" as *u8 202 let xs1: i64 = bg_xspan_at(of1, bg_slen(of1), 1000) 203 bg_ck("T11 office hint at 1000px stays within its ~250px column (x-span < 300)" as *u8, (xs1 >= 0) as i64 & (xs1 < 300) as i64, xs1, pass, fail) 204 205 // T12 THE REAL NESTING: hint is inside a <form> (grid child > form > p), exactly like of_form emits. If this 206 // OVERFLOWS at 1000px while T11 (no form) wraps, width propagation through the <form> at a multi-column 207 // width is the bug (my earlier T10b "form OK" was the 400px artifact). 208 let of2: *u8 = "<html><head><style>.g{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr))}.newt{border:1px solid rgb(200,200,200);padding-left:14px;padding-right:14px}.newt h3{display:flex;align-items:center;gap:8px}.b{background:rgb(41,84,164);margin:0}</style></head><body><div class='g'><div class='newt'><h3><span>W</span>New doc</h3><form><input type='hidden' name='spec' value='x'><p class='b'>Headlines bold tables opens in word real formulas averages</p></form></div><div class='newt'>x</div><div class='newt'>y</div></div></body></html>" as *u8 209 let xs2: i64 = bg_xspan_at(of2, bg_slen(of2), 1000) 210 bg_ck("T12 hint INSIDE <form> at 1000px stays within its column (x-span < 300)" as *u8, (xs2 >= 0) as i64 & (xs2 < 300) as i64, xs2, pass, fail) 211 212 // (A T13 tried to measure the REAL 3-tile page's hint x-span, but x-span = maxx-minx conflates CORRECT 213 // column-spread (3 hints across 3 columns naturally span ~900px) with a single-hint overflow -- retired as 214 // a bad metric. Per-column isolation would be needed to pin the real 3-tile overlap; deferred as a minor rung.) 215 216 // T14 the hint carries an inline style='margin:0' attr (real page does). Measured 217 // as a SINGLE tile so x-span is one hint. If inline-style parsing mis-sizes the box -> overflow (>300). 218 let of4: *u8 = "<html><head><style>.g{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:12px}.newt{border:1px solid rgb(200,200,200);padding-left:14px;padding-right:14px}.newt h3{display:flex;align-items:center;gap:8px}.b{background:rgb(41,84,164)}</style></head><body><div class='g'><div class='newt'><h3><span>W</span>New doc</h3><form><input type='hidden' name='spec' value='x'><p class='b' style='margin:0'>Headlines bold tables opens in word real formulas averages</p></form></div></div></body></html>" as *u8 219 let xs4: i64 = bg_xspan_at(of4, bg_slen(of4), 1000) 220 bg_ck("T14 single tile, inline style='margin:0' hint at 1000px stays column-bounded (x-span < 300)" as *u8, (xs4 >= 0) as i64 & (xs4 < 300) as i64, xs4, pass, fail) 221 222 // T15 FULL REAL NESTING DEPTH, single tile: body > main.wrap(max-width) > div.newrow > div.newt > form > p. 223 // The real page nests deeper than my shallow synthetics; if width propagation DEGRADES with depth this 224 // overflows (>300) -> a REAL engine bug (affects many pages), worth fixing. If it wraps, the office overlap 225 // is a real-page-only quirk I could not isolate cheaply. 226 let of5: *u8 = "<html><head><style>.wrap{max-width:1200px;margin:0 auto;padding:16px 20px}.newrow{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:12px}.newt{border:1px solid rgb(200,200,200);padding:12px 14px}.newt h3{display:flex;align-items:center;gap:8px}.b{background:rgb(41,84,164);margin:0}</style></head><body><main class='wrap'><div class='newrow'><div class='newt'><h3><span>W</span>New doc</h3><form><input type='hidden' name='spec' value='x'><p class='b'>Headlines bold tables opens in word real formulas averages</p></form></div></div></main></body></html>" as *u8 227 let xs5: i64 = bg_xspan_at(of5, bg_slen(of5), 1000) 228 bg_ck("T15 FULL real nesting depth single tile at 1000px stays column-bounded (x-span < 300)" as *u8, (xs5 >= 0) as i64 & (xs5 < 300) as i64, xs5, pass, fail) 229 230 // T16 .fico BADGE (the office fix): a <div class='fico'> (BLOCK kind) flex-item in a display:flex h3 PAINTS 231 // its 26x26 accent square. (26x26 at 2px sampling = ~169 cells, minus the white letter -> ~150+; >120 safe.) 232 let ficd: *u8 = "<html><head><style>.hd{display:flex;align-items:center;gap:8px}.fico{width:26px;height:26px;background:rgb(41,84,164);display:flex;align-items:center;justify-content:center;color:rgb(255,255,255)}</style></head><body><div class='newt'><h3 class='hd'><div class='fico'>W</div>New doc</h3></div></body></html>" as *u8 233 let afd: i64 = bg_accent_n(ficd, bg_slen(ficd)) 234 bg_ck("T16 <div> badge (office fix) paints its 26x26 accent square (>120)" as *u8, (afd > 120) as i64, afd, pass, fail) 235 // T16b ENGINE FIX: a <span> (INLINE tag) with display:flex is now PROMOTED to BLOCK kind after cascade 236 // (rh_promote_display_kinds) -> it gets a sized box AND its bg paints. Was 0 before the fix; now paints its 237 // 26x26 square like the <div>. This is the foundational fix (box kind from tag ignored CSS display); it also 238 // covers any display:flex/grid/block-overridden inline element, not just badges. 239 let fics: *u8 = "<html><head><style>.hd{display:flex;align-items:center;gap:8px}.fico{width:26px;height:26px;background:rgb(41,84,164);display:flex;align-items:center;justify-content:center;color:rgb(255,255,255)}</style></head><body><div class='newt'><h3 class='hd'><span class='fico'>W</span>New doc</h3></div></body></html>" as *u8 240 let afs: i64 = bg_accent_n(fics, bg_slen(fics)) 241 bg_ck("T16b ENGINE FIX: <span display:flex> promoted to BLOCK -> badge bg now paints (>120)" as *u8, (afs > 120) as i64, afs, pass, fail) 242 243 // T6: the ACTUAL live office HTML (fetched to office_live_snap.html) through the browser core -- if this 244 // paints accent, the earlier live-shot 0 was a SHOT-WINDOW artifact, not a render failure. Skips clean if 245 // the snapshot file is absent (so the gate stays runnable without a fetch). 246 let lp: *i64 = sys_mmap(16) as *i64 247 let ohtml: *u8 = sys_read_file("office_live_snap.html\x00" as *u8, lp) 248 if lp[0] > 0 { 249 let a6: i64 = bg_accent_n(ohtml, lp[0]) 250 bg_ck("T6 LIVE office HTML paints accent chrome in the browser core" as *u8, (a6 > 0) as i64, a6, pass, fail) 251 } else { 252 p(" SKIP T6 (no office_live_snap.html)\n" as *u8) 253 } 254 255 // NOTE: the office's earlier starter-leak (a display:none <textarea> rendering its spec text in the 256 // full-page cascade -- a box<->element mapping drift in complex trees with raw-text elements) is now 257 // fixed AT SOURCE: of_form emits an <input type=hidden> (correct for a hidden default value; renders 258 // nothing in every engine). The underlying engine mapping bug is documented as a named rung. 259 260 p("NX-BROWSER-BG-GATE pass=" as *u8) 261 var pv: i64 = pass[0] 262 let tp: *u8 = sys_mmap(16) 263 var pk: i64 = 0 264 if pv == 0 { tp[0] = 48 as u8; pk = 1 } 265 while pv > 0 { tp[pk] = (48 + (pv % 10)) as u8; pv = pv / 10; pk = pk + 1 } 266 var pz: i64 = 0 267 while pz < pk { let c3: *u8 = sys_mmap(2); c3[0] = tp[pk - 1 - pz]; sys_write(1, c3, 1); pz = pz + 1 } 268 if fail[0] == 0 { p(" fail=0 verdict=GREEN\n" as *u8); sys_exit(0); return 0 } 269 p(" fail>0 verdict=RED\n" as *u8); sys_exit(1) 270 return 1 271}