@@ -4543,8 +4543,20 @@ var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => {
45434543 canvas . __pydevicesInputInstalled = true ;
45444544 canvas . tabIndex = canvas . tabIndex >= 0 ? canvas . tabIndex : 0 ;
45454545 const modifiers = ( e ) => ( { altKey :e . altKey , ctrlKey :e . ctrlKey , metaKey :e . metaKey , shiftKey :e . shiftKey } ) ;
4546+ // Named and tracked on the element (rather than anonymous closures) so a
4547+ // future register on this same canvas - a new WASM instance after a VM
4548+ // restart, most likely - can strip them via pydevices_release_canvas
4549+ // before installing its own. Without that, events from a second
4550+ // instance would keep calling into the first instance's closures - and
4551+ // with them, its whole WASM heap - forever.
4552+ const listeners = [ ] ;
4553+ const on = ( name , handler , opts ) => {
4554+ canvas . addEventListener ( name , handler , opts ) ;
4555+ listeners . push ( [ name , handler , opts ] ) ;
4556+ } ;
4557+ canvas . __pydevicesListeners = listeners ;
45464558 for ( const name of [ 'pointerdown' , 'pointermove' , 'pointerup' , 'pointercancel' ] ) {
4547- canvas . addEventListener ( name , ( e ) => {
4559+ on ( name , ( e ) => {
45484560 const p = pydevices_relative_point ( canvas , e ) ;
45494561 pydevices_push_event ( canvasId , Object . assign ( { type :name , x :p . x , y :p . y , movementX :e . movementX || 0 , movementY :e . movementY || 0 , button :e . button , buttons :e . buttons , pressure :e . pressure , pointerId :e . pointerId , pointerType :e . pointerType , primary :e . isPrimary } , modifiers ( e ) ) ) ;
45504562 if ( name === 'pointerdown' ) {
@@ -4554,19 +4566,19 @@ var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => {
45544566 e . preventDefault ( ) ;
45554567 } ) ;
45564568 }
4557- canvas . addEventListener ( 'wheel' , ( e ) => {
4569+ on ( 'wheel' , ( e ) => {
45584570 const p = pydevices_relative_point ( canvas , e ) ;
45594571 pydevices_push_event ( canvasId , Object . assign ( { type :'wheel' , x :p . x , y :p . y , deltaX :e . deltaX , deltaY :e . deltaY , deltaZ :e . deltaZ , deltaMode :e . deltaMode } , modifiers ( e ) ) ) ;
45604572 e . preventDefault ( ) ;
45614573 } , { passive :false } ) ;
45624574 for ( const name of [ 'keydown' , 'keyup' ] ) {
4563- canvas . addEventListener ( name , ( e ) => {
4575+ on ( name , ( e ) => {
45644576 pydevices_push_event ( canvasId , Object . assign ( { type :name , key :e . key , code :e . code , repeat :e . repeat , location :e . location } , modifiers ( e ) ) ) ;
45654577 if ( e . key === 'Backspace' || e . key === 'Escape' || e . key === 'BrowserBack' ) e . preventDefault ( ) ;
45664578 } ) ;
45674579 }
4568- canvas . addEventListener ( 'focus' , ( ) => pydevices_push_event ( canvasId , { type :'focus' } ) ) ;
4569- canvas . addEventListener ( 'blur' , ( ) => pydevices_push_event ( canvasId , { type :'blur' } ) ) ;
4580+ on ( 'focus' , ( ) => pydevices_push_event ( canvasId , { type :'focus' } ) ) ;
4581+ on ( 'blur' , ( ) => pydevices_push_event ( canvasId , { type :'blur' } ) ) ;
45704582 canvas . focus ( ) ;
45714583 } ;
45724584
@@ -4602,9 +4614,65 @@ var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => {
46024614 state . animation = requestAnimationFrame ( pydevices_paint ) ;
46034615 } ;
46044616
4617+
4618+ var pydevices_release_canvas = ( canvas ) => {
4619+ for ( const [ name , handler , opts ] of canvas . __pydevicesListeners || [ ] ) {
4620+ canvas . removeEventListener ( name , handler , opts ) ;
4621+ }
4622+ canvas . __pydevicesListeners = null ;
4623+ canvas . __pydevicesInputInstalled = false ;
4624+ } ;
46054625 var pydevices_export_host = ( ) => {
46064626 if ( Module . pydevicesBridge ) return ;
46074627 Module . pydevicesBridge = {
4628+ /*
4629+ * Releases everything this instance holds in the host page: the
4630+ * paint loop, the canvas listeners, pending timers, and any audio
4631+ * still playing or recording. Meant to run right before an embedder
4632+ * discards this Module for a fresh one - a VM restart, most likely -
4633+ * so the old instance's WASM heap (kept alive otherwise by nothing
4634+ * but its own rAF callback and DOM listeners) actually becomes
4635+ * garbage instead of accumulating on every restart, and so canvas
4636+ * input reaches whichever instance registers on it next rather than
4637+ * silently continuing to reach this one.
4638+ *
4639+ * Idempotent - calling it twice, or on an instance that never
4640+ * registered a display, does nothing harmful.
4641+ */
4642+ shutdown : ( ) => {
4643+ const state = pydevices_state ;
4644+ if ( state . animation ) {
4645+ cancelAnimationFrame ( state . animation ) ;
4646+ state . animation = 0 ;
4647+ }
4648+ for ( const display of state . displays . values ( ) ) {
4649+ if ( display . canvas ) pydevices_release_canvas ( display . canvas ) ;
4650+ }
4651+ state . displays . clear ( ) ;
4652+ for ( const timer of state . timers . values ( ) ) {
4653+ timer . periodic ? clearInterval ( timer . handle ) : clearTimeout ( timer . handle ) ;
4654+ }
4655+ state . timers . clear ( ) ;
4656+ state . firedTimers . length = 0 ;
4657+ for ( const source of state . audioSources ) {
4658+ try { source . stop ( ) ; } catch ( _ ) { /* already finished */ }
4659+ }
4660+ state . audioSources . clear ( ) ;
4661+ state . audioQueuedBytes = 0 ;
4662+ state . inputFrames . length = 0 ;
4663+ if ( state . processor ) { try { state . processor . disconnect ( ) ; } catch ( _ ) { } }
4664+ if ( state . mediaNode ) { try { state . mediaNode . disconnect ( ) ; } catch ( _ ) { } }
4665+ if ( state . mediaStream ) { for ( const track of state . mediaStream . getTracks ( ) ) track . stop ( ) ; }
4666+ state . processor = null ;
4667+ state . mediaNode = null ;
4668+ state . mediaStream = null ;
4669+ state . microphoneEnabled = false ;
4670+ if ( state . audioContext ) {
4671+ try { state . audioContext . close ( ) ; } catch ( _ ) { }
4672+ state . audioContext = null ;
4673+ }
4674+ state . audioEnabled = false ;
4675+ } ,
46084676 enableAudio : async ( microphone = false ) => {
46094677 const state = pydevices_state ;
46104678 const AudioContext = globalThis . AudioContext || globalThis . webkitAudioContext ;
@@ -4698,8 +4766,16 @@ var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => {
46984766 pydevices_get_display ( UTF8ToString ( canvasPtr ) ) . eventRing = ptr ? { ptr, capacity, stride} : null ;
46994767 } ;
47004768
4769+
47014770 var _pydevices_host_reset = ( ) => {
47024771 const state = pydevices_state ;
4772+ if ( state . animation ) {
4773+ cancelAnimationFrame ( state . animation ) ;
4774+ state . animation = 0 ;
4775+ }
4776+ for ( const display of state . displays . values ( ) ) {
4777+ if ( display . canvas ) pydevices_release_canvas ( display . canvas ) ;
4778+ }
47034779 state . displays . clear ( ) ;
47044780 state . firedTimers . length = 0 ;
47054781 for ( const timer of state . timers . values ( ) ) {
@@ -5694,6 +5770,7 @@ missingLibrarySymbols.forEach(missingLibrarySymbol)
56945770 'pydevices_push_event' ,
56955771 'pydevices_relative_point' ,
56965772 'pydevices_install_input' ,
5773+ 'pydevices_release_canvas' ,
56975774 'pydevices_paint' ,
56985775 'pydevices_export_host' ,
56995776 'pydevices_decode_pcm' ,
@@ -5784,17 +5861,17 @@ var __emscripten_stack_restore = makeInvalidEarlyAccess('__emscripten_stack_rest
57845861var __emscripten_stack_alloc = makeInvalidEarlyAccess ( '__emscripten_stack_alloc' ) ;
57855862var _emscripten_stack_get_current = makeInvalidEarlyAccess ( '_emscripten_stack_get_current' ) ;
57865863var dynCall_iii = makeInvalidEarlyAccess ( 'dynCall_iii' ) ;
5864+ var dynCall_iiii = makeInvalidEarlyAccess ( 'dynCall_iiii' ) ;
5865+ var dynCall_iiiiiii = makeInvalidEarlyAccess ( 'dynCall_iiiiiii' ) ;
57875866var dynCall_ii = makeInvalidEarlyAccess ( 'dynCall_ii' ) ;
57885867var dynCall_iiiii = makeInvalidEarlyAccess ( 'dynCall_iiiii' ) ;
57895868var dynCall_viii = makeInvalidEarlyAccess ( 'dynCall_viii' ) ;
57905869var dynCall_iiiiii = makeInvalidEarlyAccess ( 'dynCall_iiiiii' ) ;
5791- var dynCall_iiii = makeInvalidEarlyAccess ( 'dynCall_iiii' ) ;
57925870var dynCall_di = makeInvalidEarlyAccess ( 'dynCall_di' ) ;
57935871var dynCall_vi = makeInvalidEarlyAccess ( 'dynCall_vi' ) ;
57945872var dynCall_vii = makeInvalidEarlyAccess ( 'dynCall_vii' ) ;
57955873var dynCall_viiii = makeInvalidEarlyAccess ( 'dynCall_viiii' ) ;
57965874var dynCall_i = makeInvalidEarlyAccess ( 'dynCall_i' ) ;
5797- var dynCall_iiiiiii = makeInvalidEarlyAccess ( 'dynCall_iiiiiii' ) ;
57985875var dynCall_v = makeInvalidEarlyAccess ( 'dynCall_v' ) ;
57995876var dynCall_jji = makeInvalidEarlyAccess ( 'dynCall_jji' ) ;
58005877var dynCall_viiiii = makeInvalidEarlyAccess ( 'dynCall_viiiii' ) ;
@@ -5862,17 +5939,17 @@ function assignWasmExports(wasmExports) {
58625939 assert ( typeof wasmExports [ '_emscripten_stack_alloc' ] != 'undefined' , 'missing Wasm export: _emscripten_stack_alloc' ) ;
58635940 assert ( typeof wasmExports [ 'emscripten_stack_get_current' ] != 'undefined' , 'missing Wasm export: emscripten_stack_get_current' ) ;
58645941 assert ( typeof wasmExports [ 'dynCall_iii' ] != 'undefined' , 'missing Wasm export: dynCall_iii' ) ;
5942+ assert ( typeof wasmExports [ 'dynCall_iiii' ] != 'undefined' , 'missing Wasm export: dynCall_iiii' ) ;
5943+ assert ( typeof wasmExports [ 'dynCall_iiiiiii' ] != 'undefined' , 'missing Wasm export: dynCall_iiiiiii' ) ;
58655944 assert ( typeof wasmExports [ 'dynCall_ii' ] != 'undefined' , 'missing Wasm export: dynCall_ii' ) ;
58665945 assert ( typeof wasmExports [ 'dynCall_iiiii' ] != 'undefined' , 'missing Wasm export: dynCall_iiiii' ) ;
58675946 assert ( typeof wasmExports [ 'dynCall_viii' ] != 'undefined' , 'missing Wasm export: dynCall_viii' ) ;
58685947 assert ( typeof wasmExports [ 'dynCall_iiiiii' ] != 'undefined' , 'missing Wasm export: dynCall_iiiiii' ) ;
5869- assert ( typeof wasmExports [ 'dynCall_iiii' ] != 'undefined' , 'missing Wasm export: dynCall_iiii' ) ;
58705948 assert ( typeof wasmExports [ 'dynCall_di' ] != 'undefined' , 'missing Wasm export: dynCall_di' ) ;
58715949 assert ( typeof wasmExports [ 'dynCall_vi' ] != 'undefined' , 'missing Wasm export: dynCall_vi' ) ;
58725950 assert ( typeof wasmExports [ 'dynCall_vii' ] != 'undefined' , 'missing Wasm export: dynCall_vii' ) ;
58735951 assert ( typeof wasmExports [ 'dynCall_viiii' ] != 'undefined' , 'missing Wasm export: dynCall_viiii' ) ;
58745952 assert ( typeof wasmExports [ 'dynCall_i' ] != 'undefined' , 'missing Wasm export: dynCall_i' ) ;
5875- assert ( typeof wasmExports [ 'dynCall_iiiiiii' ] != 'undefined' , 'missing Wasm export: dynCall_iiiiiii' ) ;
58765953 assert ( typeof wasmExports [ 'dynCall_v' ] != 'undefined' , 'missing Wasm export: dynCall_v' ) ;
58775954 assert ( typeof wasmExports [ 'dynCall_jji' ] != 'undefined' , 'missing Wasm export: dynCall_jji' ) ;
58785955 assert ( typeof wasmExports [ 'dynCall_viiiii' ] != 'undefined' , 'missing Wasm export: dynCall_viiiii' ) ;
@@ -5936,17 +6013,17 @@ function assignWasmExports(wasmExports) {
59366013 __emscripten_stack_alloc = wasmExports [ '_emscripten_stack_alloc' ] ;
59376014 _emscripten_stack_get_current = wasmExports [ 'emscripten_stack_get_current' ] ;
59386015 dynCall_iii = dynCalls [ 'iii' ] = createExportWrapper ( 'dynCall_iii' , 3 ) ;
6016+ dynCall_iiii = dynCalls [ 'iiii' ] = createExportWrapper ( 'dynCall_iiii' , 4 ) ;
6017+ dynCall_iiiiiii = dynCalls [ 'iiiiiii' ] = createExportWrapper ( 'dynCall_iiiiiii' , 7 ) ;
59396018 dynCall_ii = dynCalls [ 'ii' ] = createExportWrapper ( 'dynCall_ii' , 2 ) ;
59406019 dynCall_iiiii = dynCalls [ 'iiiii' ] = createExportWrapper ( 'dynCall_iiiii' , 5 ) ;
59416020 dynCall_viii = dynCalls [ 'viii' ] = createExportWrapper ( 'dynCall_viii' , 4 ) ;
59426021 dynCall_iiiiii = dynCalls [ 'iiiiii' ] = createExportWrapper ( 'dynCall_iiiiii' , 6 ) ;
5943- dynCall_iiii = dynCalls [ 'iiii' ] = createExportWrapper ( 'dynCall_iiii' , 4 ) ;
59446022 dynCall_di = dynCalls [ 'di' ] = createExportWrapper ( 'dynCall_di' , 2 ) ;
59456023 dynCall_vi = dynCalls [ 'vi' ] = createExportWrapper ( 'dynCall_vi' , 2 ) ;
59466024 dynCall_vii = dynCalls [ 'vii' ] = createExportWrapper ( 'dynCall_vii' , 3 ) ;
59476025 dynCall_viiii = dynCalls [ 'viiii' ] = createExportWrapper ( 'dynCall_viiii' , 5 ) ;
59486026 dynCall_i = dynCalls [ 'i' ] = createExportWrapper ( 'dynCall_i' , 1 ) ;
5949- dynCall_iiiiiii = dynCalls [ 'iiiiiii' ] = createExportWrapper ( 'dynCall_iiiiiii' , 7 ) ;
59506027 dynCall_v = dynCalls [ 'v' ] = createExportWrapper ( 'dynCall_v' , 1 ) ;
59516028 dynCall_jji = dynCalls [ 'jji' ] = createExportWrapper ( 'dynCall_jji' , 3 ) ;
59526029 dynCall_viiiii = dynCalls [ 'viiiii' ] = createExportWrapper ( 'dynCall_viiiii' , 6 ) ;
@@ -6927,9 +7004,20 @@ const py_proxy_handler = {
69277004class PyProxyThenable {
69287005 constructor ( ref ) {
69297006 this . _ref = ref ;
7007+ this . _generation = globalThis . proxy_js_generation ;
69307008 }
69317009
69327010 then ( resolve , reject ) {
7011+ if ( this . _generation !== globalThis . proxy_js_generation ) {
7012+ // The interpreter this generator belonged to is gone; its ref
7013+ // would resolve to an arbitrary object in the current one. The
7014+ // promise machinery calls this spontaneously, so settle the
7015+ // chain instead of throwing into it.
7016+ reject (
7017+ new Error ( "PyProxyThenable outlived its interpreter" ) ,
7018+ ) ;
7019+ return undefined ;
7020+ }
69337021 const values = Module . _malloc ( 3 * 3 * 4 ) ;
69347022 proxy_convert_js_to_mp_obj_jsside ( resolve , values + 3 * 4 ) ;
69357023 proxy_convert_js_to_mp_obj_jsside ( reject , values + 2 * 3 * 4 ) ;
@@ -7005,6 +7093,15 @@ class PythonError extends Error {
70057093}
70067094
70077095function proxy_js_init ( ) {
7096+ // Proxy state lives on globalThis and is shared by every interpreter
7097+ // instance loaded from this module. Each (re)init starts a new
7098+ // generation; wrappers handed to the browser (setTimeout callbacks,
7099+ // event listeners, promise handlers) record the generation they were
7100+ // created in, so ones that outlive their interpreter go inert instead
7101+ // of calling into the current interpreter with a recycled index --
7102+ // which resolves to an arbitrary, unrelated object.
7103+ globalThis . proxy_js_generation =
7104+ ( globalThis . proxy_js_generation || 0 ) + 1 ;
70087105 globalThis . proxy_js_ref = [ globalThis , undefined ] ;
70097106 globalThis . proxy_js_ref_next = PROXY_JS_REF_NUM_STATIC ;
70107107 globalThis . proxy_js_ref_map = new Map ( ) ;
@@ -7228,7 +7325,13 @@ function proxy_convert_mp_to_js_obj_jsside(value) {
72287325 // obj
72297326 const id = Module . getValue ( value + 4 , "i32" ) ;
72307327 if ( kind === PROXY_KIND_MP_CALLABLE ) {
7328+ const generation = globalThis . proxy_js_generation ;
72317329 obj = ( ...args ) => {
7330+ if ( generation !== globalThis . proxy_js_generation ) {
7331+ // The interpreter this callable belonged to is gone.
7332+ // Behave like a cleared timer: do nothing.
7333+ return undefined ;
7334+ }
72327335 return proxy_call_python ( id , args ) ;
72337336 } ;
72347337 obj . _ref = id ;
0 commit comments