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}