Browser security prevents parent pages from capturing iframe content:
- Python Screenshot API: Times out with embedded iframes
- html2canvas: Can only see the iframe container, not its rendered content
- Proxy approach: Breaks Neuroglancer's Server-Sent Events connection
Instead of trying to capture the iframe from the outside, we have the iframe capture itself and send the screenshot to the parent via postMessage.
┌─────────────────────────────────────┐
│ Parent Page (app.js) │
│ │
│ 1. Send postMessage → │
│ {type: 'captureScreenshot'} │
│ │
│ 3. Receive postMessage ← │
│ {type: 'screenshot', │
│ jpeg_b64: '...'} │
│ │
│ 4. Display + Send to Server │
└─────────────────────────────────────┘
↕ postMessage
┌─────────────────────────────────────┐
│ Neuroglancer Iframe │
│ (ng-screenshot-handler.js) │
│ │
│ 2. Capture from canvas → │
│ document.querySelector('canvas')│
│ .toDataURL('image/jpeg') │
└─────────────────────────────────────┘
- Parent page (
app.js) sends apostMessageto the iframe requesting a screenshot - Iframe script (
ng-screenshot-handler.js) listens for this message - Iframe script finds the Neuroglancer canvas and uses
.toDataURL()to capture it - Iframe script sends the screenshot back via
postMessage - Parent page receives the screenshot, displays it, and forwards to server
✅ Works with iframe isolation - Captures from inside the iframe
✅ No cross-origin issues - postMessage works across origins
✅ Doesn't break Neuroglancer - No proxy for SSE needed
✅ Direct canvas capture - Gets actual rendered WebGL content
Script that runs inside the Neuroglancer iframe to handle screenshot capture.
Key functions:
- Waits for Neuroglancer to load (detects canvas)
- Listens for
captureScreenshotmessages from parent - Captures screenshot from Neuroglancer's canvas element
- Sends screenshot back via postMessage
Modified screenshot capture flow:
Changes:
- Removed
html2canvasapproach - Added
setupMessageListener()to receive screenshots from iframe - Added
requestScreenshot()to request screenshots via postMessage - Added
handleScreenshotFromIframe()to process received screenshots - Added
injectScreenshotHandler()to inject script into iframe (if same-origin)
Added script injection to proxy:
Changes:
- Proxy now injects
ng-screenshot-handler.jsinto Neuroglancer HTML responses - Ensures the script loads even with cross-origin iframes
- Removed
html2canvasdependency (no longer needed)
cd /groups/cellmap/cellmap/ackermand/Programming/tourguide
conda activate igneous_daskified
python server/main.pyExpected output:
Neuroglancer viewer URL: http://...
FastAPI server starting on http://0.0.0.0:8090
Navigate to: http://localhost:8090/
Open Developer Tools → Console. You should see:
[MESSAGE] Neuroglancer iframe is ready
[SCREENSHOT] Iframe not ready ← Initial attempts while loading
[MESSAGE] Received screenshot from iframe
[SCREENSHOT] Sent to server
- Live Screenshot panel should show the Neuroglancer view
- Frame count should increment (~1 fps)
- Server console should show:
[SCREENSHOT] Received from client: XXXX bytes
Pan/zoom in the Neuroglancer viewer. Screenshots should update to show the new view.
Cause: Script not loaded yet
Fix: Wait 3-5 seconds after page load
Check 1: Browser console for errors
Check 2: Check if proxy is injecting script:
# In browser network tab, look for HTML response
# Should contain: <script src="/static/ng-screenshot-handler.js"></script>
Check 3: Try requesting manually in console:
document.getElementById('ng-iframe').contentWindow.postMessage({
type: 'captureScreenshot', width: 800, height: 600
}, '*');If you see [PROXY ERROR] in server console:
- Check that Neuroglancer is running (should see URL printed on startup)
- Check that port 9999 is accessible
- Try accessing Neuroglancer directly:
http://localhost:9999/
If the proxy approach causes issues, you can switch to direct Neuroglancer URL:
In server/stream.py, change:
return {"url": f"/ng-proxy/{path}"}To:
return {"url": ng_tracker.get_url()}Trade-off: This avoids proxy complexity but requires CORS headers or same-origin setup.
Once screenshots are working:
- Tune screenshot rate: Adjust
screenshotFpsinapp.js(currently 1 fps) - Add screenshot to narration: Pass screenshots to AI narrator
- Optimize image size: Reduce quality/resolution if needed
- Add screenshot history: Store recent screenshots for comparison
The key insight: Browser security blocks external access to iframe rendering, but the iframe can always capture itself.
By having the iframe do the work and communicate via postMessage, we bypass all the security restrictions that blocked previous approaches.