@@ -6,6 +6,8 @@ use std::path::{Path, PathBuf};
66use std:: process:: Command ;
77use std:: time:: { Duration , Instant } ;
88use windows:: core:: { IUnknown , Interface } ;
9+ use windows:: Storage :: Streams :: IBuffer ;
10+ use windows:: Win32 :: System :: WinRT :: IBufferByteAccess ;
911use windows:: Win32 :: UI :: WindowsAndMessaging :: {
1012 DispatchMessageW , PeekMessageW , TranslateMessage , MSG , PM_REMOVE ,
1113} ;
@@ -29,6 +31,19 @@ thread_local!(static DEVTOOLS_SERVER: RefCell<Option<DevtoolsServer>> = RefCell:
2931
3032thread_local ! ( static INSPECTOR_DOMAIN_DISPATCHERS : RefCell <HashMap <String , v8:: Global <v8:: Value >>> = RefCell :: new( HashMap :: new( ) ) ) ;
3133
34+ // Set once `Runtime::drop` has torn down the WinRT apartment (RoUninitialize).
35+ // Backing-store deleters that hold COM refs consult this so they skip the
36+ // Release during isolate disposal (which runs after RoUninitialize): the OS
37+ // reclaims those refs at process exit, and calling Release on a dead apartment
38+ // would access-violate. During normal GC the flag is false and refs are freed.
39+ thread_local ! ( static COM_TEARDOWN : std:: cell:: Cell <bool > = const { std:: cell:: Cell :: new( false ) } ) ;
40+
41+ /// Mark that this thread's COM apartment is being torn down. Called from
42+ /// `Runtime::new` (reset) and `Runtime::drop` (set).
43+ pub ( crate ) fn set_com_teardown ( value : bool ) {
44+ COM_TEARDOWN . with ( |c| c. set ( value) ) ;
45+ }
46+
3247/// Drop cached inspector dispatcher handles (isolate-tied `v8::Global`s).
3348/// Called from `Runtime::drop` while the isolate is still alive.
3449pub ( crate ) fn clear_thread_dispatchers ( ) {
@@ -458,6 +473,109 @@ pub(crate) fn handle_buffer_to_pointer(
458473 }
459474}
460475
476+ /// Backing-store deleter that releases the COM reference kept alive for the
477+ /// lifetime of the aliasing ArrayBuffer. `deleter_data` is a `Box<IBuffer>`.
478+ unsafe extern "C" fn release_ibuffer_keepalive (
479+ _data : * mut c_void ,
480+ _byte_length : usize ,
481+ deleter_data : * mut c_void ,
482+ ) {
483+ if deleter_data. is_null ( ) {
484+ return ;
485+ }
486+ let keep_alive = unsafe { Box :: from_raw ( deleter_data as * mut IBuffer ) } ;
487+ if COM_TEARDOWN . with ( |c| c. get ( ) ) {
488+ // Apartment is gone; releasing now would access-violate. Leak the ref —
489+ // the process is exiting and the OS reclaims it.
490+ std:: mem:: forget ( keep_alive) ;
491+ } else {
492+ drop ( keep_alive) ;
493+ }
494+ }
495+
496+ /// `__nsArrayBufferFromBuffer(buffer)` — wrap a `Windows.Storage.Streams.IBuffer`
497+ /// as a zero-copy `ArrayBuffer` aliasing the buffer's native storage. The ArrayBuffer
498+ /// covers `IBuffer.Length` (valid bytes). The IBuffer is AddRef'd and released only
499+ /// once V8 collects the ArrayBuffer, so reads/writes through the view are safe and
500+ /// propagate to the underlying WinRT buffer.
501+ pub ( crate ) fn handle_array_buffer_from_buffer (
502+ scope : & mut v8:: PinScope < ' _ , ' _ > ,
503+ args : v8:: FunctionCallbackArguments ,
504+ mut retval : v8:: ReturnValue ,
505+ ) {
506+ if args. length ( ) < 1 {
507+ throw_js_error (
508+ scope,
509+ "__nsArrayBufferFromBuffer expects a Windows.Storage.Streams.IBuffer" ,
510+ ) ;
511+ return ;
512+ }
513+
514+ let Ok ( obj) = v8:: Local :: < v8:: Object > :: try_from ( args. get ( 0 ) ) else {
515+ throw_js_error (
516+ scope,
517+ "__nsArrayBufferFromBuffer expects a WinRT IBuffer object" ,
518+ ) ;
519+ return ;
520+ } ;
521+
522+ let Some ( instance) = crate :: ns_proxy:: this_instance ( scope, obj) else {
523+ throw_js_error (
524+ scope,
525+ "__nsArrayBufferFromBuffer: value is not a WinRT object (no COM instance)" ,
526+ ) ;
527+ return ;
528+ } ;
529+
530+ // QI for the valid length and the raw data pointer. Both views address the
531+ // same underlying object, so a single AddRef'd ref keeps the storage alive.
532+ let Ok ( buffer) = instance. cast :: < IBuffer > ( ) else {
533+ throw_js_error (
534+ scope,
535+ "__nsArrayBufferFromBuffer: value does not implement Windows.Storage.Streams.IBuffer" ,
536+ ) ;
537+ return ;
538+ } ;
539+ let Ok ( byte_access) = instance. cast :: < IBufferByteAccess > ( ) else {
540+ throw_js_error (
541+ scope,
542+ "__nsArrayBufferFromBuffer: IBuffer does not expose IBufferByteAccess" ,
543+ ) ;
544+ return ;
545+ } ;
546+
547+ let len = buffer. Length ( ) . unwrap_or ( 0 ) as usize ;
548+ let data = match unsafe { byte_access. Buffer ( ) } {
549+ Ok ( p) => p,
550+ Err ( _) => {
551+ throw_js_error ( scope, "__nsArrayBufferFromBuffer: failed to obtain IBuffer data pointer" ) ;
552+ return ;
553+ }
554+ } ;
555+
556+ if data. is_null ( ) || len == 0 {
557+ // Nothing to alias — hand back an empty, owned ArrayBuffer.
558+ let ab = v8:: ArrayBuffer :: new ( scope, 0 ) ;
559+ retval. set ( ab. into ( ) ) ;
560+ return ;
561+ }
562+
563+ // Hand an AddRef'd IBuffer to the deleter so the native storage outlives the
564+ // ArrayBuffer; `byte_access` is dropped here but the object stays alive.
565+ let keep_alive = Box :: into_raw ( Box :: new ( buffer) ) as * mut c_void ;
566+ let backing = unsafe {
567+ v8:: ArrayBuffer :: new_backing_store_from_ptr (
568+ data as * mut c_void ,
569+ len,
570+ release_ibuffer_keepalive,
571+ keep_alive,
572+ )
573+ } ;
574+ let shared = backing. make_shared ( ) ;
575+ let ab = v8:: ArrayBuffer :: with_backing_store ( scope, & shared) ;
576+ retval. set ( ab. into ( ) ) ;
577+ }
578+
461579pub ( crate ) fn handle_proxy_write_text_file (
462580 scope : & mut v8:: PinScope < ' _ , ' _ > ,
463581 args : v8:: FunctionCallbackArguments ,
@@ -1488,6 +1606,20 @@ const HELPER_SOURCE: &str = r#"
14881606 fromWinRTDateTimeTicks: fromWinRTDateTimeTicks,
14891607 pointerKey: pointerKey,
14901608 pointerFromBuffer: pointerFromBuffer,
1609+ // arrayBufferFromBuffer(buffer) — zero-copy view over a
1610+ // Windows.Storage.Streams.IBuffer. The returned ArrayBuffer aliases
1611+ // the buffer's native storage (covers IBuffer.Length valid bytes) and
1612+ // keeps the IBuffer alive until it is collected. Use `.byteLength` on
1613+ // the result, or wrap in a Uint8Array/DataView to read or write through.
1614+ arrayBufferFromBuffer: function (buffer) {
1615+ if (buffer == null) {
1616+ return null;
1617+ }
1618+ if (typeof globalThis.__nsArrayBufferFromBuffer !== 'function') {
1619+ throw new Error('NSWinRT.interop.arrayBufferFromBuffer is unavailable in this runtime');
1620+ }
1621+ return globalThis.__nsArrayBufferFromBuffer(buffer);
1622+ },
14911623 trackBufferSource: trackBufferSource,
14921624 resolveTrackedBuffer: resolveTrackedBuffer,
14931625 byteLengthOf: function (value) {
@@ -4615,6 +4747,7 @@ pub(crate) fn init_async_helpers(
46154747 register ! ( "__nsEnqueueMicrotask" , handle_enqueue_microtask) ;
46164748 register ! ( "__nsPointerKey" , handle_pointer_key) ;
46174749 register ! ( "__nsBufferToPointer" , handle_buffer_to_pointer) ;
4750+ register ! ( "__nsArrayBufferFromBuffer" , handle_array_buffer_from_buffer) ;
46184751 register ! ( "__nsProxyWriteTextFile" , handle_proxy_write_text_file) ;
46194752 register ! ( "__nsProxyCompileProject" , handle_proxy_compile_project) ;
46204753 register ! ( "__nsProxyRegisterManifest" , handle_proxy_register_manifest) ;
0 commit comments