Skip to content

Commit a056261

Browse files
committed
fix(embed): create emitter inside iframe load event
Emitter was created immediately after setting srcdoc, before the iframe document had loaded. With srcdoc, contentWindow can reference a stale browsing context during navigation, causing CHILD_READY to be missed or source-check to fail. The fix follows the pattern from the reference example: add a 'load' listener on the iframe, create the emitter inside it so contentWindow is stable and CHILD_READY is already queued for delivery. https://claude.ai/code/session_014XkyCi4sXUHdDjMstV2Pbt
1 parent 9216d18 commit a056261

1 file changed

Lines changed: 29 additions & 48 deletions

File tree

docs/embed.html

Lines changed: 29 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,36 @@
99
</script>
1010
<style>
1111
*, *::before, *::after { box-sizing: border-box; margin: 0; }
12-
body { font-family: system-ui, -apple-system, sans-serif; padding: 2rem; background: #fafafa; color: #18181b; }
13-
h1 { font-size: 1rem; font-weight: 600; margin-bottom: 1rem; }
14-
.bar { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; }
15-
button {
16-
padding: 6px 16px; border-radius: 6px; border: 1px solid #e4e4e7;
17-
background: #18181b; color: #fff; font-size: 13px; cursor: pointer;
18-
}
12+
body { font-family: system-ui, -apple-system, sans-serif; padding: 2rem; background: #fafafa; }
13+
h1 { font-size: 1rem; font-weight: 600; margin-bottom: 1rem; }
14+
.bar { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; }
15+
button { padding: 6px 16px; border-radius: 6px; border: 1px solid #e4e4e7;
16+
background: #18181b; color: #fff; font-size: 13px; cursor: pointer; }
1917
button:disabled { opacity: 0.35; cursor: not-allowed; }
2018
#status { font-size: 12px; font-family: monospace; color: #71717a; }
21-
iframe {
22-
display: block; width: 100%; height: 480px;
23-
border: 1px solid #e4e4e7; border-radius: 10px;
24-
background: #fff;
25-
}
19+
iframe { display: block; width: 100%; height: 480px;
20+
border: 1px solid #e4e4e7; border-radius: 10px; background: #fff; }
2621
</style>
2722
</head>
2823
<body>
2924

3025
<h1>iframe-flight × Vega-Lite — single file, no install</h1>
31-
3226
<div class="bar">
3327
<button id="btn" disabled>New snapshot</button>
34-
<span id="status">Connecting</span>
28+
<span id="status">Loading</span>
3529
</div>
36-
3730
<iframe id="frame"></iframe>
3831

3932
<script type="module">
4033
import { ArrowParentEmitter, tableFromArrays, tableToIPC } from 'iframe-flight';
4134

42-
// ── Child iframe HTML — injected via srcdoc, no separate file needed ──────
35+
// ── Child iframe HTML ─────────────────────────────────────────────────────
4336
const CHILD = `<!DOCTYPE html>
4437
<html><head><meta charset="UTF-8">
4538
<script type="importmap">
46-
{"imports":{"iframe-flight":"https://ihatexcel.github.io/iframe-flight/iframe-flight.esm.js"}}
47-
<\/script>
48-
<style>body{margin:0;padding:12px 16px;font-family:system-ui}<\/style>
49-
</head><body>
39+
{"imports":{"iframe-flight":"https://ihatexcel.github.io/iframe-flight/iframe-flight.esm.js"}}
40+
<\/script></head>
41+
<body style="margin:0;padding:16px;font-family:system-ui">
5042
<div id="c"></div>
5143
<script type="module">
5244
import { ArrowChildReceiver } from 'iframe-flight';
@@ -65,22 +57,14 @@ <h1>iframe-flight × Vega-Lite — single file, no install</h1>
6557
data: { values: rows },
6658
mark: { type: 'bar', cornerRadiusEnd: 5 },
6759
encoding: {
68-
y: { field: 'product', type: 'nominal', sort: '-x', title: null,
69-
axis: { labelFontSize: 13 } },
70-
x: { field: 'revenue', type: 'quantitative', title: 'Revenue ($M)',
71-
axis: { gridColor: '#f4f4f5', labelColor: '#71717a', titleColor: '#71717a' } },
60+
y: { field: 'product', type: 'nominal', sort: '-x', title: null },
61+
x: { field: 'revenue', type: 'quantitative', title: 'Revenue ($M)' },
7262
color: { field: 'growth', type: 'quantitative', title: 'Growth %',
73-
scale: { scheme: 'redyellowgreen', domain: [-25, 25] },
74-
legend: { orient: 'right', format: '+.0f', labelFontSize: 10, titleFontSize: 10 } },
75-
tooltip: [
76-
{ field: 'product' },
77-
{ field: 'revenue', title: 'Revenue ($M)', format: '.1f' },
78-
{ field: 'growth', title: 'Growth %', format: '+.1f' },
79-
{ field: 'units', title: 'Units', format: ',' }
80-
]
63+
scale: { scheme: 'redyellowgreen', domain: [-25, 25] } },
64+
tooltip: [{ field:'product' }, { field:'revenue', format:'.1f' },
65+
{ field:'growth', format:'+.1f' }, { field:'units', format:',' }]
8166
},
82-
config: { view: { stroke: null }, background: '#fff',
83-
font: 'system-ui, -apple-system, sans-serif' }
67+
config: { view: { stroke: null }, background: '#fff' }
8468
}, { actions: false });
8569
});
8670
<\/script>
@@ -105,21 +89,20 @@ <h1>iframe-flight × Vega-Lite — single file, no install</h1>
10589
const btn = document.getElementById('btn');
10690
const status = document.getElementById('status');
10791

108-
frame.srcdoc = CHILD;
92+
let emitter = null;
10993

110-
const emitter = new ArrowParentEmitter(frame, {
111-
handshakeTimeout: 20000,
112-
allowedOrigins: ['*'],
94+
// Create the emitter only after the iframe document has loaded,
95+
// so contentWindow is stable and CHILD_READY is already in-flight.
96+
frame.addEventListener('load', () => {
97+
emitter = new ArrowParentEmitter(frame, { allowedOrigins: ['*'] });
98+
emitter
99+
.onReady(() => { status.textContent = 'Ready'; btn.disabled = false; push(); })
100+
.onError(err => { status.textContent = `❌ ${err.message}`; });
113101
});
114102

115-
emitter
116-
.onReady(() => {
117-
btn.disabled = false;
118-
push();
119-
})
120-
.onError(err => {
121-
status.textContent = `❌ ${err.message}`;
122-
});
103+
btn.addEventListener('click', push);
104+
105+
frame.srcdoc = CHILD;
123106

124107
async function push() {
125108
btn.disabled = true;
@@ -128,8 +111,6 @@ <h1>iframe-flight × Vega-Lite — single file, no install</h1>
128111
status.textContent = `${ack.rows} rows · ${ack.processingTime}ms`;
129112
btn.disabled = false;
130113
}
131-
132-
btn.addEventListener('click', push);
133114
</script>
134115

135116
</body>

0 commit comments

Comments
 (0)