@@ -470,5 +470,140 @@ mod tests {
470470 "_testAsync should be a function on restored isolate"
471471 ) ;
472472 }
473+
474+ // --- Part 13: Register stub bridge functions on V8 global ---
475+ // Verifies that register_stub_bridge_fns places functions on the global
476+ // and that they have the correct typeof without calling them.
477+ {
478+ use crate :: bridge:: register_stub_bridge_fns;
479+
480+ // Use a snapshot-based isolate (consistent with other parts)
481+ let bridge_code = "/* stub test */" ;
482+ let blob = create_snapshot ( bridge_code) . expect ( "snapshot creation" ) ;
483+ let mut isolate = create_isolate_from_snapshot ( blob, None ) ;
484+
485+ let scope = & mut v8:: HandleScope :: new ( & mut isolate) ;
486+ let context = v8:: Context :: new ( scope, Default :: default ( ) ) ;
487+ let scope = & mut v8:: ContextScope :: new ( scope, context) ;
488+
489+ register_stub_bridge_fns (
490+ scope,
491+ & [ "_log" , "_error" , "_fsReadFile" , "_loadPolyfill" ] ,
492+ & [ "_scheduleTimer" , "_dynamicImport" ] ,
493+ ) ;
494+
495+ let check = v8:: String :: new ( scope, r#"
496+ (function() {
497+ var names = ['_log', '_error', '_fsReadFile', '_loadPolyfill',
498+ '_scheduleTimer', '_dynamicImport'];
499+ for (var i = 0; i < names.length; i++) {
500+ if (typeof globalThis[names[i]] !== 'function') {
501+ return 'FAIL: ' + names[i] + ' is ' + typeof globalThis[names[i]];
502+ }
503+ }
504+ return 'OK';
505+ })()
506+ "# ) . unwrap ( ) ;
507+ let script = v8:: Script :: compile ( scope, check, None ) . unwrap ( ) ;
508+ let result = script. run ( scope) . unwrap ( ) ;
509+ assert_eq ! (
510+ result. to_rust_string_lossy( scope) ,
511+ "OK" ,
512+ "all stub bridge functions should be registered as functions"
513+ ) ;
514+ }
515+
516+ // --- Part 14: Bridge IIFE executes against stubs + snapshot creation ---
517+ // Verifies that setup-time code can reference stub functions (typeof,
518+ // closure wrapping, getter facade) without calling them, and that the
519+ // resulting context can be snapshotted.
520+ {
521+ use crate :: bridge:: register_stub_bridge_fns;
522+ use crate :: session:: { SYNC_BRIDGE_FNS , ASYNC_BRIDGE_FNS } ;
523+
524+ let mut snapshot_isolate = v8:: Isolate :: snapshot_creator ( Some ( external_refs ( ) ) , None ) ;
525+ {
526+ let scope = & mut v8:: HandleScope :: new ( & mut snapshot_isolate) ;
527+ let context = v8:: Context :: new ( scope, Default :: default ( ) ) ;
528+ let scope = & mut v8:: ContextScope :: new ( scope, context) ;
529+
530+ // Register all 38 bridge functions as stubs (no External data)
531+ register_stub_bridge_fns (
532+ scope,
533+ & SYNC_BRIDGE_FNS ,
534+ & ASYNC_BRIDGE_FNS ,
535+ ) ;
536+
537+ // Simulate bridge IIFE: reference all bridge functions, set up
538+ // closures and getter facade, but never call any bridge function
539+ let iife_code = r#"
540+ (function() {
541+ // Verify bridge functions exist (like ivm-compat shim)
542+ var syncKeys = ['_log', '_error', '_resolveModule', '_loadFile',
543+ '_cryptoRandomFill', '_fsReadFile', '_fsWriteFile',
544+ '_childProcessSpawnStart', '_childProcessSpawnSync'];
545+ var asyncKeys = ['_dynamicImport', '_scheduleTimer',
546+ '_networkFetchRaw', '_networkHttpServerListenRaw'];
547+
548+ for (var i = 0; i < syncKeys.length; i++) {
549+ if (typeof globalThis[syncKeys[i]] !== 'function') {
550+ throw new Error('Missing sync: ' + syncKeys[i]);
551+ }
552+ }
553+ for (var i = 0; i < asyncKeys.length; i++) {
554+ if (typeof globalThis[asyncKeys[i]] !== 'function') {
555+ throw new Error('Missing async: ' + asyncKeys[i]);
556+ }
557+ }
558+
559+ // Simulate getter-based fs facade (setup only, no calls)
560+ var _fs = {};
561+ Object.defineProperties(_fs, {
562+ readFile: { get: function() { return globalThis._fsReadFile; }, enumerable: true },
563+ writeFile: { get: function() { return globalThis._fsWriteFile; }, enumerable: true },
564+ });
565+ globalThis._fs = _fs;
566+
567+ // Verify getter returns function reference without calling it
568+ if (typeof _fs.readFile !== 'function') {
569+ throw new Error('Getter should return function, got ' + typeof _fs.readFile);
570+ }
571+
572+ // Simulate closure wrapping (setup only, no calls)
573+ globalThis.__wrappedLog = function() {
574+ return globalThis._log.apply(null, arguments);
575+ };
576+
577+ globalThis.__bridge_setup_complete = true;
578+ })();
579+ "# ;
580+ let source = v8:: String :: new ( scope, iife_code) . unwrap ( ) ;
581+ let script = v8:: Script :: compile ( scope, source, None ) . unwrap ( ) ;
582+ let result = script. run ( scope) ;
583+ assert ! (
584+ result. is_some( ) ,
585+ "bridge IIFE should execute without error against stub functions"
586+ ) ;
587+
588+ // Verify setup completed
589+ let check = v8:: String :: new ( scope, "String(globalThis.__bridge_setup_complete)" ) . unwrap ( ) ;
590+ let script = v8:: Script :: compile ( scope, check, None ) . unwrap ( ) ;
591+ let val = script. run ( scope) . unwrap ( ) ;
592+ assert_eq ! (
593+ val. to_rust_string_lossy( scope) ,
594+ "true" ,
595+ "bridge setup should complete with stub functions"
596+ ) ;
597+
598+ scope. set_default_context ( context) ;
599+ }
600+
601+ let blob = snapshot_isolate. create_blob ( v8:: FunctionCodeHandling :: Keep ) ;
602+ assert ! (
603+ blob. is_some( ) ,
604+ "snapshot creation should succeed with stub bridge functions"
605+ ) ;
606+ assert ! ( blob. unwrap( ) . len( ) > 0 , "snapshot blob should be non-empty" ) ;
607+ }
473608 }
474609}
0 commit comments