1 """SAB verification: confirm SharedArrayBuffer + Atomics.wait work in a
2 COOP+COEP Worker context.
3 4 This is the go/no-go gate for the SAB channel substrate used by spawn().
5 If this fails, blocking channel_recv must be replaced with a MessagePort
6 async fallback - the entire channel ABI changes.
7 8 The signer no longer runs in a browser extension; it is an in-page WASM
9 Worker. The Firefox MV2 extension SAB test has been removed - that question
10 (SAB in extension origins) is no longer relevant to the architecture.
11 12 Run:
13 python3 -m pytest test/test_sab_verify.py -v
14 """
15 16 import http.server
17 import os
18 import socketserver
19 import sys
20 import tempfile
21 import threading
22 import time
23 24 import pytest
25 26 PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
27 28 # HTML + worker JS served with COOP/COEP for the Chrome web-page proxy test.
29 _SAB_PAGE_HTML = b"""<!DOCTYPE html>
30 <html><body>
31 <div id="status">running...</div>
32 <script>
33 const w = new Worker('worker.js');
34 w.onmessage = function(e) {
35 window.__sabResult = e.data;
36 document.getElementById('status').textContent = JSON.stringify(e.data);
37 };
38 w.onerror = function(e) {
39 window.__sabResult = {error: 'worker error: ' + e.message};
40 document.getElementById('status').textContent = window.__sabResult.error;
41 };
42 </script>
43 </body></html>
44 """
45 46 _SAB_WORKER_JS = b"""
47 const r = {
48 crossOriginIsolated: self.crossOriginIsolated,
49 hasSABType: typeof SharedArrayBuffer !== 'undefined',
50 canCreate: false,
51 atomicsWait: null,
52 error: null
53 };
54 try {
55 const sab = new SharedArrayBuffer(4);
56 r.canCreate = true;
57 const i32 = new Int32Array(sab);
58 r.atomicsWait = Atomics.wait(i32, 0, 0, 0);
59 } catch(e) {
60 r.error = e.message;
61 }
62 self.postMessage(r);
63 """
64 65 66 class _CoopHandler(http.server.BaseHTTPRequestHandler):
67 def log_message(self, *args): pass
68 69 def do_GET(self):
70 if self.path == "/" or self.path == "/index.html":
71 body, ctype = _SAB_PAGE_HTML, b"text/html"
72 elif self.path == "/worker.js":
73 body, ctype = _SAB_WORKER_JS, b"application/javascript"
74 else:
75 self.send_response(404); self.end_headers(); return
76 self.send_response(200)
77 self.send_header("Content-Type", ctype.decode())
78 self.send_header("Content-Length", str(len(body)))
79 self.send_header("Cross-Origin-Opener-Policy", "same-origin")
80 self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
81 self.end_headers()
82 self.wfile.write(body)
83 84 85 def _start_coop_server():
86 srv = socketserver.TCPServer(("127.0.0.1", 0), _CoopHandler)
87 t = threading.Thread(target=srv.serve_forever, daemon=True)
88 t.start()
89 return srv, srv.server_address[1]
90 91 92 # ── Chrome test (Playwright + COOP/COEP web page) ─────────────────────────
93 94 95 class TestChromeSAB:
96 """Chrome: SAB in a Worker on a COOP+COEP page.
97 98 The Chrome MV3 extension manifest declares `cross_origin_opener_policy`
99 and `cross_origin_embedder_policy` with value "same-origin" / "require-corp".
100 These keys apply those headers to all extension pages, producing the same
101 crossOriginIsolated=true context as a web page served with those headers.
102 This test validates that Chrome grants SAB+Atomics.wait in Workers under
103 that condition, which is the proxy for what the offscreen document will see.
104 """
105 106 def test_sab_in_coop_worker(self):
107 """SharedArrayBuffer and Atomics.wait work in a Chrome Worker under COOP+COEP."""
108 from playwright.sync_api import sync_playwright
109 110 srv, port = _start_coop_server()
111 try:
112 with sync_playwright() as pw:
113 browser = pw.chromium.launch(headless=True, args=["--no-sandbox"])
114 page = browser.new_page()
115 page.goto(f"http://127.0.0.1:{port}/", wait_until="domcontentloaded")
116 page.wait_for_function("window.__sabResult !== undefined", timeout=8000)
117 result = page.evaluate("window.__sabResult")
118 browser.close()
119 finally:
120 srv.shutdown()
121 122 assert result is not None, "No result from Worker"
123 assert result.get("error") is None, f"Worker error: {result.get('error')}"
124 assert result.get("crossOriginIsolated") is True, \
125 f"crossOriginIsolated not true in Chrome COOP+COEP Worker: {result}"
126 assert result.get("canCreate") is True, \
127 f"SharedArrayBuffer creation failed in Chrome COOP+COEP Worker: {result}"
128 assert result.get("atomicsWait") in ("timed-out", "ok"), \
129 f"Atomics.wait did not work in Chrome COOP+COEP Worker: {result}"
130 131 132 # The Firefox MV2 extension SAB test was removed. The signer now runs as an
133 # in-page WASM Worker, not a browser extension. SAB availability in extension
134 # origins is no longer a constraint for this codebase.
135