FrameBridge 0.3.1 community source release
This commit is contained in:
commit
b23819fff1
1354 files changed
+343716
No files matched your search
@@ -0,0 +1,85 @@
|
||||
"""Run the actual viewer JS under GJS with a small DOM fixture.
|
||||
|
||||
This tests JS state and integrity handling, not browser decoding, CSP or ChatGPT UI.
|
||||
"""
|
||||
import base64
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
from framebridge_expanded.real_screen.screen_presentation import VIEWER_HTML
|
||||
from test_screen_frontdoor import png
|
||||
|
||||
|
||||
@unittest.skipUnless(shutil.which("gjs"), "GJS is not installed on this test host")
|
||||
class ScreenViewerJavaScriptTests(unittest.TestCase):
|
||||
def test_original_bytes_recovery_errors_and_inflight_teardown(self):
|
||||
data = png()
|
||||
import hashlib
|
||||
payload = {
|
||||
"structuredContent": {
|
||||
"ok": True, "node": "LOCAL", "hostname": "test-fixture",
|
||||
"result": {
|
||||
"capture_id": "1" * 32, "sha256": hashlib.sha256(data).hexdigest(),
|
||||
"bytes": len(data), "width": 100, "height": 40,
|
||||
"captured_at": "2026-10-24T12:00:00Z",
|
||||
},
|
||||
},
|
||||
"_meta": {"framebridgeImage": {"mimeType": "image/png", "data": base64.b64encode(data).decode()}},
|
||||
}
|
||||
script = VIEWER_HTML.split("<script>", 1)[1].split("</script>", 1)[0]
|
||||
bootstrap = r"""
|
||||
const GLib=imports.gi.GLib;
|
||||
const listeners={},elements={};
|
||||
class Element {
|
||||
constructor(){this.hidden=true;this.disabled=true;this.textContent="";this.attrs={};const classes=new Set();this.classList={add:x=>classes.add(x),remove:x=>classes.delete(x),contains:x=>classes.has(x)};}
|
||||
setAttribute(k,v){this.attrs[k]=v;} removeAttribute(k){delete this.attrs[k];if(k==="src"||k==="href")this[k]="";}
|
||||
async decode(){const b=GLib.base64_decode(this.src.split(",")[1]);const d=new DataView(b.buffer,b.byteOffset,b.byteLength);this.naturalWidth=d.getUint32(16);this.naturalHeight=d.getUint32(20);}
|
||||
}
|
||||
const fakeDocument={getElementById:id=>elements[id]||(elements[id]=new Element()),body:{scrollHeight:640}};
|
||||
const fakeWindow={parent:{postMessage:()=>{}},addEventListener:(name,fn)=>{listeners[name]=fn;}};
|
||||
globalThis.setTimeout=()=>0;
|
||||
globalThis.atob=value=>Array.from(GLib.base64_decode(value),x=>String.fromCharCode(x)).join("");
|
||||
async function realDigest(name,bytes){if(name!=="SHA-256")throw Error("wrong algorithm");const hex=GLib.compute_checksum_for_data(GLib.ChecksumType.SHA256,bytes);return Uint8Array.from(hex.match(/../g),x=>parseInt(x,16)).buffer;}
|
||||
globalThis.crypto={subtle:{digest:realDigest}};
|
||||
let checks=0;
|
||||
function check(v,m){if(!v)throw Error(m);checks++;}
|
||||
"""
|
||||
cases = r"""
|
||||
return (async()=>{
|
||||
const fixture=PAYLOAD,original="data:image/png;base64,"+fixture._meta.framebridgeImage.data;
|
||||
await render(fixture);
|
||||
check(img.src===original,"original bytes changed");check(!img.hidden&&!viewport.hidden,"valid image hidden");
|
||||
check(statusEl.textContent.includes("Original PNG verified"),"integrity success missing");
|
||||
check(details.textContent.includes(fixture.structuredContent.result.sha256),"hash not displayed");
|
||||
check(download.href===original,"download bytes changed");
|
||||
native.onclick();check(viewport.classList.contains("native"),"native size unavailable");
|
||||
fit.onclick();check(!viewport.classList.contains("native"),"fit unavailable");
|
||||
await render({structuredContent:{ok:false,error:"TEST_ERROR"}});
|
||||
check(img.hidden&&viewport.hidden,"error did not hide image");check(active===null,"error retained active capture");
|
||||
check(!img.src&&!download.href&&!details.textContent,"error retained stale image data");
|
||||
await render(fixture);check(!img.hidden&&img.src===original,"same original did not recover after error");
|
||||
const bad=JSON.parse(JSON.stringify(fixture));bad.structuredContent.result.sha256="0".repeat(64);
|
||||
await render(bad);check(img.hidden&&statusEl.textContent.includes("SHA-256 mismatch"),"corrupt digest not rejected");
|
||||
await render(fixture);check(!img.hidden,"valid image did not recover after bad digest");
|
||||
const slow=JSON.parse(JSON.stringify(fixture));slow.structuredContent.result.capture_id="2".repeat(32);
|
||||
let finish;crypto.subtle.digest=(...args)=>new Promise(resolve=>{finish=()=>realDigest(...args).then(resolve);});
|
||||
const inflight=render(slow);check(typeof finish==="function","digest did not start");
|
||||
listeners.message({source:window.parent,data:{jsonrpc:"2.0",method:"ui/notifications/teardown",params:{}}});
|
||||
await finish();await inflight;
|
||||
check(img.hidden&&viewport.hidden&&!img.src,"inflight render revived after teardown");
|
||||
check(fit.disabled&&native.disabled&&full.disabled,"teardown left controls enabled");
|
||||
return checks;
|
||||
})();
|
||||
"""
|
||||
executable = bootstrap + "\nconst run = function(window,document,crypto,atob,setTimeout){" + script + cases.replace("PAYLOAD", json.dumps(payload)) + "};\n"
|
||||
executable += 'const loop=new GLib.MainLoop(null,false);let failed=false;run(fakeWindow,fakeDocument,globalThis.crypto,globalThis.atob,globalThis.setTimeout).then(n=>{print("PASS "+n+" viewer state assertions");loop.quit();}).catch(e=>{failed=true;printerr(String(e.stack||e));loop.quit();});loop.run();if(failed)imports.system.exit(1);'
|
||||
result = subprocess.run([shutil.which("gjs"), "-c", executable],
|
||||
capture_output=True, text=True, timeout=8)
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
self.assertIn("PASS 16 viewer state assertions", result.stdout)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user