Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions runtime/src/interop_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2683,3 +2683,29 @@ fn ireference_parameters_and_results() {
"#,
);
}

/// Struct `IReference<T>` parameters take a plain object or a struct value, and null clears them.
#[test]
fn ireference_struct_parameters() {
run_js_assert(
"ireference_struct_parameters",
r#"
const argb = (c) => c && `${c.A},${c.R},${c.G},${c.B}`;
const quota = new Windows.Storage.Provider.StorageProviderQuotaUI();
if (quota.QuotaUsedColor !== null) throw new Error(`unset color was ${argb(quota.QuotaUsedColor)}`);
quota.QuotaUsedColor = { A: 255, R: 10, G: 20, B: 30 };
if (argb(quota.QuotaUsedColor) !== '255,10,20,30') throw new Error(`object color was ${argb(quota.QuotaUsedColor)}`);
quota.QuotaUsedColor = Windows.UI.ColorHelper.FromArgb(128, 1, 2, 3);
if (argb(quota.QuotaUsedColor) !== '128,1,2,3') throw new Error(`ColorHelper color was ${argb(quota.QuotaUsedColor)}`);
quota.QuotaUsedColor = null;
if (quota.QuotaUsedColor !== null) throw new Error(`cleared color was ${argb(quota.QuotaUsedColor)}`);

const share = new Windows.ApplicationModel.DataTransfer.ShareUIOptions();
share.SelectionRect = { X: 1, Y: 2, Width: 3.5, Height: 4 };
const r = share.SelectionRect;
if (!r || r.X !== 1 || r.Y !== 2 || r.Width !== 3.5 || r.Height !== 4) {
throw new Error(`SelectionRect was ${r && `${r.X},${r.Y},${r.Width},${r.Height}`}`);
}
"#,
);
}
1 change: 1 addition & 0 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ mod property_call;
#[cfg(feature = "classic")]
mod proxy_manifest_loader;
pub mod source_protect;
mod struct_reference;
#[cfg(feature = "classic")]
pub mod timers;
#[cfg(feature = "classic")]
Expand Down
15 changes: 15 additions & 0 deletions runtime/src/napi_engine/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,21 @@ pub fn box_as_ireference(
None,
));
}
if vt == ValueType::Object {
if let Some(declaration) = crate::struct_reference::struct_declaration(inner_type) {
let obj: JsObject = unsafe { arg.cast() };
let mut bytes = Vec::new();
{
let lock = declaration.read();
let sd = lock
.as_any()
.downcast_ref::<metadata::declarations::struct_declaration::StructDeclaration>()?;
crate::property_call::append_struct_object_bytes_napi(env, &mut bytes, &obj, sd);
}
let reference = crate::struct_reference::create(bytes, *iid);
return Some((NativeValue { pointer: reference.as_raw() }, Some(reference)));
}
}
let wrapped = if vt == ValueType::Object || vt == ValueType::Function {
let obj: JsObject = unsafe { arg.cast() };
try_get_external_handle(env, &obj).filter(|ptr| !ptr.is_null())
Expand Down
141 changes: 141 additions & 0 deletions runtime/src/struct_reference.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//! A JS struct value passed where WinRT expects `IReference<T>` of a struct, such as the nullable
//! `IReference<Windows.UI.Color>` colors of `AppWindowTitleBar`.
//!
//! `PropertyValue` only boxes primitives and a few Foundation types, so the struct's bytes (laid
//! out as for a struct parameter) are held by an agile object that answers the reference's IID,
//! which is computed from its closed generic name, and copies them out of `get_Value`.

use std::ffi::c_void;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;

use metadata::declarations::declaration::{Declaration, DeclarationKind};
use metadata::meta_data_reader::MetadataReader;
use parking_lot::RwLock;
use windows::core::{Interface, GUID, HRESULT, IUnknown};

type Raw = *mut c_void;

const E_NOINTERFACE: HRESULT = HRESULT(0x80004002u32 as i32);
const E_POINTER: HRESULT = HRESULT(0x80004003u32 as i32);
const S_OK: HRESULT = HRESULT(0);
const IID_IINSPECTABLE: GUID = GUID::from_u128(0xAF86E2E0_B12D_4c6a_9C5A_D7AA65101E90);
const IID_IAGILE_OBJECT: GUID = GUID::from_u128(0x94ea2b94_e9cc_49e0_c0ff_ee64ca8f5b90);

/// The struct declaration for an `IReference<T>`'s `T`, or `None` when `T` isn't a struct.
/// `TimeSpan` and `DateTime` stay with `PropertyValue`, which also accepts a number for them.
pub(crate) fn struct_declaration(inner_type: &str) -> Option<Arc<RwLock<dyn Declaration>>> {
let name = inner_type.trim();
if matches!(name, "Windows.Foundation.TimeSpan" | "Windows.Foundation.DateTime") {
return None;
}
let declaration = MetadataReader::find_by_name(crate::helpers::strip_generic_suffix(name))?;
let is_struct = declaration.read().kind() == DeclarationKind::Struct;
is_struct.then_some(declaration)
}

#[repr(C)]
struct Vtbl {
query_interface: unsafe extern "system" fn(Raw, *const GUID, *mut Raw) -> HRESULT,
add_ref: unsafe extern "system" fn(Raw) -> u32,
release: unsafe extern "system" fn(Raw) -> u32,
get_iids: unsafe extern "system" fn(Raw, *mut u32, *mut *mut GUID) -> HRESULT,
get_runtime_class_name: unsafe extern "system" fn(Raw, *mut Raw) -> HRESULT,
get_trust_level: unsafe extern "system" fn(Raw, *mut i32) -> HRESULT,
get_value: unsafe extern "system" fn(Raw, *mut c_void) -> HRESULT,
}

#[repr(C)]
struct StructReference {
vtbl: &'static Vtbl,
refs: AtomicU32,
iid: GUID,
bytes: Box<[u8]>,
}

unsafe fn from_raw<'a>(this: Raw) -> &'a StructReference {
&*(this as *const StructReference)
}

unsafe extern "system" fn query_interface(this: Raw, iid: *const GUID, out: *mut Raw) -> HRESULT {
if out.is_null() || iid.is_null() {
return E_POINTER;
}
let object = from_raw(this);
let iid = &*iid;
if *iid == IUnknown::IID || *iid == IID_IINSPECTABLE || *iid == IID_IAGILE_OBJECT || *iid == object.iid {
object.refs.fetch_add(1, Ordering::Relaxed);
*out = this;
return S_OK;
}
*out = std::ptr::null_mut();
E_NOINTERFACE
}

unsafe extern "system" fn add_ref(this: Raw) -> u32 {
from_raw(this).refs.fetch_add(1, Ordering::Relaxed) + 1
}

unsafe extern "system" fn release(this: Raw) -> u32 {
let remaining = from_raw(this).refs.fetch_sub(1, Ordering::Release) - 1;
if remaining == 0 {
std::sync::atomic::fence(Ordering::Acquire);
drop(Box::from_raw(this as *mut StructReference));
}
remaining
}

unsafe extern "system" fn get_iids(_: Raw, count: *mut u32, iids: *mut *mut GUID) -> HRESULT {
if count.is_null() || iids.is_null() {
return E_POINTER;
}
*count = 0;
*iids = std::ptr::null_mut();
S_OK
}

unsafe extern "system" fn get_runtime_class_name(_: Raw, name: *mut Raw) -> HRESULT {
if name.is_null() {
return E_POINTER;
}
*name = std::ptr::null_mut();
S_OK
}

unsafe extern "system" fn get_trust_level(_: Raw, level: *mut i32) -> HRESULT {
if level.is_null() {
return E_POINTER;
}
*level = 0;
S_OK
}

unsafe extern "system" fn get_value(this: Raw, out: *mut c_void) -> HRESULT {
if out.is_null() {
return E_POINTER;
}
let bytes = &from_raw(this).bytes;
std::ptr::copy_nonoverlapping(bytes.as_ptr(), out as *mut u8, bytes.len());
S_OK
}

static VTBL: Vtbl = Vtbl {
query_interface,
add_ref,
release,
get_iids,
get_runtime_class_name,
get_trust_level,
get_value,
};

/// An `IReference<T>` (`iid`) holding a struct's bytes.
pub(crate) fn create(bytes: Vec<u8>, iid: GUID) -> IUnknown {
let object = Box::new(StructReference {
vtbl: &VTBL,
refs: AtomicU32::new(1),
iid,
bytes: bytes.into_boxed_slice(),
});
unsafe { IUnknown::from_raw(Box::into_raw(object) as Raw) }
}
15 changes: 15 additions & 0 deletions runtime/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,21 @@ pub fn box_as_ireference(
None,
));
}
if arg.is_object() {
if let Some(declaration) = crate::struct_reference::struct_declaration(inner_type) {
let obj = arg.to_object(scope)?;
let mut bytes = Vec::new();
{
let lock = declaration.read();
let sd = lock
.as_any()
.downcast_ref::<metadata::declarations::struct_declaration::StructDeclaration>()?;
crate::property_call::append_struct_object_bytes(&mut bytes, scope, obj, sd);
}
let reference = crate::struct_reference::create(bytes, *iid);
return Some((NativeValue { pointer: reference.as_raw() }, Some(reference)));
}
}
let wrapped = if arg.is_object() {
arg.to_object(scope)
.and_then(|obj| try_get_external_handle(scope, obj))
Expand Down
Loading