11/**
22 * T-Ruby WASM Loader for Playground
33 *
4- * Uses a sandboxed iframe with srcdoc to isolate WASM execution from browser extensions
5- * that might interfere with FinalizationRegistry and other APIs .
4+ * Uses a Web Worker to isolate WASM execution from browser extensions.
5+ * Web Workers run in a separate thread and extensions don't inject content scripts into them .
66 */
77
88// Types
@@ -30,7 +30,7 @@ export interface LoadingProgress {
3030// Singleton state
3131let compiler : TRubyCompiler | null = null ;
3232let loadingPromise : Promise < TRubyCompiler > | null = null ;
33- let workerIframe : HTMLIFrameElement | null = null ;
33+ let wasmWorker : Worker | null = null ;
3434let healthData : { loaded : boolean ; version : string ; ruby_version : string } | null = null ;
3535
3636// Pending compile requests
@@ -45,22 +45,14 @@ function generateRequestId(): string {
4545 return `req_${ ++ requestIdCounter } _${ Date . now ( ) } ` ;
4646}
4747
48- // Worker HTML content - embedded as string to use srcdoc
49- // This creates an about:srcdoc origin iframe which most extensions don't target
50- const WORKER_HTML = `<!DOCTYPE html>
51- <html>
52- <head>
53- <meta charset="UTF-8">
54- <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data: https://cdn.jsdelivr.net;">
55- <title>T-Ruby WASM Worker</title>
56- </head>
57- <body>
58- <script type="module">
59- // CDN URLs
48+ // Web Worker code as a string - runs in isolated thread
49+ const WORKER_CODE = `
50+ // Web Worker for T-Ruby WASM compilation
51+ // This runs in a separate thread, isolated from browser extensions
52+
6053const RUBY_WASM_CDN = 'https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.7.0/dist/browser/+esm';
6154const RUBY_WASM_BINARY = 'https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.7.0/dist/ruby+stdlib.wasm';
6255
63- // Bootstrap code for T-Ruby compiler
6456const BOOTSTRAP_CODE = \`
6557require "json"
6658
@@ -111,36 +103,36 @@ end
111103let vm = null;
112104let isReady = false;
113105
114- // Send message to parent
115- function sendToParent (type, data, requestId) {
116- parent .postMessage({ type, data, requestId }, '*' );
106+ // Send message to main thread
107+ function sendToMain (type, data, requestId) {
108+ self .postMessage({ type, data, requestId });
117109}
118110
119111// Initialize Ruby VM
120112async function initialize() {
121113 try {
122114 console.log('[WASM Worker] Step 1: Loading Ruby WASM module...');
123- sendToParent ('progress', { message: 'Loading Ruby runtime...', progress: 10 });
115+ sendToMain ('progress', { message: 'Loading Ruby runtime...', progress: 10 });
124116
125117 const { DefaultRubyVM } = await import(RUBY_WASM_CDN);
126118 console.log('[WASM Worker] Step 1 complete');
127119
128120 console.log('[WASM Worker] Step 2: Fetching WASM binary...');
129- sendToParent ('progress', { message: 'Downloading Ruby WASM binary...', progress: 30 });
121+ sendToMain ('progress', { message: 'Downloading Ruby WASM binary...', progress: 30 });
130122
131123 const response = await fetch(RUBY_WASM_BINARY);
132124 const wasmModule = await WebAssembly.compileStreaming(response);
133125 console.log('[WASM Worker] Step 2 complete');
134126
135127 console.log('[WASM Worker] Step 3: Initializing Ruby VM...');
136- sendToParent ('progress', { message: 'Initializing Ruby VM...', progress: 60 });
128+ sendToMain ('progress', { message: 'Initializing Ruby VM...', progress: 60 });
137129
138130 const result = await DefaultRubyVM(wasmModule);
139131 vm = result.vm;
140132 console.log('[WASM Worker] Step 3 complete');
141133
142134 console.log('[WASM Worker] Step 4: Loading T-Ruby bootstrap...');
143- sendToParent ('progress', { message: 'Loading T-Ruby compiler...', progress: 80 });
135+ sendToMain ('progress', { message: 'Loading T-Ruby compiler...', progress: 80 });
144136
145137 vm.eval(BOOTSTRAP_CODE);
146138 console.log('[WASM Worker] Step 4 complete');
@@ -150,19 +142,21 @@ async function initialize() {
150142 console.log('[WASM Worker] Health check:', healthResult.toString());
151143
152144 isReady = true;
153- sendToParent ('ready', { health: JSON.parse(healthResult.toString()) });
145+ sendToMain ('ready', { health: JSON.parse(healthResult.toString()) });
154146
155147 } catch (error) {
156148 console.error('[WASM Worker] Init error:', error);
157- sendToParent ('error', { message: error.message });
149+ sendToMain ('error', { message: error.message });
158150 }
159151}
160152
161153// Compile code
162154function compile(code, requestId) {
163155 if (!isReady || !vm) {
164- sendToParent ('compile-result', {
156+ sendToMain ('compile-result', {
165157 success: false,
158+ ruby: '',
159+ rbs: '',
166160 errors: ['Compiler not ready']
167161 }, requestId);
168162 return;
@@ -173,10 +167,10 @@ function compile(code, requestId) {
173167 const resultJson = vm.eval('__trb_compile__(' + JSON.stringify(code) + ')');
174168 const result = JSON.parse(resultJson.toString());
175169 console.log('[WASM Worker] Compile result:', result);
176- sendToParent ('compile-result', result, requestId);
170+ sendToMain ('compile-result', result, requestId);
177171 } catch (error) {
178172 console.error('[WASM Worker] Compile error:', error);
179- sendToParent ('compile-result', {
173+ sendToMain ('compile-result', {
180174 success: false,
181175 ruby: '',
182176 rbs: '',
@@ -185,8 +179,8 @@ function compile(code, requestId) {
185179 }
186180}
187181
188- // Listen for messages from parent
189- window .addEventListener('message', (event) => {
182+ // Listen for messages from main thread
183+ self .addEventListener('message', (event) => {
190184 const { type, data, requestId } = event.data || {};
191185
192186 switch (type) {
@@ -200,13 +194,11 @@ window.addEventListener('message', (event) => {
200194});
201195
202196// Signal that worker is loaded
203- sendToParent('loaded', {});
204- <\/script>
205- </body>
206- </html>` ;
197+ sendToMain('loaded', {});
198+ ` ;
207199
208200/**
209- * Load the T-Ruby WASM compiler using sandboxed iframe with srcdoc
201+ * Load the T-Ruby WASM compiler using Web Worker
210202 * Returns cached instance if already loaded
211203 */
212204export async function loadTRubyCompiler (
@@ -239,40 +231,31 @@ async function doLoadCompiler(
239231 onProgress ?: ( progress : LoadingProgress ) => void
240232) : Promise < TRubyCompiler > {
241233 return new Promise ( ( resolve , reject ) => {
242- console . log ( '[T-Ruby] Creating sandboxed srcdoc iframe for WASM execution...' ) ;
234+ console . log ( '[T-Ruby] Creating Web Worker for WASM execution...' ) ;
243235 onProgress ?.( {
244236 state : 'loading' ,
245- message : 'Initializing compiler sandbox ...' ,
237+ message : 'Initializing compiler worker ...' ,
246238 progress : 5
247239 } ) ;
248240
249- // Create iframe with blob URL
250- // Using blob: URL creates a unique origin that extensions typically don't target
251- const iframe = document . createElement ( 'iframe' ) ;
252- iframe . style . display = 'none' ;
253-
254- // Create blob URL - this gives us a blob: origin
255- const blob = new Blob ( [ WORKER_HTML ] , { type : 'text/html' } ) ;
241+ // Create Web Worker from blob URL
242+ // Web Workers run in a separate thread, isolated from extension content scripts
243+ const blob = new Blob ( [ WORKER_CODE ] , { type : 'application/javascript' } ) ;
256244 const blobUrl = URL . createObjectURL ( blob ) ;
257- iframe . src = blobUrl ;
245+ const worker = new Worker ( blobUrl , { type : 'module' } ) ;
258246
259- // Clean up blob URL after iframe loads
260- iframe . onload = ( ) => {
261- URL . revokeObjectURL ( blobUrl ) ;
262- } ;
247+ // Clean up blob URL
248+ URL . revokeObjectURL ( blobUrl ) ;
263249
264250 // Message handler
265- const messageHandler = ( event : MessageEvent ) => {
266- // Only accept messages from our iframe
267- if ( event . source !== iframe . contentWindow ) return ;
268-
251+ worker . onmessage = ( event : MessageEvent ) => {
269252 const { type, data, requestId } = event . data || { } ;
270253 console . log ( '[T-Ruby] Received message from worker:' , type , data ) ;
271254
272255 switch ( type ) {
273256 case 'loaded' :
274- console . log ( '[T-Ruby] Worker iframe loaded, sending init command...' ) ;
275- iframe . contentWindow ?. postMessage ( { type : 'init' } , '*' ) ;
257+ console . log ( '[T-Ruby] Worker loaded, sending init command...' ) ;
258+ worker . postMessage ( { type : 'init' } ) ;
276259 break ;
277260
278261 case 'progress' :
@@ -300,11 +283,11 @@ async function doLoadCompiler(
300283 pendingRequests . set ( reqId , { resolve : res , reject : rej } ) ;
301284
302285 console . log ( '[T-Ruby] Sending compile request:' , reqId ) ;
303- iframe . contentWindow ? .postMessage ( {
286+ worker . postMessage ( {
304287 type : 'compile' ,
305288 data : { code } ,
306289 requestId : reqId
307- } , '*' ) ;
290+ } ) ;
308291
309292 // Timeout after 30 seconds
310293 setTimeout ( ( ) => {
@@ -328,7 +311,7 @@ async function doLoadCompiler(
328311 }
329312 } ;
330313
331- workerIframe = iframe ;
314+ wasmWorker = worker ;
332315 resolve ( compilerInstance ) ;
333316 break ;
334317
@@ -347,28 +330,23 @@ async function doLoadCompiler(
347330 state : 'error' ,
348331 message : data . message
349332 } ) ;
350- window . removeEventListener ( 'message' , messageHandler ) ;
351- document . body . removeChild ( iframe ) ;
333+ worker . terminate ( ) ;
352334 reject ( new Error ( data . message ) ) ;
353335 break ;
354336 }
355337 } ;
356338
357- window . addEventListener ( 'message' , messageHandler ) ;
358-
359- // Error handling for iframe load failure
360- iframe . onerror = ( ) => {
361- console . error ( '[T-Ruby] Failed to load worker iframe' ) ;
362- window . removeEventListener ( 'message' , messageHandler ) ;
363- reject ( new Error ( 'Failed to load WASM worker' ) ) ;
339+ // Error handling
340+ worker . onerror = ( error ) => {
341+ console . error ( '[T-Ruby] Worker error:' , error ) ;
342+ reject ( new Error ( 'Failed to initialize WASM worker' ) ) ;
364343 } ;
365344
366345 // Timeout for initial load
367346 const loadTimeout = setTimeout ( ( ) => {
368347 if ( ! compiler ) {
369348 console . error ( '[T-Ruby] Worker initialization timeout' ) ;
370- window . removeEventListener ( 'message' , messageHandler ) ;
371- document . body . removeChild ( iframe ) ;
349+ worker . terminate ( ) ;
372350 reject ( new Error ( 'WASM worker initialization timeout' ) ) ;
373351 }
374352 } , 60000 ) ; // 60 second timeout for initial load
@@ -379,9 +357,6 @@ async function doLoadCompiler(
379357 clearTimeout ( loadTimeout ) ;
380358 originalResolve ( value ) ;
381359 } ;
382-
383- // Add iframe to document
384- document . body . appendChild ( iframe ) ;
385360 } ) ;
386361}
387362
0 commit comments