mirror of
https://github.com/lexmount/moli.git
synced 2026-09-23 08:01:28 +00:00
2991 lines
102 KiB
Rust
2991 lines
102 KiB
Rust
use std::pin::pin;
|
|
|
|
use moli_v8_test_util::ensure_v8;
|
|
use moli_webapi_declare::{
|
|
ObjectLiteralDeclaration, WebApiFunctionTemplate, WebApiFunctionTemplateDeclaration,
|
|
WebApiObject, WebApiValue, define_array_data_property,
|
|
};
|
|
|
|
fn install_template<'s, D: WebApiFunctionTemplateDeclaration>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
global: v8::Local<'s, v8::Object>,
|
|
) -> v8::Local<'s, v8::Function> {
|
|
let constructor = D::build(scope)
|
|
.get_function(scope)
|
|
.expect("build constructor");
|
|
global
|
|
.define_own_property(
|
|
scope,
|
|
v8::String::new(scope, D::NAME).unwrap().into(),
|
|
constructor.into(),
|
|
v8::PropertyAttribute::DONT_ENUM,
|
|
)
|
|
.expect("publish constructor");
|
|
constructor
|
|
}
|
|
|
|
fn run_script<'s>(scope: &mut v8::PinScope<'s, '_>, source: &str) -> v8::Local<'s, v8::Value> {
|
|
let source = v8::String::new(scope, source).expect("test source should allocate");
|
|
v8::Script::compile(scope, source, None)
|
|
.and_then(|script| script.run(scope))
|
|
.expect("test script should run")
|
|
}
|
|
|
|
fn sample_now_callback(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
_args: v8::FunctionCallbackArguments<'_>,
|
|
mut rv: v8::ReturnValue<'_, v8::Value>,
|
|
) {
|
|
rv.set(v8::Number::new(scope, 7.0).into());
|
|
}
|
|
|
|
fn sample_data_callback(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
args: v8::FunctionCallbackArguments<'_>,
|
|
mut rv: v8::ReturnValue<'_, v8::Value>,
|
|
) {
|
|
rv.set(args.data().to_string(scope).unwrap().into());
|
|
}
|
|
|
|
fn sample_object_data_callback(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
args: v8::FunctionCallbackArguments<'_>,
|
|
mut rv: v8::ReturnValue<'_, v8::Value>,
|
|
) {
|
|
rv.set(v8::Boolean::new(scope, args.data().strict_equals(args.this().into())).into());
|
|
}
|
|
|
|
fn sample_accessor_getter(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
args: v8::FunctionCallbackArguments<'_>,
|
|
mut rv: v8::ReturnValue<'_, v8::Value>,
|
|
) {
|
|
rv.set(args.data().to_string(scope).unwrap().into());
|
|
}
|
|
|
|
fn sample_accessor_setter(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
args: v8::FunctionCallbackArguments<'_>,
|
|
_rv: v8::ReturnValue<'_, v8::Value>,
|
|
) {
|
|
let payload_key = v8::String::new(scope, "setterPayload").unwrap();
|
|
let written_key = v8::String::new(scope, "writtenValue").unwrap();
|
|
let payload = args.data().to_string(scope).unwrap();
|
|
let _ = args.this().set(scope, payload_key.into(), payload.into());
|
|
let _ = args.this().set(scope, written_key.into(), args.get(0));
|
|
}
|
|
|
|
fn sample_receiver_getter(
|
|
_scope: &mut v8::PinScope<'_, '_>,
|
|
args: v8::FunctionCallbackArguments<'_>,
|
|
mut rv: v8::ReturnValue<'_, v8::Value>,
|
|
) {
|
|
rv.set(args.this().into());
|
|
}
|
|
|
|
fn sample_native_holder_getter(
|
|
_scope: &mut v8::PinScope<'_, '_>,
|
|
_key: v8::Local<'_, v8::Name>,
|
|
args: v8::PropertyCallbackArguments<'_>,
|
|
mut rv: v8::ReturnValue<'_, v8::Value>,
|
|
) {
|
|
rv.set(args.holder().into());
|
|
}
|
|
|
|
fn sample_getter_value_callback(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
_args: v8::FunctionCallbackArguments<'_>,
|
|
mut rv: v8::ReturnValue<'_, v8::Value>,
|
|
) {
|
|
rv.set(
|
|
v8::String::new(scope, "declared-getter-value")
|
|
.unwrap()
|
|
.into(),
|
|
);
|
|
}
|
|
|
|
fn sample_constructor_callback(
|
|
_scope: &mut v8::PinScope<'_, '_>,
|
|
_args: v8::FunctionCallbackArguments<'_>,
|
|
_rv: v8::ReturnValue<'_, v8::Value>,
|
|
) {
|
|
}
|
|
|
|
fn indexed_zero_getter(
|
|
_scope: &mut v8::PinScope<'_, '_>,
|
|
index: u32,
|
|
_args: v8::PropertyCallbackArguments<'_>,
|
|
mut rv: v8::ReturnValue<'_, v8::Value>,
|
|
) -> v8::Intercepted {
|
|
if index != 0 {
|
|
return v8::Intercepted::kNo;
|
|
}
|
|
rv.set_uint32(42);
|
|
v8::Intercepted::kYes
|
|
}
|
|
|
|
fn indexed_zero_query(
|
|
_scope: &mut v8::PinScope<'_, '_>,
|
|
index: u32,
|
|
_args: v8::PropertyCallbackArguments<'_>,
|
|
mut rv: v8::ReturnValue<'_, v8::Integer>,
|
|
) -> v8::Intercepted {
|
|
if index != 0 {
|
|
return v8::Intercepted::kNo;
|
|
}
|
|
rv.set_int32(v8::PropertyAttribute::NONE.as_u32() as i32);
|
|
v8::Intercepted::kYes
|
|
}
|
|
|
|
const SAMPLE_TIME_ORIGIN_SLOT: &str = "__lmSampleTimeOrigin";
|
|
const SAMPLE_ENTRIES_SLOT: &str = "__lmSampleEntries";
|
|
const SAMPLE_PRIVATE_COUNT_SLOT: &str = "__lmSamplePrivateCount";
|
|
const SAMPLE_PRIVATE_LABEL_SLOT: &str = "__lmSamplePrivateLabel";
|
|
const SAMPLE_OPTIONAL_SLOT: &str = "__lmSampleOptional";
|
|
const SAMPLE_MISSING_OPTIONAL_SLOT: &str = "__lmSampleMissingOptional";
|
|
const SAMPLE_HIDDEN_SLOT: &str = "__lmSampleHidden";
|
|
const SAMPLE_LOCKED_HIDDEN_SLOT: &str = "__lmSampleLockedHidden";
|
|
const SAMPLE_LOCAL_OBJECT_SLOT: &str = "__lmSampleLocalObject";
|
|
|
|
#[derive(WebApiFunctionTemplate)]
|
|
#[webapi(
|
|
name = "SamplePerformance",
|
|
constructor = "illegal",
|
|
readonly_prototype,
|
|
prototype_to_string_tag = "SamplePerformance"
|
|
)]
|
|
struct SamplePerformanceInterface {
|
|
#[webapi(method, length = 0, callback = sample_now_callback)]
|
|
now: (),
|
|
#[webapi(method, length = 0, callback = sample_now_callback)]
|
|
sample_now: (),
|
|
|
|
#[webapi(accessor_property, getter = sample_now_callback)]
|
|
current_time: (),
|
|
}
|
|
|
|
#[derive(WebApiFunctionTemplate)]
|
|
#[webapi(
|
|
interface = interfaces::ConstructibleSample,
|
|
constructor_callback = sample_constructor_callback,
|
|
constructor_length = 2
|
|
)]
|
|
struct ConstructibleSampleInterface {}
|
|
|
|
#[derive(WebApiFunctionTemplate)]
|
|
#[webapi(name = "InterfaceDescriptorSample", constructor = "illegal")]
|
|
struct InterfaceDescriptorSample {
|
|
#[webapi(method, enumerable, readonly, dont_delete, callback = sample_now_callback)]
|
|
locked_visible: (),
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(interface = interfaces::SamplePerformance)]
|
|
struct SamplePerformanceObject {
|
|
#[webapi(data_property = SAMPLE_TIME_ORIGIN_SLOT)]
|
|
time_origin: f64,
|
|
|
|
#[webapi(data_property = "timeOrigin")]
|
|
public_time_origin: f64,
|
|
|
|
#[webapi(data_property)]
|
|
enabled: bool,
|
|
|
|
#[webapi(data_property)]
|
|
label: &'static str,
|
|
|
|
#[webapi(data_property = "visibleLabel", enumerable)]
|
|
visible_label: &'static str,
|
|
|
|
#[webapi(data_property = SAMPLE_ENTRIES_SLOT)]
|
|
entries: Vec<u32>,
|
|
|
|
#[webapi(data_property)]
|
|
sized_entries: [u32; 2],
|
|
|
|
#[webapi(data_property = "nullable", init = "null")]
|
|
nullable: (),
|
|
|
|
#[webapi(data_property = "emptyObject", init = "object")]
|
|
empty_object: (),
|
|
|
|
#[webapi(data_property = "nullObject", init = "null_object")]
|
|
null_object: (),
|
|
|
|
#[webapi(data_property = "emptyArray", init = "array")]
|
|
empty_array: (),
|
|
|
|
#[webapi(data_property = "undefinedValue", init = "undefined")]
|
|
undefined_value: (),
|
|
|
|
#[webapi(data_property = "defaultTrue", init = true)]
|
|
default_true: (),
|
|
|
|
#[webapi(data_property = "defaultFalse", init = false)]
|
|
default_false: (),
|
|
|
|
#[webapi(data_property = "defaultZero", init = 0)]
|
|
default_zero: (),
|
|
|
|
#[webapi(data_property = "defaultEmptyString", init = "")]
|
|
default_empty_string: (),
|
|
|
|
#[webapi(data_property = "defaultLiteralString", init = string("ready"))]
|
|
default_literal_string: (),
|
|
|
|
#[webapi(hidden = SAMPLE_HIDDEN_SLOT)]
|
|
hidden_value: &'static str,
|
|
|
|
#[webapi(hidden = SAMPLE_LOCKED_HIDDEN_SLOT)]
|
|
#[webapi(readonly, dont_delete)]
|
|
locked_hidden_value: &'static str,
|
|
|
|
#[webapi(hidden)]
|
|
hidden_by_field: &'static str,
|
|
|
|
#[webapi(slot = SAMPLE_PRIVATE_COUNT_SLOT)]
|
|
private_count: u32,
|
|
|
|
#[webapi(slot = SAMPLE_PRIVATE_LABEL_SLOT)]
|
|
private_label: &'static str,
|
|
|
|
#[webapi(slot)]
|
|
private_by_field: u32,
|
|
|
|
#[webapi(slot = SAMPLE_OPTIONAL_SLOT)]
|
|
optional_slot: Option<u32>,
|
|
|
|
#[webapi(slot = SAMPLE_MISSING_OPTIONAL_SLOT)]
|
|
missing_optional_slot: Option<u32>,
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(interface = interfaces::SamplePerformance)]
|
|
struct SampleLocalObjectSlot<'scope> {
|
|
#[webapi(slot = SAMPLE_LOCAL_OBJECT_SLOT)]
|
|
local_object: v8::Local<'scope, v8::Object>,
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain)]
|
|
struct GeneratedConstructorObject<'scope> {
|
|
#[webapi(data_property, init = true)]
|
|
brand: (),
|
|
|
|
#[webapi(data_property)]
|
|
value: v8::Local<'scope, v8::Value>,
|
|
|
|
#[webapi(data_property, init = "array")]
|
|
entries: (),
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain)]
|
|
struct NestedValueRecord {
|
|
#[webapi(data_property, enumerable)]
|
|
name: &'static str,
|
|
|
|
#[webapi(data_property, enumerable)]
|
|
count: u32,
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain)]
|
|
struct DefaultNameObject<'scope> {
|
|
#[webapi(data_property, enumerable)]
|
|
_visible_label: &'static str,
|
|
|
|
cached_getter: v8::Local<'scope, v8::Function>,
|
|
|
|
#[webapi(method, callback = sample_now_callback)]
|
|
sample_method: (),
|
|
|
|
#[webapi(method, callback = sample_now_callback, readonly, dont_delete)]
|
|
locked_method: (),
|
|
|
|
#[webapi(method, callback = sample_now_callback)]
|
|
optional_method: Option<()>,
|
|
|
|
#[webapi(method, callback = sample_now_callback)]
|
|
missing_optional_method: Option<()>,
|
|
|
|
#[webapi(method = "explicitMethod", callback = sample_now_callback)]
|
|
explicit_method: (),
|
|
|
|
#[webapi(
|
|
method,
|
|
callback = sample_data_callback,
|
|
data = "declared-data"
|
|
)]
|
|
data_method: (),
|
|
|
|
#[webapi(method, callback = sample_object_data_callback, data = object)]
|
|
object_data_method: (),
|
|
|
|
#[webapi(method, symbol = "iterator", callback = sample_now_callback)]
|
|
symbol_iterator: (),
|
|
|
|
#[webapi(method, symbol = "asyncIterator", callback = sample_now_callback)]
|
|
symbol_async_iterator: (),
|
|
|
|
#[webapi(
|
|
accessor_property,
|
|
getter = sample_accessor_getter,
|
|
data = "declared-accessor"
|
|
)]
|
|
data_value: (),
|
|
|
|
#[webapi(
|
|
accessor_property = "mutableValue",
|
|
getter = sample_accessor_getter,
|
|
setter = sample_accessor_setter,
|
|
data = "declared-setter",
|
|
setter_data = "declared-setter-write",
|
|
enumerable,
|
|
dont_delete
|
|
)]
|
|
mutable_value: (),
|
|
|
|
#[webapi(accessor_property, getter_value = self.cached_getter, enumerable, dont_delete)]
|
|
cached_value: (),
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain)]
|
|
struct AliasObject {
|
|
#[webapi(method, enumerable, length = 0, callback = sample_now_callback)]
|
|
entries: (),
|
|
|
|
#[webapi(alias = "entries", symbol = "iterator", enumerable)]
|
|
iterator: (),
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain)]
|
|
struct ConstantObject {
|
|
#[webapi(constant = "READY", value = 4u32)]
|
|
ready: (),
|
|
}
|
|
|
|
#[derive(WebApiFunctionTemplate)]
|
|
#[webapi(
|
|
interface = interfaces::SampleTemplate,
|
|
constructor_callback = sample_constructor_callback,
|
|
constructor_length = 1,
|
|
enumerable
|
|
)]
|
|
struct SampleFunctionTemplateDeclaration {
|
|
#[webapi(
|
|
static_method = "from",
|
|
length = 1,
|
|
callback = sample_data_callback,
|
|
data = "template-static-data"
|
|
)]
|
|
from: (),
|
|
|
|
#[webapi(static_method, callback = sample_now_callback, readonly, dont_delete)]
|
|
locked_static: (),
|
|
|
|
#[webapi(constant = "READY", value = 4u32)]
|
|
ready: (),
|
|
|
|
#[webapi(
|
|
method,
|
|
length = 2,
|
|
callback = sample_data_callback,
|
|
data = "template-method-data"
|
|
)]
|
|
append: (),
|
|
|
|
#[webapi(method, callback = sample_now_callback, readonly, dont_delete)]
|
|
locked_method: (),
|
|
|
|
#[webapi(method, length = 0, callback = sample_now_callback)]
|
|
entries: (),
|
|
|
|
#[webapi(alias = "entries", symbol = "iterator")]
|
|
iterator: (),
|
|
|
|
#[webapi(accessor_property, getter = sample_receiver_getter)]
|
|
receiver: (),
|
|
|
|
#[webapi(
|
|
accessor_property,
|
|
getter = sample_accessor_getter,
|
|
setter = sample_accessor_setter,
|
|
data = "template-getter-data",
|
|
setter_data = "template-setter-data"
|
|
)]
|
|
data_value: (),
|
|
|
|
#[webapi(native_data_property, getter = sample_native_holder_getter)]
|
|
native_holder: (),
|
|
}
|
|
|
|
#[derive(WebApiFunctionTemplate)]
|
|
#[webapi(name = "IntrinsicArrayTemplate", enumerable)]
|
|
struct IntrinsicArrayTemplateDeclaration {
|
|
#[webapi(intrinsic_data_property = v8::Intrinsic::ArrayProtoEntries)]
|
|
entries: (),
|
|
|
|
#[webapi(intrinsic_data_property = v8::Intrinsic::ArrayProtoValues)]
|
|
values: (),
|
|
|
|
#[webapi(
|
|
intrinsic_data_property = v8::Intrinsic::ArrayProtoValues,
|
|
symbol = "iterator"
|
|
)]
|
|
iterator: (),
|
|
}
|
|
|
|
#[derive(WebApiFunctionTemplate)]
|
|
#[webapi(
|
|
name = "IntrinsicErrorParentTemplate",
|
|
intrinsic_prototype_parent = v8::Intrinsic::ErrorPrototype
|
|
)]
|
|
struct IntrinsicErrorParentTemplateDeclaration {
|
|
#[webapi(method, callback = sample_now_callback)]
|
|
marker: (),
|
|
}
|
|
|
|
#[derive(WebApiFunctionTemplate)]
|
|
#[webapi(
|
|
name = "Example Iterator",
|
|
intrinsic_prototype_parent = v8::Intrinsic::IteratorPrototype,
|
|
prototype_to_string_tag = "Example Iterator",
|
|
readonly_prototype,
|
|
enumerable
|
|
)]
|
|
struct IntrinsicIteratorTemplateDeclaration {
|
|
#[webapi(method, callback = sample_now_callback)]
|
|
next: (),
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain)]
|
|
struct ExplicitFunctionNameSymbolMethodObject {
|
|
#[webapi(
|
|
method,
|
|
symbol = "iterator",
|
|
function_name = "values",
|
|
callback = sample_now_callback
|
|
)]
|
|
iterator: (),
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain)]
|
|
struct NativeDataPropertyObject {
|
|
#[webapi(native_data_property, getter = sample_native_holder_getter)]
|
|
native_holder: (),
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain, enumerable)]
|
|
struct DefaultEnumerableMembersObject {
|
|
#[webapi(data_property)]
|
|
property_value: &'static str,
|
|
|
|
#[webapi(method, callback = sample_now_callback)]
|
|
string_method: (),
|
|
|
|
#[webapi(
|
|
accessor_property,
|
|
getter = sample_accessor_getter,
|
|
data = "default-enumerable-accessor"
|
|
)]
|
|
accessor_value: (),
|
|
|
|
#[webapi(method, symbol = "iterator", callback = sample_now_callback)]
|
|
symbol_iterator: (),
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain, data_properties, enumerable)]
|
|
struct DefaultEnumerablePropertiesObject {
|
|
client_x: f64,
|
|
is_2d: bool,
|
|
related_target: Option<&'static str>,
|
|
missing_target: Option<&'static str>,
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(interface = interfaces::SamplePerformance, data_properties, enumerable)]
|
|
struct PrototypeBackedEnumerablePropertiesObject {
|
|
client_x: f64,
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain, rename_all = "none", data_properties, enumerable)]
|
|
struct ExplicitSnakeCasePropertiesObject {
|
|
snake_case_name: &'static str,
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(
|
|
interface = interfaces::SamplePerformance,
|
|
own_to_string_tag = "ReadonlySample",
|
|
readonly_to_string_tag,
|
|
)]
|
|
struct ReadonlyTagObject {}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(interface = interfaces::MissingSampleInterface, require_prototype)]
|
|
struct RequiredPrototypeObject {}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain, prototype = "SamplePerformance", require_prototype)]
|
|
struct PrototypeOnlyObject {}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain, prototype = "MissingSampleInterface", require_prototype)]
|
|
struct MissingPrototypeOnlyObject {}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain, scope_lifetime = 'scope)]
|
|
struct RuntimePrototypeTaggedObject<'scope, 'tag> {
|
|
#[webapi(prototype)]
|
|
prototype: v8::Local<'scope, v8::Object>,
|
|
|
|
#[webapi(to_string_tag)]
|
|
to_string_tag: Option<&'tag str>,
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain)]
|
|
struct RuntimeTagOnlyObject<'tag> {
|
|
#[webapi(to_string_tag)]
|
|
to_string_tag: Option<&'tag str>,
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain)]
|
|
struct RuntimeReadonlyLockedTagObject {
|
|
#[webapi(to_string_tag, readonly, dont_delete, init = string("LockedRuntimeTag"))]
|
|
to_string_tag: (),
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(
|
|
interface = interfaces::SamplePerformance,
|
|
fallback_to_string_tag = "SampleFallback",
|
|
)]
|
|
struct PrototypeBackedFallbackTagObject {}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(
|
|
interface = interfaces::MissingSampleInterface,
|
|
fallback_to_string_tag = "MissingSample",
|
|
)]
|
|
struct MissingPrototypeFallbackTagObject {}
|
|
|
|
#[test]
|
|
fn declared_template_and_object_have_expected_surface() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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 global = context.global(scope);
|
|
|
|
install_template::<SamplePerformanceInterface>(scope, global);
|
|
let declaration = SamplePerformanceObject {
|
|
time_origin: 12.0,
|
|
public_time_origin: 12.0,
|
|
enabled: true,
|
|
label: "declared",
|
|
visible_label: "visible",
|
|
entries: Vec::new(),
|
|
sized_entries: [5, 7],
|
|
nullable: (),
|
|
empty_object: (),
|
|
null_object: (),
|
|
empty_array: (),
|
|
undefined_value: (),
|
|
default_true: (),
|
|
default_false: (),
|
|
default_zero: (),
|
|
default_empty_string: (),
|
|
default_literal_string: (),
|
|
hidden_value: "hidden-data",
|
|
locked_hidden_value: "locked-hidden-data",
|
|
hidden_by_field: "field-hidden",
|
|
private_count: 3,
|
|
private_label: "hidden",
|
|
private_by_field: 11,
|
|
optional_slot: Some(9),
|
|
missing_optional_slot: None,
|
|
};
|
|
let object = declaration.bind(scope).expect("bind object");
|
|
let existing = v8::Object::new(scope);
|
|
declaration
|
|
.bind_into(scope, existing)
|
|
.expect("bind existing object");
|
|
let local_slot_value = v8::Object::new(scope);
|
|
let local_object = SampleLocalObjectSlot {
|
|
local_object: local_slot_value,
|
|
}
|
|
.bind(scope)
|
|
.expect("bind object with local slot");
|
|
let generated_constructor_value = v8::Boolean::new(scope, true).into();
|
|
let generated_constructor_object = GeneratedConstructorObject::new(generated_constructor_value)
|
|
.bind(scope)
|
|
.expect("bind generated-constructor object");
|
|
let getter_value = v8::Function::builder(sample_getter_value_callback)
|
|
.build(scope)
|
|
.expect("build cached getter");
|
|
let default_name_object = DefaultNameObject {
|
|
_visible_label: "renamed",
|
|
cached_getter: getter_value,
|
|
sample_method: (),
|
|
locked_method: (),
|
|
optional_method: Some(()),
|
|
missing_optional_method: None,
|
|
explicit_method: (),
|
|
data_method: (),
|
|
object_data_method: (),
|
|
symbol_iterator: (),
|
|
symbol_async_iterator: (),
|
|
data_value: (),
|
|
mutable_value: (),
|
|
cached_value: (),
|
|
}
|
|
.bind(scope)
|
|
.expect("bind object with default names");
|
|
let alias_object = AliasObject {
|
|
entries: (),
|
|
iterator: (),
|
|
}
|
|
.bind(scope)
|
|
.expect("bind alias object");
|
|
let constant_object = ConstantObject { ready: () }
|
|
.bind(scope)
|
|
.expect("bind constant object");
|
|
let default_enumerable_members_object = DefaultEnumerableMembersObject {
|
|
property_value: "default-enumerable",
|
|
string_method: (),
|
|
accessor_value: (),
|
|
symbol_iterator: (),
|
|
}
|
|
.bind(scope)
|
|
.expect("bind object with default enumerable members");
|
|
let default_properties_object = DefaultEnumerablePropertiesObject {
|
|
client_x: 42.0,
|
|
is_2d: true,
|
|
related_target: Some("target"),
|
|
missing_target: None,
|
|
}
|
|
.bind(scope)
|
|
.expect("bind object with default enumerable properties");
|
|
let explicit_snake_case_object = ExplicitSnakeCasePropertiesObject {
|
|
snake_case_name: "snake",
|
|
}
|
|
.bind(scope)
|
|
.expect("bind explicit snake-case properties object");
|
|
|
|
let constructor = global
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "SamplePerformance").unwrap().into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("constructor");
|
|
let prototype = constructor
|
|
.get(scope, v8::String::new(scope, "prototype").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("prototype");
|
|
let now = prototype
|
|
.get(scope, v8::String::new(scope, "now").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("now function");
|
|
assert_eq!(
|
|
now.has_own_property(scope, v8::String::new(scope, "prototype").unwrap().into()),
|
|
Some(false),
|
|
"Web IDL operations must be concise non-constructor functions"
|
|
);
|
|
let value = now.call(scope, object.into(), &[]).expect("call now");
|
|
assert_eq!(value.number_value(scope), Some(7.0));
|
|
let sample_now = prototype
|
|
.get(scope, v8::String::new(scope, "sampleNow").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("sampleNow function");
|
|
let value = sample_now
|
|
.call(scope, object.into(), &[])
|
|
.expect("call sampleNow");
|
|
assert_eq!(value.number_value(scope), Some(7.0));
|
|
assert_eq!(
|
|
object
|
|
.get(scope, v8::String::new(scope, "currentTime").unwrap().into())
|
|
.and_then(|value| value.number_value(scope)),
|
|
Some(7.0)
|
|
);
|
|
let current_time_descriptor = prototype
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "currentTime").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("currentTime descriptor");
|
|
let current_time_getter = current_time_descriptor
|
|
.get(scope, v8::String::new(scope, "get").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("currentTime getter");
|
|
assert_eq!(
|
|
current_time_getter
|
|
.has_own_property(scope, v8::String::new(scope, "prototype").unwrap().into(),),
|
|
Some(false),
|
|
"Web IDL accessor functions must not expose a prototype"
|
|
);
|
|
assert_eq!(
|
|
current_time_getter
|
|
.get_name(scope)
|
|
.to_string(scope)
|
|
.map(|value| value.to_rust_string_lossy(scope)),
|
|
Some("get currentTime".to_owned())
|
|
);
|
|
|
|
let instance_constructor = object
|
|
.get(scope, v8::String::new(scope, "constructor").unwrap().into())
|
|
.expect("instance constructor");
|
|
assert!(instance_constructor.strict_equals(constructor.into()));
|
|
let constructor_descriptor = prototype
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "constructor").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("prototype constructor descriptor");
|
|
assert_eq!(
|
|
constructor_descriptor
|
|
.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
.map(|value| value.strict_equals(constructor.into())),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
constructor_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
constructor_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
constructor_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
|
|
let prototype_descriptor = constructor
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "prototype").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("prototype descriptor");
|
|
assert_eq!(
|
|
prototype_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
prototype_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
prototype_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
|
|
let time_origin = object
|
|
.get(scope, v8::String::new(scope, "timeOrigin").unwrap().into())
|
|
.expect("timeOrigin");
|
|
assert_eq!(time_origin.number_value(scope), Some(12.0));
|
|
let label = object
|
|
.get(scope, v8::String::new(scope, "label").unwrap().into())
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(label.as_deref(), Some("declared"));
|
|
let visible_label = object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "visibleLabel").unwrap().into(),
|
|
)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(visible_label.as_deref(), Some("visible"));
|
|
assert_eq!(
|
|
object
|
|
.get_own_property_descriptor(
|
|
scope,
|
|
v8::String::new(scope, "visibleLabel").unwrap().into()
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.and_then(|descriptor| descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into()))
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
object
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "label").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.and_then(|descriptor| descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into()))
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
let direct_hidden = v8::Object::new(scope);
|
|
moli_webapi_declare::define_declared_hidden_property_with_descriptor(
|
|
scope,
|
|
direct_hidden,
|
|
SAMPLE_LOCKED_HIDDEN_SLOT,
|
|
&"direct-hidden",
|
|
false,
|
|
false,
|
|
)
|
|
.expect("direct locked hidden property should define");
|
|
let direct_hidden_descriptor = direct_hidden
|
|
.get_own_property_descriptor(
|
|
scope,
|
|
v8::String::new(scope, SAMPLE_LOCKED_HIDDEN_SLOT)
|
|
.unwrap()
|
|
.into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("direct locked hidden descriptor");
|
|
assert_eq!(
|
|
direct_hidden_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
let locked_hidden_descriptor = object
|
|
.get_own_property_descriptor(
|
|
scope,
|
|
v8::String::new(scope, SAMPLE_LOCKED_HIDDEN_SLOT)
|
|
.unwrap()
|
|
.into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("locked hidden descriptor");
|
|
assert_eq!(
|
|
locked_hidden_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
locked_hidden_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
locked_hidden_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
object
|
|
.get(scope, v8::String::new(scope, "enabled").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
default_properties_object
|
|
.get(scope, v8::String::new(scope, "clientX").unwrap().into())
|
|
.and_then(|value| value.number_value(scope)),
|
|
Some(42.0)
|
|
);
|
|
let data_method = default_name_object
|
|
.get(scope, v8::String::new(scope, "dataMethod").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("dataMethod function");
|
|
assert_eq!(
|
|
data_method.has_own_property(scope, v8::String::new(scope, "prototype").unwrap().into(),),
|
|
Some(false),
|
|
"operations with callback data must remain non-constructible"
|
|
);
|
|
let data_value = data_method
|
|
.call(scope, default_name_object.into(), &[])
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(data_value.as_deref(), Some("declared-data"));
|
|
let optional_method = default_name_object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "optionalMethod").unwrap().into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("optionalMethod function");
|
|
let optional_method_value = optional_method
|
|
.call(scope, default_name_object.into(), &[])
|
|
.and_then(|value| value.number_value(scope));
|
|
assert_eq!(optional_method_value, Some(7.0));
|
|
assert!(
|
|
!default_name_object
|
|
.has_own_property(
|
|
scope,
|
|
v8::String::new(scope, "missingOptionalMethod")
|
|
.unwrap()
|
|
.into()
|
|
)
|
|
.unwrap_or(false)
|
|
);
|
|
let object_data_method = default_name_object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "objectDataMethod").unwrap().into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("objectDataMethod function");
|
|
let object_data_matches_receiver = object_data_method
|
|
.call(scope, default_name_object.into(), &[])
|
|
.map(|value| value.boolean_value(scope));
|
|
assert_eq!(object_data_matches_receiver, Some(true));
|
|
let accessor_value = default_name_object
|
|
.get(scope, v8::String::new(scope, "dataValue").unwrap().into())
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(accessor_value.as_deref(), Some("declared-accessor"));
|
|
let cached_value = default_name_object
|
|
.get(scope, v8::String::new(scope, "cachedValue").unwrap().into())
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(cached_value.as_deref(), Some("declared-getter-value"));
|
|
let cached_descriptor = default_name_object
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "cachedValue").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("cachedValue descriptor");
|
|
assert_eq!(
|
|
cached_descriptor
|
|
.get(scope, v8::String::new(scope, "get").unwrap().into())
|
|
.map(|value| value.strict_equals(getter_value.into())),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
cached_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
cached_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
let accessor_descriptor = default_name_object
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "dataValue").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("dataValue descriptor");
|
|
assert_eq!(
|
|
accessor_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
accessor_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
accessor_descriptor
|
|
.get(scope, v8::String::new(scope, "set").unwrap().into())
|
|
.map(|value| value.is_undefined()),
|
|
Some(true)
|
|
);
|
|
let mutable_key = v8::String::new(scope, "mutableValue").unwrap();
|
|
let written = v8::String::new(scope, "written").unwrap();
|
|
assert!(
|
|
default_name_object
|
|
.set(scope, mutable_key.into(), written.into())
|
|
.unwrap_or(false)
|
|
);
|
|
let mutable_descriptor = default_name_object
|
|
.get_own_property_descriptor(scope, mutable_key.into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("mutableValue descriptor");
|
|
assert_eq!(
|
|
mutable_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
mutable_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
let setter_payload = default_name_object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "setterPayload").unwrap().into(),
|
|
)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(setter_payload.as_deref(), Some("declared-setter-write"));
|
|
let written_value = default_name_object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "writtenValue").unwrap().into(),
|
|
)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(written_value.as_deref(), Some("written"));
|
|
assert_eq!(
|
|
default_properties_object
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "clientX").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.and_then(|descriptor| descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into()))
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
for key in ["propertyValue", "stringMethod", "accessorValue"] {
|
|
assert_eq!(
|
|
default_enumerable_members_object
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, key).unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.and_then(|descriptor| descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into()))
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true),
|
|
"{key} should inherit the struct enumerable default"
|
|
);
|
|
}
|
|
let default_enumerable_symbol_descriptor = default_enumerable_members_object
|
|
.get_own_property_descriptor(scope, v8::Symbol::get_iterator(scope).into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("default enumerable Symbol.iterator descriptor");
|
|
assert_eq!(
|
|
default_enumerable_symbol_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
default_properties_object
|
|
.get(scope, v8::String::new(scope, "is2D").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
let related_target = default_properties_object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "relatedTarget").unwrap().into(),
|
|
)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(related_target.as_deref(), Some("target"));
|
|
let snake_case_name = explicit_snake_case_object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "snake_case_name").unwrap().into(),
|
|
)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(snake_case_name.as_deref(), Some("snake"));
|
|
assert!(
|
|
!explicit_snake_case_object
|
|
.has_own_property(
|
|
scope,
|
|
v8::String::new(scope, "snakeCaseName").unwrap().into()
|
|
)
|
|
.unwrap_or(false)
|
|
);
|
|
assert!(
|
|
!default_properties_object
|
|
.has_own_property(
|
|
scope,
|
|
v8::String::new(scope, "missingTarget").unwrap().into()
|
|
)
|
|
.unwrap_or(false)
|
|
);
|
|
let entries = object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, SAMPLE_ENTRIES_SLOT).unwrap().into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
|
.expect("entries");
|
|
assert_eq!(entries.length(), 0);
|
|
let sized_entries = object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "sizedEntries").unwrap().into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
|
.expect("sized entries");
|
|
assert_eq!(sized_entries.length(), 2);
|
|
assert_eq!(
|
|
sized_entries
|
|
.get_index(scope, 0)
|
|
.and_then(|value| value.number_value(scope)),
|
|
Some(5.0)
|
|
);
|
|
assert_eq!(
|
|
sized_entries
|
|
.get_index(scope, 1)
|
|
.and_then(|value| value.number_value(scope)),
|
|
Some(7.0)
|
|
);
|
|
assert_eq!(
|
|
object
|
|
.get(scope, v8::String::new(scope, "nullable").unwrap().into())
|
|
.map(|value| value.is_null()),
|
|
Some(true)
|
|
);
|
|
let empty_object = object
|
|
.get(scope, v8::String::new(scope, "emptyObject").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("empty object");
|
|
assert_eq!(
|
|
empty_object
|
|
.get_prototype(scope)
|
|
.map(|value| value.is_null()),
|
|
Some(false)
|
|
);
|
|
let null_object = object
|
|
.get(scope, v8::String::new(scope, "nullObject").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("null object");
|
|
assert_eq!(
|
|
null_object
|
|
.get_prototype(scope)
|
|
.map(|value| value.is_null()),
|
|
Some(true)
|
|
);
|
|
let empty_array = object
|
|
.get(scope, v8::String::new(scope, "emptyArray").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
|
.expect("empty array");
|
|
assert_eq!(empty_array.length(), 0);
|
|
assert_eq!(
|
|
object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "undefinedValue").unwrap().into()
|
|
)
|
|
.map(|value| value.is_undefined()),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
object
|
|
.get(scope, v8::String::new(scope, "defaultTrue").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "defaultFalse").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
object
|
|
.get(scope, v8::String::new(scope, "defaultZero").unwrap().into())
|
|
.and_then(|value| value.number_value(scope)),
|
|
Some(0.0)
|
|
);
|
|
let default_empty_string = object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "defaultEmptyString").unwrap().into(),
|
|
)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(default_empty_string.as_deref(), Some(""));
|
|
let default_literal_string = object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "defaultLiteralString")
|
|
.unwrap()
|
|
.into(),
|
|
)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(default_literal_string.as_deref(), Some("ready"));
|
|
let hidden_time_origin = object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, SAMPLE_TIME_ORIGIN_SLOT)
|
|
.unwrap()
|
|
.into(),
|
|
)
|
|
.expect("hidden timeOrigin slot");
|
|
assert_eq!(hidden_time_origin.number_value(scope), Some(12.0));
|
|
let hidden_value = object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, SAMPLE_HIDDEN_SLOT).unwrap().into(),
|
|
)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(hidden_value.as_deref(), Some("hidden-data"));
|
|
assert_eq!(
|
|
object
|
|
.get_own_property_descriptor(
|
|
scope,
|
|
v8::String::new(scope, SAMPLE_HIDDEN_SLOT).unwrap().into()
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.and_then(|descriptor| descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into()))
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
let hidden_by_field = object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "hidden_by_field").unwrap().into(),
|
|
)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(hidden_by_field.as_deref(), Some("field-hidden"));
|
|
let private_count = get_private_value(scope, object, SAMPLE_PRIVATE_COUNT_SLOT)
|
|
.expect("private count")
|
|
.number_value(scope);
|
|
assert_eq!(private_count, Some(3.0));
|
|
let private_label = get_private_value(scope, object, SAMPLE_PRIVATE_LABEL_SLOT)
|
|
.expect("private label")
|
|
.to_string(scope)
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(private_label.as_deref(), Some("hidden"));
|
|
let private_by_field = get_private_value(scope, object, "private_by_field")
|
|
.expect("private field")
|
|
.number_value(scope);
|
|
assert_eq!(private_by_field, Some(11.0));
|
|
let optional_slot = get_private_value(scope, object, SAMPLE_OPTIONAL_SLOT)
|
|
.expect("optional slot")
|
|
.number_value(scope);
|
|
assert_eq!(optional_slot, Some(9.0));
|
|
assert!(get_private_value(scope, object, SAMPLE_MISSING_OPTIONAL_SLOT).is_none());
|
|
let local_object_slot = get_private_value(scope, local_object, SAMPLE_LOCAL_OBJECT_SLOT)
|
|
.expect("local object slot");
|
|
assert!(local_object_slot.strict_equals(local_slot_value.into()));
|
|
assert_eq!(
|
|
generated_constructor_object
|
|
.get(scope, v8::String::new(scope, "brand").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
generated_constructor_object
|
|
.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
.map(|value| value.is_true()),
|
|
Some(true)
|
|
);
|
|
assert!(
|
|
generated_constructor_object
|
|
.get(scope, v8::String::new(scope, "entries").unwrap().into())
|
|
.is_some_and(|value| value.is_array())
|
|
);
|
|
let default_visible_label = default_name_object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "visibleLabel").unwrap().into(),
|
|
)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(default_visible_label.as_deref(), Some("renamed"));
|
|
let sample_method = default_name_object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "sampleMethod").unwrap().into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("sampleMethod function");
|
|
let value = sample_method
|
|
.call(scope, default_name_object.into(), &[])
|
|
.expect("call sampleMethod");
|
|
assert_eq!(value.number_value(scope), Some(7.0));
|
|
let locked_method = default_name_object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "lockedMethod").unwrap().into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("lockedMethod function");
|
|
let value = locked_method
|
|
.call(scope, default_name_object.into(), &[])
|
|
.expect("call lockedMethod");
|
|
assert_eq!(value.number_value(scope), Some(7.0));
|
|
let locked_method_descriptor = default_name_object
|
|
.get_own_property_descriptor(
|
|
scope,
|
|
v8::String::new(scope, "lockedMethod").unwrap().into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("lockedMethod descriptor");
|
|
assert_eq!(
|
|
locked_method_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
locked_method_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
locked_method_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
let explicit_method = default_name_object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "explicitMethod").unwrap().into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("explicitMethod function");
|
|
let value = explicit_method
|
|
.call(scope, default_name_object.into(), &[])
|
|
.expect("call explicitMethod");
|
|
assert_eq!(value.number_value(scope), Some(7.0));
|
|
assert!(
|
|
!default_name_object
|
|
.has_own_property(
|
|
scope,
|
|
v8::String::new(scope, "symbolIterator").unwrap().into()
|
|
)
|
|
.unwrap_or(false)
|
|
);
|
|
assert!(
|
|
!default_name_object
|
|
.has_own_property(
|
|
scope,
|
|
v8::String::new(scope, "symbolAsyncIterator")
|
|
.unwrap()
|
|
.into()
|
|
)
|
|
.unwrap_or(false)
|
|
);
|
|
let iterator_key = v8::Symbol::get_iterator(scope);
|
|
let symbol_iterator = default_name_object
|
|
.get(scope, iterator_key.into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("Symbol.iterator method");
|
|
let value = symbol_iterator
|
|
.call(scope, default_name_object.into(), &[])
|
|
.expect("call Symbol.iterator method");
|
|
assert_eq!(value.number_value(scope), Some(7.0));
|
|
let symbol_iterator_descriptor = default_name_object
|
|
.get_own_property_descriptor(scope, iterator_key.into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("Symbol.iterator descriptor");
|
|
assert_eq!(
|
|
symbol_iterator_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
symbol_iterator_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
symbol_iterator_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
let async_iterator_key = v8::Symbol::get_async_iterator(scope);
|
|
let symbol_async_iterator = default_name_object
|
|
.get(scope, async_iterator_key.into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("Symbol.asyncIterator method");
|
|
let value = symbol_async_iterator
|
|
.call(scope, default_name_object.into(), &[])
|
|
.expect("call Symbol.asyncIterator method");
|
|
assert_eq!(value.number_value(scope), Some(7.0));
|
|
let symbol_async_iterator_descriptor = default_name_object
|
|
.get_own_property_descriptor(scope, async_iterator_key.into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("Symbol.asyncIterator descriptor");
|
|
assert_eq!(
|
|
symbol_async_iterator_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
symbol_async_iterator_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
symbol_async_iterator_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
let alias_entries = alias_object
|
|
.get(scope, v8::String::new(scope, "entries").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("entries alias source");
|
|
let alias_iterator_key = v8::Symbol::get_iterator(scope);
|
|
let alias_iterator = alias_object
|
|
.get(scope, alias_iterator_key.into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("Symbol.iterator alias");
|
|
assert!(alias_iterator.strict_equals(alias_entries.into()));
|
|
let alias_value = alias_iterator
|
|
.call(scope, alias_object.into(), &[])
|
|
.expect("call aliased Symbol.iterator");
|
|
assert_eq!(alias_value.number_value(scope), Some(7.0));
|
|
let alias_iterator_descriptor = alias_object
|
|
.get_own_property_descriptor(scope, alias_iterator_key.into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("Symbol.iterator alias descriptor");
|
|
assert_eq!(
|
|
alias_iterator_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
alias_iterator_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
alias_iterator_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
let constant_descriptor = constant_object
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "READY").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("READY object constant descriptor");
|
|
assert_eq!(
|
|
constant_descriptor
|
|
.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
.and_then(|value| value.uint32_value(scope)),
|
|
Some(4)
|
|
);
|
|
assert_eq!(
|
|
constant_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
constant_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
constant_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
let existing_time_origin = existing
|
|
.get(scope, v8::String::new(scope, "timeOrigin").unwrap().into())
|
|
.expect("existing timeOrigin");
|
|
assert_eq!(existing_time_origin.number_value(scope), Some(12.0));
|
|
let prototype_only_object = PrototypeOnlyObject {}
|
|
.bind(scope)
|
|
.expect("prototype-only object");
|
|
assert_eq!(
|
|
prototype_only_object
|
|
.get_prototype(scope)
|
|
.map(|value| value.strict_equals(prototype.into())),
|
|
Some(true)
|
|
);
|
|
let runtime_prototype = v8::Object::new(scope);
|
|
let runtime_tagged_object = RuntimePrototypeTaggedObject {
|
|
prototype: runtime_prototype,
|
|
to_string_tag: Some("RuntimeDeclared"),
|
|
}
|
|
.bind(scope)
|
|
.expect("runtime prototype tagged object");
|
|
assert_eq!(
|
|
runtime_tagged_object
|
|
.get_prototype(scope)
|
|
.map(|value| value.strict_equals(runtime_prototype.into())),
|
|
Some(true)
|
|
);
|
|
let runtime_tag_descriptor = runtime_tagged_object
|
|
.get_own_property_descriptor(scope, v8::Symbol::get_to_string_tag(scope).into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("runtime tag descriptor");
|
|
assert_eq!(
|
|
runtime_tag_descriptor
|
|
.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope))
|
|
.as_deref(),
|
|
Some("RuntimeDeclared")
|
|
);
|
|
assert_eq!(
|
|
runtime_tag_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
let runtime_untagged_object = RuntimePrototypeTaggedObject {
|
|
prototype: runtime_prototype,
|
|
to_string_tag: None,
|
|
}
|
|
.bind(scope)
|
|
.expect("runtime prototype untagged object");
|
|
assert_eq!(
|
|
runtime_untagged_object
|
|
.has_own_property(scope, v8::Symbol::get_to_string_tag(scope).into()),
|
|
Some(false)
|
|
);
|
|
let runtime_tag_only_object = RuntimeTagOnlyObject {
|
|
to_string_tag: Some("RuntimeTagOnly"),
|
|
}
|
|
.bind(scope)
|
|
.expect("runtime tag-only object");
|
|
let runtime_tag_only = runtime_tag_only_object
|
|
.get_own_property_descriptor(scope, v8::Symbol::get_to_string_tag(scope).into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.and_then(|descriptor| {
|
|
descriptor.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
})
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(runtime_tag_only.as_deref(), Some("RuntimeTagOnly"));
|
|
let locked_runtime_tag = RuntimeReadonlyLockedTagObject { to_string_tag: () }
|
|
.bind(scope)
|
|
.expect("bind readonly locked runtime tag");
|
|
let locked_runtime_tag_descriptor = locked_runtime_tag
|
|
.get_own_property_descriptor(scope, v8::Symbol::get_to_string_tag(scope).into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("locked runtime tag descriptor");
|
|
assert_eq!(
|
|
locked_runtime_tag_descriptor
|
|
.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope))
|
|
.as_deref(),
|
|
Some("LockedRuntimeTag")
|
|
);
|
|
assert_eq!(
|
|
locked_runtime_tag_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
locked_runtime_tag_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
locked_runtime_tag_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
|
|
let tag = object
|
|
.get_prototype(scope)
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.and_then(|prototype| prototype.get(scope, v8::Symbol::get_to_string_tag(scope).into()))
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(tag.as_deref(), Some("SamplePerformance"));
|
|
assert_eq!(
|
|
object.has_own_property(scope, v8::Symbol::get_to_string_tag(scope).into()),
|
|
Some(false)
|
|
);
|
|
|
|
let readonly_tag_object = ReadonlyTagObject {}
|
|
.bind(scope)
|
|
.expect("readonly tag object");
|
|
let readonly_tag_descriptor = readonly_tag_object
|
|
.get_own_property_descriptor(scope, v8::Symbol::get_to_string_tag(scope).into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("readonly tag descriptor");
|
|
assert_eq!(
|
|
readonly_tag_descriptor
|
|
.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope))
|
|
.as_deref(),
|
|
Some("ReadonlySample")
|
|
);
|
|
assert_eq!(
|
|
readonly_tag_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
readonly_tag_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
|
|
let prototype_backed_fallback = PrototypeBackedFallbackTagObject {}
|
|
.bind(scope)
|
|
.expect("prototype backed fallback tag object");
|
|
assert_eq!(
|
|
prototype_backed_fallback
|
|
.has_own_property(scope, v8::Symbol::get_to_string_tag(scope).into()),
|
|
Some(false)
|
|
);
|
|
let missing_prototype_fallback = MissingPrototypeFallbackTagObject {}
|
|
.bind(scope)
|
|
.expect("missing prototype fallback tag object");
|
|
let fallback_tag = missing_prototype_fallback
|
|
.get_own_property_descriptor(scope, v8::Symbol::get_to_string_tag(scope).into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.and_then(|descriptor| {
|
|
descriptor.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
})
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(fallback_tag.as_deref(), Some("MissingSample"));
|
|
}
|
|
|
|
#[test]
|
|
fn declared_constructor_callback_uses_configured_length() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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 global = context.global(scope);
|
|
|
|
let constructor = install_template::<ConstructibleSampleInterface>(scope, global);
|
|
let length = constructor
|
|
.get(scope, v8::String::new(scope, "length").unwrap().into())
|
|
.and_then(|value| value.number_value(scope));
|
|
assert_eq!(length, Some(2.0));
|
|
}
|
|
|
|
#[test]
|
|
fn declared_native_data_property_uses_native_holder() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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 object = NativeDataPropertyObject::new()
|
|
.bind(scope)
|
|
.expect("bind native data property object");
|
|
let value = object
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "nativeHolder").unwrap().into(),
|
|
)
|
|
.expect("read native data property");
|
|
|
|
assert!(
|
|
value.strict_equals(object.into()),
|
|
"native data property callback must receive the defining holder"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn declared_template_method_descriptor_attributes_are_applied() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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 global = context.global(scope);
|
|
|
|
let constructor = install_template::<InterfaceDescriptorSample>(scope, global);
|
|
let prototype = constructor
|
|
.get(scope, v8::String::new(scope, "prototype").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("descriptor sample prototype");
|
|
let descriptor = prototype
|
|
.get_own_property_descriptor(
|
|
scope,
|
|
v8::String::new(scope, "lockedVisible").unwrap().into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("lockedVisible descriptor");
|
|
|
|
assert_eq!(
|
|
descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
let method = descriptor
|
|
.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("lockedVisible method");
|
|
assert_eq!(
|
|
method
|
|
.get_name(scope)
|
|
.to_string(scope)
|
|
.map(|value| value.to_rust_string_lossy(scope)),
|
|
Some("lockedVisible".to_owned())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn indexed_property_handler_flags_are_forwarded_to_v8() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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 object_prototype = context
|
|
.global(scope)
|
|
.get(scope, v8::String::new(scope, "Object").unwrap().into())
|
|
.and_then(|value| value.to_object(scope))
|
|
.and_then(|object| {
|
|
object
|
|
.get(scope, v8::String::new(scope, "prototype").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
})
|
|
.expect("Object.prototype");
|
|
object_prototype
|
|
.define_own_property(
|
|
scope,
|
|
v8::String::new(scope, "0").unwrap().into(),
|
|
v8::Integer::new(scope, 5).into(),
|
|
v8::PropertyAttribute::NONE,
|
|
)
|
|
.expect("define Object.prototype[0]");
|
|
|
|
let masking_template = v8::ObjectTemplate::new(scope);
|
|
masking_template.set_indexed_property_handler(
|
|
v8::IndexedPropertyHandlerConfiguration::new()
|
|
.getter(indexed_zero_getter)
|
|
.query(indexed_zero_query),
|
|
);
|
|
let masking = masking_template
|
|
.new_instance(scope)
|
|
.expect("masking indexed interceptor instance");
|
|
assert_eq!(
|
|
masking
|
|
.get(scope, v8::Integer::new(scope, 0).into())
|
|
.and_then(|value| value.uint32_value(scope)),
|
|
Some(42)
|
|
);
|
|
|
|
let non_masking_template = v8::ObjectTemplate::new(scope);
|
|
non_masking_template.set_indexed_property_handler(
|
|
v8::IndexedPropertyHandlerConfiguration::new()
|
|
.getter(indexed_zero_getter)
|
|
.query(indexed_zero_query)
|
|
.flags(v8::PropertyHandlerFlags::NON_MASKING),
|
|
);
|
|
let non_masking = non_masking_template
|
|
.new_instance(scope)
|
|
.expect("non-masking indexed interceptor instance");
|
|
assert_eq!(
|
|
non_masking
|
|
.get(scope, v8::Integer::new(scope, 0).into())
|
|
.and_then(|value| value.uint32_value(scope)),
|
|
Some(5)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn webapi_value_converts_tuple_sequences_to_arrays() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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 pairs = vec![
|
|
("name".to_owned(), "value".to_owned()),
|
|
("second".to_owned(), "entry".to_owned()),
|
|
];
|
|
let array = pairs
|
|
.to_v8_value(scope)
|
|
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
|
.expect("pairs array");
|
|
|
|
assert_eq!(array.length(), 2);
|
|
let first = array
|
|
.get_index(scope, 0)
|
|
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
|
.expect("first pair");
|
|
assert_eq!(first.length(), 2);
|
|
assert_eq!(
|
|
first
|
|
.get_index(scope, 0)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope))
|
|
.as_deref(),
|
|
Some("name")
|
|
);
|
|
assert_eq!(
|
|
first
|
|
.get_index(scope, 1)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope))
|
|
.as_deref(),
|
|
Some("value")
|
|
);
|
|
|
|
let triple = ("event", 7_u32, true)
|
|
.to_v8_value(scope)
|
|
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
|
.expect("triple array");
|
|
assert_eq!(triple.length(), 3);
|
|
assert_eq!(
|
|
triple
|
|
.get_index(scope, 0)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope))
|
|
.as_deref(),
|
|
Some("event")
|
|
);
|
|
assert_eq!(
|
|
triple
|
|
.get_index(scope, 1)
|
|
.and_then(|value| value.uint32_value(scope)),
|
|
Some(7)
|
|
);
|
|
assert_eq!(
|
|
triple
|
|
.get_index(scope, 2)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
|
|
let quad = ("x", "y", 1_u32, 2_u32)
|
|
.to_v8_value(scope)
|
|
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
|
.expect("quad array");
|
|
assert_eq!(quad.length(), 4);
|
|
assert_eq!(
|
|
quad.get_index(scope, 3)
|
|
.and_then(|value| value.uint32_value(scope)),
|
|
Some(2)
|
|
);
|
|
|
|
run_script(
|
|
scope,
|
|
r#"
|
|
globalThis.__arraySetterHit = null;
|
|
Object.defineProperty(Array.prototype, "1", {
|
|
configurable: true,
|
|
set(value) { globalThis.__arraySetterHit = this; }
|
|
});
|
|
"#,
|
|
);
|
|
let guarded = vec![("guard".to_owned(), "value".to_owned())]
|
|
.to_v8_value(scope)
|
|
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
|
.expect("guarded pair array");
|
|
assert_eq!(guarded.length(), 1);
|
|
let hit = run_script(
|
|
scope,
|
|
r#"
|
|
const hit = globalThis.__arraySetterHit;
|
|
delete Array.prototype["1"];
|
|
hit;
|
|
"#,
|
|
);
|
|
assert!(hit.is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn define_array_data_property_does_not_trigger_inherited_indexed_setters() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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);
|
|
|
|
run_script(
|
|
scope,
|
|
r#"
|
|
globalThis.__arrayDataPropertySetterHit = null;
|
|
Object.defineProperty(Array.prototype, "0", {
|
|
configurable: true,
|
|
set(value) { globalThis.__arrayDataPropertySetterHit = value; }
|
|
});
|
|
"#,
|
|
);
|
|
|
|
let array = v8::Array::new(scope, 0);
|
|
let value = v8::String::new(scope, "stored").expect("test string should allocate");
|
|
define_array_data_property(scope, array, 0, value.into())
|
|
.expect("indexed data property should define");
|
|
|
|
assert_eq!(array.length(), 1);
|
|
assert_eq!(
|
|
array
|
|
.get_index(scope, 0)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope))
|
|
.as_deref(),
|
|
Some("stored")
|
|
);
|
|
let hit = run_script(
|
|
scope,
|
|
r#"
|
|
const hit = globalThis.__arrayDataPropertySetterHit;
|
|
delete Array.prototype["0"];
|
|
hit;
|
|
"#,
|
|
);
|
|
assert!(hit.is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn declared_enumerable_properties_do_not_trigger_inherited_setters() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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);
|
|
|
|
run_script(
|
|
scope,
|
|
r#"
|
|
globalThis.__declaredDataPropertySetterHit = null;
|
|
Object.defineProperty(Object.prototype, "clientX", {
|
|
configurable: true,
|
|
set(value) { globalThis.__declaredDataPropertySetterHit = value; }
|
|
});
|
|
"#,
|
|
);
|
|
|
|
let object = DefaultEnumerablePropertiesObject {
|
|
client_x: 42.0,
|
|
is_2d: true,
|
|
related_target: Some("target"),
|
|
missing_target: None,
|
|
}
|
|
.bind(scope)
|
|
.expect("default enumerable properties object should bind");
|
|
|
|
assert_eq!(
|
|
object
|
|
.get(scope, v8::String::new(scope, "clientX").unwrap().into())
|
|
.and_then(|value| value.number_value(scope)),
|
|
Some(42.0)
|
|
);
|
|
let descriptor = object
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "clientX").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("clientX descriptor");
|
|
assert_eq!(
|
|
descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
|
|
let hit = run_script(
|
|
scope,
|
|
r#"
|
|
const hit = globalThis.__declaredDataPropertySetterHit;
|
|
delete Object.prototype.clientX;
|
|
hit;
|
|
"#,
|
|
);
|
|
assert!(hit.is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn webapi_value_binds_declared_objects_inside_arrays() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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 records = vec![
|
|
NestedValueRecord {
|
|
name: "alpha",
|
|
count: 1,
|
|
},
|
|
NestedValueRecord {
|
|
name: "beta",
|
|
count: 2,
|
|
},
|
|
];
|
|
let array = records
|
|
.to_v8_value(scope)
|
|
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
|
.expect("records array");
|
|
|
|
assert_eq!(array.length(), 2);
|
|
let first = array
|
|
.get_index(scope, 0)
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("first record");
|
|
assert_eq!(
|
|
first
|
|
.get(scope, v8::String::new(scope, "name").unwrap().into())
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope))
|
|
.as_deref(),
|
|
Some("alpha")
|
|
);
|
|
assert_eq!(
|
|
first
|
|
.get(scope, v8::String::new(scope, "count").unwrap().into())
|
|
.and_then(|value| value.uint32_value(scope)),
|
|
Some(1)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn declared_object_can_require_interface_prototype() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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 error = RequiredPrototypeObject {}
|
|
.bind(scope)
|
|
.expect_err("missing required prototype should fail");
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"missing `MissingSampleInterface` prototype"
|
|
);
|
|
let error = MissingPrototypeOnlyObject {}
|
|
.bind(scope)
|
|
.expect_err("missing required explicit prototype should fail");
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"missing `MissingSampleInterface` prototype"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn declared_enumerable_properties_bypass_prototype_assignment_semantics() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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 global = context.global(scope);
|
|
|
|
install_template::<SamplePerformanceInterface>(scope, global);
|
|
let constructor = global
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "SamplePerformance").unwrap().into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("constructor");
|
|
let prototype = constructor
|
|
.get(scope, v8::String::new(scope, "prototype").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("prototype");
|
|
let client_x_name = v8::String::new(scope, "clientX").unwrap();
|
|
let prototype_value = v8::String::new(scope, "prototype-blocked").unwrap();
|
|
prototype
|
|
.define_own_property(
|
|
scope,
|
|
client_x_name.into(),
|
|
prototype_value.into(),
|
|
v8::PropertyAttribute::READ_ONLY,
|
|
)
|
|
.expect("readonly prototype property should define");
|
|
|
|
let object = PrototypeBackedEnumerablePropertiesObject { client_x: 42.0 }
|
|
.bind(scope)
|
|
.expect("bind prototype-backed enumerable object");
|
|
|
|
assert_eq!(
|
|
object
|
|
.get(scope, v8::String::new(scope, "clientX").unwrap().into())
|
|
.and_then(|value| value.number_value(scope)),
|
|
Some(42.0)
|
|
);
|
|
assert_eq!(
|
|
object.has_own_property(scope, v8::String::new(scope, "clientX").unwrap().into()),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
object
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "clientX").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.and_then(|descriptor| {
|
|
descriptor.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
})
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn symbol_method_can_declare_explicit_function_name() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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 object = ExplicitFunctionNameSymbolMethodObject { iterator: () }
|
|
.bind(scope)
|
|
.expect("bind explicit function name method object");
|
|
let iterator_key = v8::Symbol::get_iterator(scope);
|
|
let iterator = object
|
|
.get(scope, iterator_key.into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("Symbol.iterator method");
|
|
assert_eq!(
|
|
iterator
|
|
.get_name(scope)
|
|
.to_string(scope)
|
|
.map(|value| value.to_rust_string_lossy(scope)),
|
|
Some("values".to_owned())
|
|
);
|
|
assert_eq!(
|
|
iterator
|
|
.get(scope, v8::String::new(scope, "length").unwrap().into())
|
|
.and_then(|value| value.uint32_value(scope)),
|
|
Some(0)
|
|
);
|
|
assert_eq!(
|
|
object.has_own_property(scope, v8::String::new(scope, "values").unwrap().into()),
|
|
Some(false)
|
|
);
|
|
let descriptor = object
|
|
.get_own_property_descriptor(scope, iterator_key.into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("Symbol.iterator descriptor");
|
|
let descriptor_value = descriptor
|
|
.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
.expect("descriptor value");
|
|
assert!(descriptor_value.strict_equals(iterator.into()));
|
|
assert_eq!(
|
|
descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn function_template_declaration_installs_webidl_method_descriptors() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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 template = SampleFunctionTemplateDeclaration::build(scope);
|
|
let constructor = template
|
|
.get_function(scope)
|
|
.expect("SampleTemplate function");
|
|
assert_eq!(
|
|
constructor
|
|
.get(scope, v8::String::new(scope, "length").unwrap().into())
|
|
.and_then(|value| value.uint32_value(scope)),
|
|
Some(1)
|
|
);
|
|
let from_descriptor = constructor
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "from").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("from static method descriptor");
|
|
assert_eq!(
|
|
from_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
from_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
from_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
let from = from_descriptor
|
|
.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("from static method function");
|
|
assert_eq!(
|
|
from.has_own_property(scope, v8::String::new(scope, "prototype").unwrap().into()),
|
|
Some(false),
|
|
"static Web IDL operations must be concise non-constructor functions"
|
|
);
|
|
assert_eq!(
|
|
from.get_name(scope)
|
|
.to_string(scope)
|
|
.map(|value| value.to_rust_string_lossy(scope)),
|
|
Some("from".to_owned())
|
|
);
|
|
assert_eq!(
|
|
from.get(scope, v8::String::new(scope, "length").unwrap().into())
|
|
.and_then(|value| value.uint32_value(scope)),
|
|
Some(1)
|
|
);
|
|
let from_result = from
|
|
.call(scope, constructor.into(), &[])
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(from_result.as_deref(), Some("template-static-data"));
|
|
|
|
let locked_static_descriptor = constructor
|
|
.get_own_property_descriptor(
|
|
scope,
|
|
v8::String::new(scope, "lockedStatic").unwrap().into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("lockedStatic descriptor");
|
|
assert_eq!(
|
|
locked_static_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
locked_static_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
locked_static_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
|
|
let prototype = constructor
|
|
.get(scope, v8::String::new(scope, "prototype").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("SampleTemplate.prototype");
|
|
|
|
for owner in [constructor.into(), prototype] {
|
|
let ready_descriptor = owner
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "READY").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("READY constant descriptor");
|
|
assert_eq!(
|
|
ready_descriptor
|
|
.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
.and_then(|value| value.uint32_value(scope)),
|
|
Some(4)
|
|
);
|
|
assert_eq!(
|
|
ready_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
ready_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
ready_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
}
|
|
|
|
let append_descriptor = prototype
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "append").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("append descriptor");
|
|
assert_eq!(
|
|
append_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
append_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
append_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
let append = append_descriptor
|
|
.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("append function");
|
|
assert_eq!(
|
|
append.has_own_property(scope, v8::String::new(scope, "prototype").unwrap().into(),),
|
|
Some(false),
|
|
"prototype Web IDL operations must be concise non-constructor functions"
|
|
);
|
|
assert_eq!(
|
|
append
|
|
.get_name(scope)
|
|
.to_string(scope)
|
|
.map(|value| value.to_rust_string_lossy(scope)),
|
|
Some("append".to_owned())
|
|
);
|
|
assert_eq!(
|
|
append
|
|
.get(scope, v8::String::new(scope, "length").unwrap().into())
|
|
.and_then(|value| value.uint32_value(scope)),
|
|
Some(2)
|
|
);
|
|
let append_result = append
|
|
.call(scope, prototype.into(), &[])
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(append_result.as_deref(), Some("template-method-data"));
|
|
|
|
let locked_descriptor = prototype
|
|
.get_own_property_descriptor(
|
|
scope,
|
|
v8::String::new(scope, "lockedMethod").unwrap().into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("lockedMethod descriptor");
|
|
assert_eq!(
|
|
locked_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
locked_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
locked_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
|
|
let entries = prototype
|
|
.get(scope, v8::String::new(scope, "entries").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("entries function");
|
|
let iterator_key = v8::Symbol::get_iterator(scope);
|
|
let iterator_descriptor = prototype
|
|
.get_own_property_descriptor(scope, iterator_key.into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("Symbol.iterator descriptor");
|
|
assert_eq!(
|
|
iterator_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
iterator_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
iterator_descriptor
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "configurable").unwrap().into()
|
|
)
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
let iterator = iterator_descriptor
|
|
.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("Symbol.iterator function");
|
|
assert!(iterator.strict_equals(entries.into()));
|
|
assert_eq!(
|
|
iterator
|
|
.get_name(scope)
|
|
.to_string(scope)
|
|
.map(|value| value.to_rust_string_lossy(scope)),
|
|
Some("entries".to_owned())
|
|
);
|
|
|
|
let instance = constructor
|
|
.new_instance(scope, &[])
|
|
.expect("SampleTemplate instance");
|
|
let receiver_key = v8::String::new(scope, "receiver").unwrap();
|
|
let receiver = instance
|
|
.get(scope, receiver_key.into())
|
|
.expect("receiver accessor value");
|
|
assert!(
|
|
receiver.strict_equals(instance.into()),
|
|
"function accessor must observe the actual receiver"
|
|
);
|
|
assert!(
|
|
!instance
|
|
.has_own_property(scope, receiver_key.into())
|
|
.unwrap_or(false),
|
|
"prototype accessor must not create an instance property"
|
|
);
|
|
let receiver_descriptor = prototype
|
|
.get_own_property_descriptor(scope, receiver_key.into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("receiver descriptor");
|
|
let receiver_getter = receiver_descriptor
|
|
.get(scope, v8::String::new(scope, "get").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("receiver getter");
|
|
assert_eq!(
|
|
receiver_getter
|
|
.has_own_property(scope, v8::String::new(scope, "prototype").unwrap().into(),),
|
|
Some(false),
|
|
"template accessor getters must not expose a prototype"
|
|
);
|
|
assert_eq!(
|
|
receiver_getter
|
|
.get_name(scope)
|
|
.to_string(scope)
|
|
.map(|value| value.to_rust_string_lossy(scope)),
|
|
Some("get receiver".to_owned())
|
|
);
|
|
assert_eq!(
|
|
receiver_getter
|
|
.get(scope, v8::String::new(scope, "length").unwrap().into())
|
|
.and_then(|value| value.uint32_value(scope)),
|
|
Some(0)
|
|
);
|
|
|
|
let data_key = v8::String::new(scope, "dataValue").unwrap();
|
|
let getter_data = instance
|
|
.get(scope, data_key.into())
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(getter_data.as_deref(), Some("template-getter-data"));
|
|
let written = v8::String::new(scope, "template-written").unwrap();
|
|
assert_eq!(
|
|
instance.set(scope, data_key.into(), written.into()),
|
|
Some(true)
|
|
);
|
|
let setter_payload = instance
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "setterPayload").unwrap().into(),
|
|
)
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
assert_eq!(setter_payload.as_deref(), Some("template-setter-data"));
|
|
let data_descriptor = prototype
|
|
.get_own_property_descriptor(scope, data_key.into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("dataValue descriptor");
|
|
let data_setter = data_descriptor
|
|
.get(scope, v8::String::new(scope, "set").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.expect("dataValue setter");
|
|
assert_eq!(
|
|
data_setter.has_own_property(scope, v8::String::new(scope, "prototype").unwrap().into(),),
|
|
Some(false),
|
|
"template accessor setters with callback data must not expose a prototype"
|
|
);
|
|
assert_eq!(
|
|
data_setter
|
|
.get_name(scope)
|
|
.to_string(scope)
|
|
.map(|value| value.to_rust_string_lossy(scope)),
|
|
Some("set dataValue".to_owned())
|
|
);
|
|
assert_eq!(
|
|
data_setter
|
|
.get(scope, v8::String::new(scope, "length").unwrap().into())
|
|
.and_then(|value| value.uint32_value(scope)),
|
|
Some(1)
|
|
);
|
|
|
|
let native_holder = instance
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "nativeHolder").unwrap().into(),
|
|
)
|
|
.expect("native holder accessor value");
|
|
assert!(
|
|
native_holder.strict_equals(prototype.into()),
|
|
"native accessor must retain explicit holder semantics"
|
|
);
|
|
let native_holder_descriptor = prototype
|
|
.get_own_property_descriptor(
|
|
scope,
|
|
v8::String::new(scope, "nativeHolder").unwrap().into(),
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("nativeHolder descriptor");
|
|
assert_eq!(
|
|
native_holder_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true),
|
|
"template-level enumerable must apply to native accessors"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn function_template_intrinsic_properties_do_not_read_public_array() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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);
|
|
|
|
run_script(
|
|
scope,
|
|
r#"
|
|
globalThis.__originalArrayEntries = Array.prototype.entries;
|
|
globalThis.__originalArrayValues = Array.prototype.values;
|
|
globalThis.Array = undefined;
|
|
"#,
|
|
);
|
|
let global = context.global(scope);
|
|
let original_entries = global
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "__originalArrayEntries")
|
|
.unwrap()
|
|
.into(),
|
|
)
|
|
.expect("saved Array.prototype.entries");
|
|
let original_values = global
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "__originalArrayValues")
|
|
.unwrap()
|
|
.into(),
|
|
)
|
|
.expect("saved Array.prototype.values");
|
|
|
|
let template = IntrinsicArrayTemplateDeclaration::build(scope);
|
|
let constructor = template
|
|
.get_function(scope)
|
|
.expect("IntrinsicArrayTemplate function");
|
|
let prototype = constructor
|
|
.get(scope, v8::String::new(scope, "prototype").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("IntrinsicArrayTemplate.prototype");
|
|
|
|
let entries = prototype
|
|
.get(scope, v8::String::new(scope, "entries").unwrap().into())
|
|
.expect("entries intrinsic");
|
|
let values = prototype
|
|
.get(scope, v8::String::new(scope, "values").unwrap().into())
|
|
.expect("values intrinsic");
|
|
let iterator_key = v8::Symbol::get_iterator(scope);
|
|
let iterator = prototype
|
|
.get(scope, iterator_key.into())
|
|
.expect("Symbol.iterator intrinsic");
|
|
assert!(entries.strict_equals(original_entries));
|
|
assert!(values.strict_equals(original_values));
|
|
assert!(iterator.strict_equals(original_values));
|
|
|
|
let values_descriptor = prototype
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "values").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("values descriptor");
|
|
assert_eq!(
|
|
values_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
let iterator_descriptor = prototype
|
|
.get_own_property_descriptor(scope, iterator_key.into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("Symbol.iterator descriptor");
|
|
assert_eq!(
|
|
iterator_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn function_template_intrinsic_parent_does_not_read_public_error() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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);
|
|
|
|
run_script(
|
|
scope,
|
|
r#"
|
|
globalThis.__originalErrorPrototype = Error.prototype;
|
|
globalThis.Error = undefined;
|
|
"#,
|
|
);
|
|
let global = context.global(scope);
|
|
let original_error_prototype = global
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "__originalErrorPrototype")
|
|
.unwrap()
|
|
.into(),
|
|
)
|
|
.expect("saved Error.prototype");
|
|
|
|
let template = v8::FunctionTemplate::builder(sample_constructor_callback).build(scope);
|
|
IntrinsicErrorParentTemplateDeclaration::initialize_template(scope, template);
|
|
let constructor = template
|
|
.get_function(scope)
|
|
.expect("IntrinsicErrorParentTemplate function");
|
|
let prototype = constructor
|
|
.get(scope, v8::String::new(scope, "prototype").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("IntrinsicErrorParentTemplate.prototype");
|
|
assert!(
|
|
prototype
|
|
.get_prototype(scope)
|
|
.is_some_and(|parent| parent.strict_equals(original_error_prototype))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn function_template_declares_complete_intrinsic_iterator_shape() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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);
|
|
|
|
run_script(
|
|
scope,
|
|
r#"
|
|
globalThis.__originalIteratorPrototype =
|
|
Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
|
|
Array.prototype[Symbol.iterator] = function poisonedIterator() {
|
|
throw new Error("public iterator observed");
|
|
};
|
|
Object.getPrototypeOf = function poisonedGetPrototypeOf() {
|
|
throw new Error("public Object.getPrototypeOf observed");
|
|
};
|
|
"#,
|
|
);
|
|
let global = context.global(scope);
|
|
let original_iterator_prototype = global
|
|
.get(
|
|
scope,
|
|
v8::String::new(scope, "__originalIteratorPrototype")
|
|
.unwrap()
|
|
.into(),
|
|
)
|
|
.expect("saved IteratorPrototype");
|
|
|
|
let template = IntrinsicIteratorTemplateDeclaration::build(scope);
|
|
let constructor = template
|
|
.get_function(scope)
|
|
.expect("Example Iterator function");
|
|
let prototype_key = v8::String::new(scope, "prototype").unwrap();
|
|
let prototype = constructor
|
|
.get(scope, prototype_key.into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("Example Iterator prototype");
|
|
assert!(
|
|
prototype
|
|
.get_prototype(scope)
|
|
.is_some_and(|parent| parent.strict_equals(original_iterator_prototype))
|
|
);
|
|
|
|
let constructor_prototype_descriptor = constructor
|
|
.get_own_property_descriptor(scope, prototype_key.into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("constructor prototype descriptor");
|
|
assert_eq!(
|
|
constructor_prototype_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
|
|
let tag = v8::Symbol::get_to_string_tag(scope);
|
|
let tag_descriptor = prototype
|
|
.get_own_property_descriptor(scope, tag.into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.expect("iterator toStringTag descriptor");
|
|
assert_eq!(
|
|
tag_descriptor
|
|
.get(scope, v8::String::new(scope, "value").unwrap().into())
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope))
|
|
.as_deref(),
|
|
Some("Example Iterator")
|
|
);
|
|
assert_eq!(
|
|
tag_descriptor
|
|
.get(scope, v8::String::new(scope, "writable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
assert_eq!(
|
|
tag_descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(false)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn object_literal_declaration_sets_runtime_keys_and_copies_snapshot_values() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::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);
|
|
|
|
run_script(
|
|
scope,
|
|
r#"
|
|
globalThis.__objectLiteralSetterHit = null;
|
|
Object.defineProperty(Object.prototype, "runtimeKey", {
|
|
configurable: true,
|
|
set(value) { globalThis.__objectLiteralSetterHit = value; }
|
|
});
|
|
"#,
|
|
);
|
|
|
|
let source = v8::Object::new(scope);
|
|
let _ = source.set(
|
|
scope,
|
|
v8::String::new(scope, "copied").unwrap().into(),
|
|
v8::Number::new(scope, 42.0).into(),
|
|
);
|
|
|
|
let dynamic_key = v8::String::new(scope, "runtimeKey").unwrap();
|
|
let literal = ObjectLiteralDeclaration::bind(scope);
|
|
let dynamic_value = v8::Boolean::new(scope, true).into();
|
|
literal.set_value_property(scope, dynamic_key.into(), dynamic_value);
|
|
let string_value = v8::String::new(scope, "value").unwrap().into();
|
|
literal.set_string_property(scope, "stringKey", string_value);
|
|
let proto_key = v8::String::new(scope, "__proto__").unwrap();
|
|
let attempted_prototype = v8::Object::new(scope);
|
|
literal.set_value_property(scope, proto_key.into(), attempted_prototype.into());
|
|
literal.copy_string_property(scope, source, "copied");
|
|
literal.copy_string_property(scope, source, "missing");
|
|
|
|
let object_view = literal.as_object();
|
|
assert_eq!(
|
|
object_view.has_own_property(scope, proto_key.into()),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
object_view
|
|
.get_prototype(scope)
|
|
.map(|prototype| prototype.strict_equals(attempted_prototype.into())),
|
|
Some(false)
|
|
);
|
|
let object = literal.into_object();
|
|
assert!(object.strict_equals(object_view.into()));
|
|
assert_eq!(
|
|
object
|
|
.get(scope, v8::String::new(scope, "runtimeKey").unwrap().into())
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
object
|
|
.get(scope, v8::String::new(scope, "stringKey").unwrap().into())
|
|
.and_then(|value| value.to_string(scope))
|
|
.map(|value| value.to_rust_string_lossy(scope))
|
|
.as_deref(),
|
|
Some("value")
|
|
);
|
|
assert_eq!(
|
|
object
|
|
.get(scope, v8::String::new(scope, "copied").unwrap().into())
|
|
.and_then(|value| value.number_value(scope)),
|
|
Some(42.0)
|
|
);
|
|
assert_eq!(
|
|
object.has_own_property(scope, v8::String::new(scope, "missing").unwrap().into()),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
object
|
|
.get(scope, v8::String::new(scope, "missing").unwrap().into())
|
|
.map(|value| value.is_undefined()),
|
|
Some(true)
|
|
);
|
|
assert_eq!(
|
|
object
|
|
.get_own_property_descriptor(scope, v8::String::new(scope, "copied").unwrap().into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.and_then(|descriptor| descriptor
|
|
.get(scope, v8::String::new(scope, "enumerable").unwrap().into()))
|
|
.map(|value| value.boolean_value(scope)),
|
|
Some(true)
|
|
);
|
|
let hit = run_script(
|
|
scope,
|
|
r#"
|
|
const hit = globalThis.__objectLiteralSetterHit;
|
|
delete Object.prototype.runtimeKey;
|
|
hit;
|
|
"#,
|
|
);
|
|
assert!(hit.is_null());
|
|
}
|
|
|
|
fn get_private_value<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
slot: &str,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
let name = v8::String::new(scope, slot)?;
|
|
let key = v8::Private::for_api(scope, Some(name));
|
|
let value = object.get_private(scope, key)?;
|
|
if value.is_undefined() {
|
|
None
|
|
} else {
|
|
Some(value)
|
|
}
|
|
}
|
|
|
|
mod interfaces {
|
|
moli_webapi_declare::declare_web_api_interfaces! {
|
|
pub(super) ConstructibleSample;
|
|
pub(super) MissingSampleInterface;
|
|
pub(super) SamplePerformance;
|
|
pub(super) SampleTemplate;
|
|
}
|
|
}
|