mirror of
https://github.com/lexmount/moli.git
synced 2026-11-01 16:05:37 +00:00
refactor(webapi): add native cross-origin member declarations
Generate stateless method and accessor tables with receiver checks and native value callbacks. Cache FunctionTemplates per isolate and use V8's per-context function instances, without retaining target Windows or observer realms. Cover function identity, realm prototypes, descriptors, receiver validation, exception and termination propagation, and context collection.
This commit is contained in:
@@ -15,6 +15,9 @@ struct DeclaredField {
|
||||
attrs: FieldAttrs,
|
||||
}
|
||||
|
||||
mod cross_origin;
|
||||
pub(crate) use cross_origin::expand_webapi_cross_origin;
|
||||
|
||||
fn declared_fields(
|
||||
data: &Data,
|
||||
rename_all: RenameRule,
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
use super::*;
|
||||
|
||||
pub(crate) fn expand_webapi_cross_origin(
|
||||
input: DeriveInput,
|
||||
) -> Result<proc_macro2::TokenStream, Error> {
|
||||
let name = &input.ident;
|
||||
if !input.generics.params.is_empty() {
|
||||
return Err(Error::new_spanned(
|
||||
&input.generics,
|
||||
"cross-origin declarations cannot carry runtime state",
|
||||
));
|
||||
}
|
||||
let attrs = parse_object_attrs(&input.attrs)?;
|
||||
if !matches!(attrs.role, None | Some(ObjectRole::Plain))
|
||||
|| attrs.prototype.is_some()
|
||||
|| attrs.own_to_string_tag.is_some()
|
||||
|| attrs.fallback_to_string_tag.is_some()
|
||||
|| attrs.scope_lifetime.is_some()
|
||||
|| attrs.default_data_properties
|
||||
|| attrs.default_enumerable
|
||||
{
|
||||
return Err(Error::new_spanned(
|
||||
input,
|
||||
"cross-origin declarations describe stateless property tables, not object initialization",
|
||||
));
|
||||
}
|
||||
let fields = declared_fields(
|
||||
&input.data,
|
||||
attrs.rename_all,
|
||||
FieldDefaults {
|
||||
receiver: attrs.receiver.as_ref(),
|
||||
..FieldDefaults::default()
|
||||
},
|
||||
)?;
|
||||
let mut helpers = Vec::new();
|
||||
let mut entries = Vec::new();
|
||||
for DeclaredField { field, attrs } in fields {
|
||||
if !type_is_unit(&field.ty)
|
||||
|| attrs.data.is_some()
|
||||
|| attrs.setter_data.is_some()
|
||||
|| attrs.returns_promise
|
||||
|| attrs.symbol.is_some()
|
||||
|| attrs.getter_value.is_some()
|
||||
|| attrs.value.is_some()
|
||||
|| attrs.init.is_some()
|
||||
|| attrs.enumerable
|
||||
|| attrs.dont_delete
|
||||
{
|
||||
return Err(Error::new_spanned(
|
||||
field,
|
||||
"cross-origin members must be stateless string-keyed methods or accessors",
|
||||
));
|
||||
}
|
||||
let property_name = match attrs.name.as_ref() {
|
||||
Some(syn::Expr::Lit(syn::ExprLit {
|
||||
lit: Lit::Str(name),
|
||||
..
|
||||
})) => name.clone(),
|
||||
_ => {
|
||||
return Err(Error::new_spanned(
|
||||
&field,
|
||||
"cross-origin member names must be string literals",
|
||||
));
|
||||
}
|
||||
};
|
||||
let ident = field.ident.as_ref().expect("named field");
|
||||
let receiver_check = attrs.receiver.as_ref().map(|receiver| {
|
||||
quote! {
|
||||
if !#receiver(__moli_scope, __moli_receiver) {
|
||||
::moli_webapi_declare::__private::throw_illegal_invocation(__moli_scope);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
});
|
||||
let function = |suffix: &str,
|
||||
callback: proc_macro2::TokenStream,
|
||||
length: i32,
|
||||
function_name: LitStr| {
|
||||
let factory = format_ident!("__cross_origin_{}_{}_template", ident, suffix);
|
||||
let helper = quote! {
|
||||
fn #factory<'s>(
|
||||
scope: &::moli_webapi_declare::v8::PinScope<'s, '_>,
|
||||
) -> ::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::FunctionTemplate> {
|
||||
let template = ::moli_webapi_declare::v8::FunctionTemplate::builder(#callback)
|
||||
.length(#length)
|
||||
.constructor_behavior(::moli_webapi_declare::v8::ConstructorBehavior::Throw)
|
||||
.build(scope);
|
||||
template.remove_prototype();
|
||||
template.set_class_name(::moli_webapi_declare::__private::v8str(scope, #function_name));
|
||||
template
|
||||
}
|
||||
};
|
||||
(
|
||||
helper,
|
||||
quote!(::moli_webapi_declare::CrossOriginFunction { build: Self::#factory }),
|
||||
)
|
||||
};
|
||||
match attrs.kind {
|
||||
FieldKind::Method => {
|
||||
let callback = attrs
|
||||
.callback
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::new_spanned(&field, "method requires callback"))?;
|
||||
let callback = expand_callback(callback, &attrs, false);
|
||||
let (helper, function) = function(
|
||||
"method",
|
||||
callback,
|
||||
attrs.length.unwrap_or(0),
|
||||
property_name.clone(),
|
||||
);
|
||||
helpers.push(helper);
|
||||
entries.push(quote! {
|
||||
::moli_webapi_declare::CrossOriginProperty::Method { name: #property_name, function: #function }
|
||||
});
|
||||
}
|
||||
FieldKind::AccessorProperty => {
|
||||
if attrs.getter.is_none() && attrs.setter.is_none() {
|
||||
return Err(Error::new_spanned(
|
||||
field,
|
||||
"cross-origin accessor requires a getter or setter",
|
||||
));
|
||||
}
|
||||
let mut accessors = Vec::new();
|
||||
for (suffix, callback, length) in [
|
||||
("get", attrs.getter.as_ref(), 0),
|
||||
("set", attrs.setter.as_ref(), 1),
|
||||
] {
|
||||
let Some(callback) = callback else {
|
||||
accessors.push(quote!(None));
|
||||
continue;
|
||||
};
|
||||
let value_helper = format_ident!("__cross_origin_{}_{}", ident, suffix);
|
||||
let callback_helper =
|
||||
format_ident!("__cross_origin_{}_{}_callback", ident, suffix);
|
||||
let function_name = LitStr::new(
|
||||
&format!("{suffix} {}", property_name.value()),
|
||||
property_name.span(),
|
||||
);
|
||||
let (helper, function) = function(
|
||||
suffix,
|
||||
quote!(Self::#callback_helper),
|
||||
length,
|
||||
function_name,
|
||||
);
|
||||
helpers.push(helper);
|
||||
if length == 0 {
|
||||
helpers.push(quote! {
|
||||
fn #value_helper<'s>(
|
||||
__moli_scope: &mut ::moli_webapi_declare::v8::PinScope<'s, '_>,
|
||||
__moli_receiver: ::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::Object>,
|
||||
) -> Option<::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::Value>> {
|
||||
#receiver_check
|
||||
#callback(__moli_scope, __moli_receiver)
|
||||
}
|
||||
fn #callback_helper<'s>(
|
||||
__moli_scope: &mut ::moli_webapi_declare::v8::PinScope<'s, '_>,
|
||||
args: ::moli_webapi_declare::v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: ::moli_webapi_declare::v8::ReturnValue<'s>,
|
||||
) {
|
||||
if let Some(value) = Self::#value_helper(__moli_scope, args.this()) { rv.set(value); }
|
||||
}
|
||||
});
|
||||
} else {
|
||||
helpers.push(quote! {
|
||||
fn #value_helper<'s>(
|
||||
__moli_scope: &mut ::moli_webapi_declare::v8::PinScope<'s, '_>,
|
||||
__moli_receiver: ::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::Object>,
|
||||
value: ::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::Value>,
|
||||
) -> Option<()> {
|
||||
#receiver_check
|
||||
#callback(__moli_scope, __moli_receiver, value)
|
||||
}
|
||||
fn #callback_helper<'s>(
|
||||
__moli_scope: &mut ::moli_webapi_declare::v8::PinScope<'s, '_>,
|
||||
args: ::moli_webapi_declare::v8::FunctionCallbackArguments<'s>,
|
||||
_rv: ::moli_webapi_declare::v8::ReturnValue<'s>,
|
||||
) {
|
||||
let _ = Self::#value_helper(__moli_scope, args.this(), args.get(0));
|
||||
}
|
||||
});
|
||||
}
|
||||
accessors.push(quote!(Some((Self::#value_helper, #function))));
|
||||
}
|
||||
let getter = &accessors[0];
|
||||
let setter = &accessors[1];
|
||||
entries.push(quote! {
|
||||
::moli_webapi_declare::CrossOriginProperty::Accessor {
|
||||
name: #property_name, getter: #getter, setter: #setter,
|
||||
}
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::new_spanned(
|
||||
&field,
|
||||
"cross-origin declarations only support method and accessor_property",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(quote! {
|
||||
impl #name { #(#helpers)* }
|
||||
impl ::moli_webapi_declare::WebApiCrossOriginDeclaration for #name {
|
||||
fn cross_origin_properties() -> &'static [::moli_webapi_declare::CrossOriginProperty] {
|
||||
&[#(#entries),*]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rejects_declarations_that_would_capture_runtime_state() {
|
||||
let invalid: [DeriveInput; 3] = [
|
||||
syn::parse_quote! {
|
||||
#[webapi(plain)]
|
||||
struct Bad<T> { #[webapi(method, callback = callback)] operation: T }
|
||||
},
|
||||
syn::parse_quote! {
|
||||
#[webapi(plain)]
|
||||
struct Bad { #[webapi(method, callback = callback, data = self.state)] operation: (), state: usize }
|
||||
},
|
||||
syn::parse_quote! {
|
||||
#[webapi(plain)]
|
||||
struct Bad { #[webapi(method, name = runtime_name(), callback = callback)] operation: () }
|
||||
},
|
||||
];
|
||||
for input in invalid {
|
||||
assert!(expand_webapi_cross_origin(input).is_err());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,17 @@ mod expand;
|
||||
use proc_macro::TokenStream;
|
||||
use syn::{DeriveInput, parse_macro_input};
|
||||
|
||||
/// Declares native cross-origin methods and accessors without an intermediate
|
||||
/// JavaScript object. Accessor callbacks return Option<Value> / Option<()> and
|
||||
/// are also adapted to realm-correct descriptor functions.
|
||||
#[proc_macro_derive(WebApiCrossOrigin, attributes(webapi))]
|
||||
pub fn derive_webapi_cross_origin(input: TokenStream) -> TokenStream {
|
||||
match expand::expand_webapi_cross_origin(parse_macro_input!(input as DeriveInput)) {
|
||||
Ok(tokens) => tokens.into(),
|
||||
Err(error) => error.to_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Derives a Web API function-template declaration binder.
|
||||
///
|
||||
/// The generated implementation creates a V8 `FunctionTemplate`, sets its class
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
//! Native cross-origin member declarations. Templates contain callback metadata
|
||||
//! only; V8 owns each context's unique function instance and its lifetime.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use moli_v8_util::v8str;
|
||||
|
||||
pub type CrossOriginGetter = for<'s, 'i> fn(
|
||||
&mut v8::PinScope<'s, 'i>,
|
||||
v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Value>>;
|
||||
pub type CrossOriginSetter = for<'s, 'i> fn(
|
||||
&mut v8::PinScope<'s, 'i>,
|
||||
v8::Local<'s, v8::Object>,
|
||||
v8::Local<'s, v8::Value>,
|
||||
) -> Option<()>;
|
||||
|
||||
pub struct CrossOriginFunction {
|
||||
pub build: for<'s, 'i> fn(&v8::PinScope<'s, 'i>) -> v8::Local<'s, v8::FunctionTemplate>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FunctionTemplates(HashMap<usize, v8::Global<v8::FunctionTemplate>>);
|
||||
|
||||
impl CrossOriginFunction {
|
||||
pub fn get<'s>(
|
||||
&'static self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
) -> Option<v8::Local<'s, v8::Function>> {
|
||||
// The key identifies an immutable declaration member, never a Window
|
||||
// or a realm. No callback data can retain a target or an observer.
|
||||
let key = self as *const Self as usize;
|
||||
let cached = scope
|
||||
.get_slot::<FunctionTemplates>()
|
||||
.and_then(|templates| templates.0.get(&key))
|
||||
.map(|template| v8::Local::new(scope, template));
|
||||
let template = match cached {
|
||||
Some(template) => template,
|
||||
None => {
|
||||
let template = (self.build)(scope);
|
||||
let retained = v8::Global::new(scope, template);
|
||||
if scope.get_slot::<FunctionTemplates>().is_none() {
|
||||
scope.set_slot(FunctionTemplates::default());
|
||||
}
|
||||
scope
|
||||
.get_slot_mut::<FunctionTemplates>()
|
||||
.expect("template store was initialized")
|
||||
.0
|
||||
.insert(key, retained);
|
||||
template
|
||||
}
|
||||
};
|
||||
template.get_function(scope)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum CrossOriginProperty {
|
||||
Method {
|
||||
name: &'static str,
|
||||
function: CrossOriginFunction,
|
||||
},
|
||||
Accessor {
|
||||
name: &'static str,
|
||||
getter: Option<(CrossOriginGetter, CrossOriginFunction)>,
|
||||
setter: Option<(CrossOriginSetter, CrossOriginFunction)>,
|
||||
},
|
||||
}
|
||||
|
||||
impl CrossOriginProperty {
|
||||
pub const fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Method { name, .. } | Self::Accessor { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get<'s>(
|
||||
&'static self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
match self {
|
||||
Self::Method { function, .. } => function.get(scope).map(Into::into),
|
||||
Self::Accessor {
|
||||
getter: Some((getter, _)),
|
||||
..
|
||||
} => getter(scope, receiver),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set<'s>(
|
||||
&'static self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
) -> Option<()> {
|
||||
match self {
|
||||
Self::Accessor {
|
||||
setter: Some((setter, _)),
|
||||
..
|
||||
} => setter(scope, receiver, value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn has_getter(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Method { .. }
|
||||
| Self::Accessor {
|
||||
getter: Some(_),
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub const fn has_setter(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Accessor {
|
||||
setter: Some(_),
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn descriptor<'s>(
|
||||
&'static self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let descriptor = v8::Object::new(scope);
|
||||
let fields: [(&str, v8::Local<v8::Value>); 2] = match self {
|
||||
Self::Method { function, .. } => [
|
||||
("value", function.get(scope)?.into()),
|
||||
("writable", v8::Boolean::new(scope, false).into()),
|
||||
],
|
||||
Self::Accessor { getter, setter, .. } => {
|
||||
let get = match getter {
|
||||
Some((_, function)) => function.get(scope)?.into(),
|
||||
None => v8::undefined(scope).into(),
|
||||
};
|
||||
let set = match setter {
|
||||
Some((_, function)) => function.get(scope)?.into(),
|
||||
None => v8::undefined(scope).into(),
|
||||
};
|
||||
[("get", get), ("set", set)]
|
||||
}
|
||||
};
|
||||
for (name, value) in fields.into_iter().chain([
|
||||
("enumerable", v8::Boolean::new(scope, false).into()),
|
||||
("configurable", v8::Boolean::new(scope, true).into()),
|
||||
]) {
|
||||
descriptor
|
||||
.define_own_property(
|
||||
scope,
|
||||
v8str(scope, name).into(),
|
||||
value,
|
||||
v8::PropertyAttribute::NONE,
|
||||
)
|
||||
.filter(|defined| *defined)?;
|
||||
}
|
||||
Some(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WebApiCrossOriginDeclaration {
|
||||
fn cross_origin_properties() -> &'static [CrossOriginProperty];
|
||||
|
||||
fn cross_origin_property(name: &str) -> Option<&'static CrossOriginProperty> {
|
||||
Self::cross_origin_properties()
|
||||
.iter()
|
||||
.find(|property| property.name() == name)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,14 @@
|
||||
//! `moli-webidl` and focuses on constructing V8 interface objects,
|
||||
//! prototypes, and branded object instances.
|
||||
//!
|
||||
//! `#[derive(WebApiCrossOrigin)]` describes a stateless cross-origin property
|
||||
//! table for native access-check handlers. Methods use ordinary V8 callbacks;
|
||||
//! accessors use native value callbacks returning `Option`, with receiver
|
||||
//! predicates checked before either invocation. Function templates are cached
|
||||
//! per isolate and produce a unique function in each accessing context. The
|
||||
//! table and templates never capture a target Window or an observer context.
|
||||
//! Runtime V8 failures propagate to the script execution boundary.
|
||||
//!
|
||||
//! # Object declaration model
|
||||
//!
|
||||
//! A `#[derive(WebApiObject)]` struct describes the JavaScript surface that must
|
||||
@@ -241,6 +249,7 @@ extern crate self as moli_webapi_declare;
|
||||
|
||||
mod brand;
|
||||
mod callback;
|
||||
mod cross_origin;
|
||||
mod declaration;
|
||||
mod error;
|
||||
mod interface;
|
||||
@@ -251,7 +260,7 @@ mod value;
|
||||
pub mod __private;
|
||||
|
||||
pub use interface::WebApiInterfaceDescriptor;
|
||||
pub use moli_webapi_declare_derive::{WebApiFunctionTemplate, WebApiObject};
|
||||
pub use moli_webapi_declare_derive::{WebApiCrossOrigin, WebApiFunctionTemplate, WebApiObject};
|
||||
pub use v8;
|
||||
|
||||
/// Adapts a manual native constructor to the same identity initialization used
|
||||
@@ -282,6 +291,10 @@ pub use brand::{
|
||||
register_web_api_proxy, web_api_object_target, web_api_object_type,
|
||||
};
|
||||
|
||||
pub use cross_origin::{
|
||||
CrossOriginFunction, CrossOriginGetter, CrossOriginProperty, CrossOriginSetter,
|
||||
WebApiCrossOriginDeclaration,
|
||||
};
|
||||
pub use declaration::{
|
||||
DataPropertyDescriptorDeclaration, ObjectLiteralDeclaration, WebApiFunctionTemplateDeclaration,
|
||||
WebApiObjectDeclaration,
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
use std::pin::pin;
|
||||
|
||||
use moli_v8_test_util::ensure_v8;
|
||||
use moli_v8_util::{get_private_value, set_private_value, v8str};
|
||||
use moli_webapi_declare::{WebApiCrossOrigin, WebApiCrossOriginDeclaration};
|
||||
|
||||
const VALUE: &str = "__crossOriginTestValue";
|
||||
|
||||
fn receiver<'s>(scope: &mut v8::PinScope<'s, '_>, object: v8::Local<'s, v8::Object>) -> bool {
|
||||
get_private_value(scope, object, VALUE).is_some()
|
||||
}
|
||||
|
||||
fn get<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
get_private_value(scope, object, VALUE)
|
||||
}
|
||||
|
||||
fn set<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
) -> Option<()> {
|
||||
let value = value.to_string(scope)?;
|
||||
set_private_value(scope, object, VALUE, value.into());
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn method<'s>(
|
||||
_scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s>,
|
||||
) {
|
||||
rv.set(args.this().into());
|
||||
}
|
||||
|
||||
// Fields are declaration metadata; no runtime object carries this state.
|
||||
#[allow(dead_code)]
|
||||
#[derive(WebApiCrossOrigin)]
|
||||
#[webapi(plain, receiver = receiver)]
|
||||
struct Members {
|
||||
#[webapi(method, length = 1, callback = method)]
|
||||
operation: (),
|
||||
#[webapi(accessor_property, getter = get, setter = set)]
|
||||
value: (),
|
||||
#[webapi(accessor_property, setter = set)]
|
||||
write_only: (),
|
||||
}
|
||||
|
||||
fn eval<'s>(scope: &mut v8::PinScope<'s, '_>, source: &str) -> v8::Local<'s, v8::Value> {
|
||||
let source = v8::String::new(scope, source).unwrap();
|
||||
v8::Script::compile(scope, source, None)
|
||||
.unwrap()
|
||||
.run(scope)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_members_share_templates_but_keep_functions_in_the_accessing_context() {
|
||||
ensure_v8();
|
||||
let mut isolate = v8::Isolate::new(Default::default());
|
||||
let scope = pin!(v8::HandleScope::new(&mut isolate));
|
||||
let scope = &mut scope.init();
|
||||
let a = v8::Context::new(scope, Default::default());
|
||||
let b = v8::Context::new(scope, Default::default());
|
||||
let member = Members::cross_origin_property("operation").unwrap();
|
||||
let function_a = {
|
||||
let scope = &mut v8::ContextScope::new(scope, a);
|
||||
let object = v8::Object::new(scope);
|
||||
let value = v8str(scope, "initial");
|
||||
set_private_value(scope, object, VALUE, value.into());
|
||||
let function = member.get(scope, object).unwrap();
|
||||
assert!(function.strict_equals(member.get(scope, object).unwrap()));
|
||||
let global = a.global(scope);
|
||||
global
|
||||
.set(scope, v8str(scope, "object").into(), object.into())
|
||||
.unwrap();
|
||||
for (name, value) in [
|
||||
("method", function),
|
||||
("methodDescriptor", member.descriptor(scope).unwrap().into()),
|
||||
(
|
||||
"descriptor",
|
||||
Members::cross_origin_property("value")
|
||||
.unwrap()
|
||||
.descriptor(scope)
|
||||
.unwrap()
|
||||
.into(),
|
||||
),
|
||||
(
|
||||
"writeOnly",
|
||||
Members::cross_origin_property("writeOnly")
|
||||
.unwrap()
|
||||
.descriptor(scope)
|
||||
.unwrap()
|
||||
.into(),
|
||||
),
|
||||
] {
|
||||
global.set(scope, v8str(scope, name).into(), value).unwrap();
|
||||
}
|
||||
assert!(eval(scope, r#"
|
||||
(() => {
|
||||
function throwsTypeError(callback) { try { callback(); } catch (e) { return e instanceof TypeError; } return false; }
|
||||
if (Object.getPrototypeOf(method) !== Function.prototype || method.name !== 'operation' || method.length !== 1 || Object.hasOwn(method, 'prototype')) return false;
|
||||
if (method.call(object) !== object || !throwsTypeError(() => method.call({})) || !throwsTypeError(() => new method())) return false;
|
||||
if (methodDescriptor.writable || methodDescriptor.enumerable || !methodDescriptor.configurable) return false;
|
||||
if (descriptor.get.name !== 'get value' || descriptor.get.length !== 0 || descriptor.set.name !== 'set value' || descriptor.set.length !== 1 || descriptor.enumerable || !descriptor.configurable) return false;
|
||||
if (descriptor.get.call(object) !== 'initial' || !throwsTypeError(() => descriptor.get.call({}))) return false;
|
||||
let conversions = 0;
|
||||
if (!throwsTypeError(() => descriptor.set.call({}, {toString() { conversions++; return 'bad'; }})) || conversions !== 0) return false;
|
||||
const exception = new Error('conversion');
|
||||
try { descriptor.set.call(object, {toString() { throw exception; }}); return false; } catch (e) { if (e !== exception) return false; }
|
||||
descriptor.set.call(object, 'updated');
|
||||
return descriptor.get.call(object) === 'updated' && writeOnly.get === undefined && writeOnly.set.name === 'set writeOnly';
|
||||
})()
|
||||
"#).is_true());
|
||||
v8::Global::new(scope, function)
|
||||
};
|
||||
let scope = &mut v8::ContextScope::new(scope, b);
|
||||
let object = v8::Object::new(scope);
|
||||
let function_b = member.get(scope, object).unwrap();
|
||||
let function_a = v8::Local::new(scope, &function_a);
|
||||
assert!(!function_a.strict_equals(function_b));
|
||||
let function_b = v8::Local::<v8::Object>::try_from(function_b).unwrap();
|
||||
assert!(
|
||||
function_b
|
||||
.get_prototype(scope)
|
||||
.unwrap()
|
||||
.strict_equals(eval(scope, "Function.prototype"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_member_failure_propagates_script_termination() {
|
||||
ensure_v8();
|
||||
let mut isolate = v8::Isolate::new(Default::default());
|
||||
let scope = pin!(v8::HandleScope::new(&mut isolate));
|
||||
let scope = &mut scope.init();
|
||||
let context = v8::Context::new(scope, Default::default());
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let member = Members::cross_origin_property("value").unwrap();
|
||||
let object = v8::Object::new(scope);
|
||||
let value = v8str(scope, "initial");
|
||||
set_private_value(scope, object, VALUE, value.into());
|
||||
let input = v8::Object::new(scope);
|
||||
{
|
||||
v8::tc_scope!(let scope, scope);
|
||||
scope.terminate_execution();
|
||||
assert!(member.set(scope, object, input.into()).is_none());
|
||||
assert!(
|
||||
scope.has_terminated(),
|
||||
"termination must reach the execution boundary"
|
||||
);
|
||||
assert!(!scope.can_continue());
|
||||
scope.cancel_terminate_execution();
|
||||
}
|
||||
assert!(member.set(scope, object, input.into()).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn isolate_template_cache_does_not_retain_an_accessing_context() {
|
||||
ensure_v8();
|
||||
let mut isolate = v8::Isolate::new(Default::default());
|
||||
let weak = {
|
||||
let scope = pin!(v8::HandleScope::new(&mut isolate));
|
||||
let scope = &mut scope.init();
|
||||
let context = v8::Context::new(scope, Default::default());
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let object = v8::Object::new(scope);
|
||||
assert!(
|
||||
Members::cross_origin_property("operation")
|
||||
.unwrap()
|
||||
.get(scope, object)
|
||||
.is_some()
|
||||
);
|
||||
v8::Weak::new(scope, context)
|
||||
};
|
||||
isolate.low_memory_notification();
|
||||
let scope = pin!(v8::HandleScope::new(&mut isolate));
|
||||
let scope = &mut scope.init();
|
||||
assert!(weak.to_local(scope).is_none());
|
||||
}
|
||||
Reference in New Issue
Block a user