avm1: Extract ArrayObject
This commit is contained in:
parent
b7de03b5c8
commit
95c30b85e1
|
@ -40,6 +40,7 @@ pub use crate::avm1::error::Error;
|
||||||
use crate::avm1::globals::as_broadcaster;
|
use crate::avm1::globals::as_broadcaster;
|
||||||
use crate::avm1::globals::as_broadcaster::BroadcasterFunctions;
|
use crate::avm1::globals::as_broadcaster::BroadcasterFunctions;
|
||||||
pub use globals::SystemPrototypes;
|
pub use globals::SystemPrototypes;
|
||||||
|
pub use object::array_object::ArrayObject;
|
||||||
pub use object::script_object::ScriptObject;
|
pub use object::script_object::ScriptObject;
|
||||||
pub use object::sound_object::SoundObject;
|
pub use object::sound_object::SoundObject;
|
||||||
pub use object::stage_object::StageObject;
|
pub use object::stage_object::StageObject;
|
||||||
|
|
|
@ -5,7 +5,8 @@ use crate::avm1::object::{Object, TObject};
|
||||||
use crate::avm1::property::Attribute;
|
use crate::avm1::property::Attribute;
|
||||||
use crate::avm1::scope::Scope;
|
use crate::avm1::scope::Scope;
|
||||||
use crate::avm1::{
|
use crate::avm1::{
|
||||||
fscommand, globals, scope, skip_actions, start_drag, AvmString, ScriptObject, Value,
|
fscommand, globals, scope, skip_actions, start_drag, ArrayObject, AvmString, ScriptObject,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
use crate::backend::navigator::{NavigationMethod, RequestOptions};
|
use crate::backend::navigator::{NavigationMethod, RequestOptions};
|
||||||
use crate::context::UpdateContext;
|
use crate::context::UpdateContext;
|
||||||
|
@ -1469,15 +1470,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
// InitArray pops no args and pushes undefined if num_elements is out of range.
|
// InitArray pops no args and pushes undefined if num_elements is out of range.
|
||||||
Value::Undefined
|
Value::Undefined
|
||||||
} else {
|
} else {
|
||||||
let array = ScriptObject::array(
|
ArrayObject::new(
|
||||||
self.context.gc_context,
|
self.context.gc_context,
|
||||||
Some(self.context.avm1.prototypes.array),
|
self.context.avm1.prototypes().array,
|
||||||
);
|
(0..num_elements as i32).map(|_| self.context.avm1.pop()),
|
||||||
for i in 0..num_elements as i32 {
|
)
|
||||||
let element = self.context.avm1.pop();
|
.into()
|
||||||
array.set_element(self, i, element).unwrap();
|
|
||||||
}
|
|
||||||
Value::Object(array.into())
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.context.avm1.push(result);
|
self.context.avm1.push(result);
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::avm1::object::super_object::SuperObject;
|
||||||
use crate::avm1::property::Attribute;
|
use crate::avm1::property::Attribute;
|
||||||
use crate::avm1::scope::Scope;
|
use crate::avm1::scope::Scope;
|
||||||
use crate::avm1::value::Value;
|
use crate::avm1::value::Value;
|
||||||
use crate::avm1::{Object, ObjectPtr, ScriptObject, TObject};
|
use crate::avm1::{ArrayObject, Object, ObjectPtr, ScriptObject, TObject};
|
||||||
use crate::display_object::{DisplayObject, TDisplayObject};
|
use crate::display_object::{DisplayObject, TDisplayObject};
|
||||||
use crate::tag_utils::SwfSlice;
|
use crate::tag_utils::SwfSlice;
|
||||||
use gc_arena::{Collect, CollectionContext, Gc, GcCell, MutationContext};
|
use gc_arena::{Collect, CollectionContext, Gc, GcCell, MutationContext};
|
||||||
|
@ -244,10 +244,16 @@ impl<'gc> Executable<'gc> {
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Scope::new_local_scope(af.scope(), activation.context.gc_context),
|
Scope::new_local_scope(af.scope(), activation.context.gc_context),
|
||||||
);
|
);
|
||||||
let arguments = ScriptObject::array(
|
|
||||||
|
let arguments = if af.flags.contains(FunctionFlags::SUPPRESS_ARGUMENTS) {
|
||||||
|
ArrayObject::empty(activation)
|
||||||
|
} else {
|
||||||
|
ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes().array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
args.iter().cloned(),
|
||||||
|
)
|
||||||
|
};
|
||||||
arguments.define_value(
|
arguments.define_value(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
"callee",
|
"callee",
|
||||||
|
@ -262,14 +268,6 @@ impl<'gc> Executable<'gc> {
|
||||||
Attribute::DONT_ENUM,
|
Attribute::DONT_ENUM,
|
||||||
);
|
);
|
||||||
|
|
||||||
if !af.flags.contains(FunctionFlags::SUPPRESS_ARGUMENTS) {
|
|
||||||
for (i, arg) in args.iter().enumerate() {
|
|
||||||
arguments
|
|
||||||
.set_element(activation, i as i32, arg.to_owned())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let argcell = arguments.into();
|
let argcell = arguments.into();
|
||||||
let super_object: Option<Object<'gc>> =
|
let super_object: Option<Object<'gc>> =
|
||||||
if !af.flags.contains(FunctionFlags::SUPPRESS_SUPER) {
|
if !af.flags.contains(FunctionFlags::SUPPRESS_SUPER) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::avm1::activation::Activation;
|
||||||
use crate::avm1::error::Error;
|
use crate::avm1::error::Error;
|
||||||
use crate::avm1::function::{Executable, FunctionObject};
|
use crate::avm1::function::{Executable, FunctionObject};
|
||||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||||
use crate::avm1::{AvmString, Object, ScriptObject, TObject, Value};
|
use crate::avm1::{ArrayObject, AvmString, Object, TObject, Value};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use gc_arena::MutationContext;
|
use gc_arena::MutationContext;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
@ -101,11 +101,24 @@ pub fn array_function<'gc>(
|
||||||
_this: Object<'gc>,
|
_this: Object<'gc>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
let array = ScriptObject::array(
|
if let [Value::Number(length)] = *args {
|
||||||
|
let length = if length.is_finite() && length >= i32::MIN.into() && length <= i32::MAX.into()
|
||||||
|
{
|
||||||
|
length as i32
|
||||||
|
} else {
|
||||||
|
i32::MIN
|
||||||
|
};
|
||||||
|
let array = ArrayObject::empty(activation);
|
||||||
|
array.set_length(activation, length)?;
|
||||||
|
Ok(array.into())
|
||||||
|
} else {
|
||||||
|
Ok(ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes.array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
args.iter().cloned(),
|
||||||
constructor(activation, array.into(), args)
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push<'gc>(
|
pub fn push<'gc>(
|
||||||
|
@ -297,18 +310,12 @@ pub fn slice<'gc>(
|
||||||
make_index_absolute(end.coerce_to_i32(activation)?, length)
|
make_index_absolute(end.coerce_to_i32(activation)?, length)
|
||||||
};
|
};
|
||||||
|
|
||||||
let array = ScriptObject::array(
|
Ok(ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes.array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
(start..end).map(|i| this.get_element(activation, i)),
|
||||||
for i in start..end {
|
)
|
||||||
if this.has_element(activation, i) {
|
.into())
|
||||||
let element = this.get_element(activation, i);
|
|
||||||
array.set_element(activation, i - start, element).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(array.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn splice<'gc>(
|
pub fn splice<'gc>(
|
||||||
|
@ -332,17 +339,9 @@ pub fn splice<'gc>(
|
||||||
length - start
|
length - start
|
||||||
};
|
};
|
||||||
|
|
||||||
let result_array = ScriptObject::array(
|
let result_elements: Vec<_> = (0..delete_count)
|
||||||
activation.context.gc_context,
|
.map(|i| this.get_element(activation, start + i))
|
||||||
Some(activation.context.avm1.prototypes.array),
|
.collect();
|
||||||
);
|
|
||||||
for i in 0..delete_count {
|
|
||||||
if this.has_element(activation, start + i) {
|
|
||||||
let element = this.get_element(activation, start + i);
|
|
||||||
result_array.set_element(activation, i, element).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result_array.set_length(activation, delete_count).unwrap();
|
|
||||||
|
|
||||||
let items = if args.len() > 2 { &args[2..] } else { &[] };
|
let items = if args.len() > 2 { &args[2..] } else { &[] };
|
||||||
// TODO: Avoid code duplication.
|
// TODO: Avoid code duplication.
|
||||||
|
@ -371,7 +370,12 @@ pub fn splice<'gc>(
|
||||||
}
|
}
|
||||||
this.set_length(activation, length - delete_count + items.len() as i32)?;
|
this.set_length(activation, length - delete_count + items.len() as i32)?;
|
||||||
|
|
||||||
Ok(result_array.into())
|
Ok(ArrayObject::new(
|
||||||
|
activation.context.gc_context,
|
||||||
|
activation.context.avm1.prototypes().array,
|
||||||
|
result_elements,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn concat<'gc>(
|
pub fn concat<'gc>(
|
||||||
|
@ -379,21 +383,10 @@ pub fn concat<'gc>(
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
let result_array = ScriptObject::array(
|
let mut elements = vec![];
|
||||||
activation.context.gc_context,
|
|
||||||
Some(activation.context.avm1.prototypes.array),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut index = 0;
|
|
||||||
for &value in [this.into()].iter().chain(args) {
|
for &value in [this.into()].iter().chain(args) {
|
||||||
let array_object = if let Value::Object(object) = value {
|
let array_object = if let Value::Object(object) = value {
|
||||||
if activation
|
if object.as_array_object().is_some() {
|
||||||
.context
|
|
||||||
.avm1
|
|
||||||
.prototypes
|
|
||||||
.array
|
|
||||||
.is_prototype_of(object)
|
|
||||||
{
|
|
||||||
Some(object)
|
Some(object)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -405,21 +398,19 @@ pub fn concat<'gc>(
|
||||||
if let Some(array_object) = array_object {
|
if let Some(array_object) = array_object {
|
||||||
let length = array_object.length(activation)?;
|
let length = array_object.length(activation)?;
|
||||||
for i in 0..length {
|
for i in 0..length {
|
||||||
if array_object.has_element(activation, i) {
|
|
||||||
let element = array_object.get_element(activation, i);
|
let element = array_object.get_element(activation, i);
|
||||||
result_array
|
elements.push(element);
|
||||||
.set_element(activation, index, element)
|
|
||||||
.unwrap();
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result_array.set_element(activation, index, value).unwrap();
|
elements.push(value);
|
||||||
index += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(ArrayObject::new(
|
||||||
Ok(result_array.into())
|
activation.context.gc_context,
|
||||||
|
activation.context.avm1.prototypes().array,
|
||||||
|
elements,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_string<'gc>(
|
pub fn to_string<'gc>(
|
||||||
|
@ -597,17 +588,12 @@ fn sort_with_function<'gc>(
|
||||||
if flags.contains(SortFlags::RETURN_INDEXED_ARRAY) {
|
if flags.contains(SortFlags::RETURN_INDEXED_ARRAY) {
|
||||||
// Array.RETURNINDEXEDARRAY returns an array containing the sorted indices, and does not modify
|
// Array.RETURNINDEXEDARRAY returns an array containing the sorted indices, and does not modify
|
||||||
// the original array.
|
// the original array.
|
||||||
let array = ScriptObject::array(
|
Ok(ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes.array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
values.into_iter().map(|(index, _)| index.into()),
|
||||||
for (i, (index, _)) in values.into_iter().enumerate() {
|
)
|
||||||
array
|
.into())
|
||||||
.set_element(activation, i as i32, index.into())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
array.set_length(activation, length).unwrap();
|
|
||||||
Ok(array.into())
|
|
||||||
} else {
|
} else {
|
||||||
// Standard sort modifies the original array, and returns it.
|
// Standard sort modifies the original array, and returns it.
|
||||||
// AS2 reference incorrectly states this returns nothing, but it returns the original array, sorted.
|
// AS2 reference incorrectly states this returns nothing, but it returns the original array, sorted.
|
||||||
|
@ -624,10 +610,10 @@ pub fn create_proto<'gc>(
|
||||||
proto: Object<'gc>,
|
proto: Object<'gc>,
|
||||||
fn_proto: Object<'gc>,
|
fn_proto: Object<'gc>,
|
||||||
) -> Object<'gc> {
|
) -> Object<'gc> {
|
||||||
let array = ScriptObject::array(gc_context, Some(proto));
|
let array = ArrayObject::empty_with_proto(gc_context, Some(proto));
|
||||||
let object = array.as_script_object().unwrap();
|
let object = array.as_script_object().unwrap();
|
||||||
define_properties_on(PROTO_DECLS, gc_context, object, fn_proto);
|
define_properties_on(PROTO_DECLS, gc_context, object, fn_proto);
|
||||||
array.into()
|
object.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sort_compare_string<'gc>(
|
fn sort_compare_string<'gc>(
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::avm1::error::Error;
|
||||||
use crate::avm1::object::TObject;
|
use crate::avm1::object::TObject;
|
||||||
use crate::avm1::property::Attribute;
|
use crate::avm1::property::Attribute;
|
||||||
use crate::avm1::property_decl::Declaration;
|
use crate::avm1::property_decl::Declaration;
|
||||||
use crate::avm1::{Object, ScriptObject, Value};
|
use crate::avm1::{ArrayObject, Object, ScriptObject, Value};
|
||||||
use gc_arena::{Collect, MutationContext};
|
use gc_arena::{Collect, MutationContext};
|
||||||
|
|
||||||
const OBJECT_DECLS: &[Declaration] = declare_properties! {
|
const OBJECT_DECLS: &[Declaration] = declare_properties! {
|
||||||
|
@ -183,35 +183,30 @@ pub fn initialize<'gc>(
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initialize_internal<'gc>(
|
fn initialize_internal<'gc>(
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
broadcaster: Object<'gc>,
|
broadcaster: Object<'gc>,
|
||||||
functions: BroadcasterFunctions<'gc>,
|
functions: BroadcasterFunctions<'gc>,
|
||||||
array_proto: Object<'gc>,
|
array_proto: Object<'gc>,
|
||||||
) {
|
) {
|
||||||
let listeners = ScriptObject::array(gc_context, Some(array_proto));
|
|
||||||
|
|
||||||
broadcaster.define_value(
|
broadcaster.define_value(
|
||||||
gc_context,
|
gc_context,
|
||||||
"_listeners",
|
"_listeners",
|
||||||
listeners.into(),
|
ArrayObject::empty_with_proto(gc_context, Some(array_proto)).into(),
|
||||||
Attribute::DONT_ENUM,
|
Attribute::DONT_ENUM,
|
||||||
);
|
);
|
||||||
|
|
||||||
broadcaster.define_value(
|
broadcaster.define_value(
|
||||||
gc_context,
|
gc_context,
|
||||||
"addListener",
|
"addListener",
|
||||||
functions.add_listener.into(),
|
functions.add_listener.into(),
|
||||||
Attribute::DONT_DELETE | Attribute::DONT_ENUM,
|
Attribute::DONT_DELETE | Attribute::DONT_ENUM,
|
||||||
);
|
);
|
||||||
|
|
||||||
broadcaster.define_value(
|
broadcaster.define_value(
|
||||||
gc_context,
|
gc_context,
|
||||||
"removeListener",
|
"removeListener",
|
||||||
functions.remove_listener.into(),
|
functions.remove_listener.into(),
|
||||||
Attribute::DONT_DELETE | Attribute::DONT_ENUM,
|
Attribute::DONT_DELETE | Attribute::DONT_ENUM,
|
||||||
);
|
);
|
||||||
|
|
||||||
broadcaster.define_value(
|
broadcaster.define_value(
|
||||||
gc_context,
|
gc_context,
|
||||||
"broadcastMessage",
|
"broadcastMessage",
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::avm1::activation::Activation;
|
||||||
use crate::avm1::error::Error;
|
use crate::avm1::error::Error;
|
||||||
use crate::avm1::object::color_matrix_filter::ColorMatrixFilterObject;
|
use crate::avm1::object::color_matrix_filter::ColorMatrixFilterObject;
|
||||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||||
use crate::avm1::{Object, ScriptObject, TObject, Value};
|
use crate::avm1::{ArrayObject, Object, TObject, Value};
|
||||||
use gc_arena::MutationContext;
|
use gc_arena::MutationContext;
|
||||||
|
|
||||||
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
||||||
|
@ -27,16 +27,12 @@ pub fn matrix<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
if let Some(filter) = this.as_color_matrix_filter_object() {
|
if let Some(filter) = this.as_color_matrix_filter_object() {
|
||||||
let array = ScriptObject::array(
|
return Ok(ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes.array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
filter.matrix().iter().map(|&x| x.into()),
|
||||||
for (i, item) in filter.matrix().iter().copied().enumerate() {
|
)
|
||||||
array
|
.into());
|
||||||
.set_element(activation, i as i32, item.into())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
return Ok(array.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::avm1::activation::Activation;
|
||||||
use crate::avm1::error::Error;
|
use crate::avm1::error::Error;
|
||||||
use crate::avm1::object::convolution_filter::ConvolutionFilterObject;
|
use crate::avm1::object::convolution_filter::ConvolutionFilterObject;
|
||||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||||
use crate::avm1::{Object, ScriptObject, TObject, Value};
|
use crate::avm1::{ArrayObject, Object, TObject, Value};
|
||||||
use gc_arena::MutationContext;
|
use gc_arena::MutationContext;
|
||||||
|
|
||||||
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
||||||
|
@ -189,16 +189,12 @@ pub fn matrix<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
if let Some(filter) = this.as_convolution_filter_object() {
|
if let Some(filter) = this.as_convolution_filter_object() {
|
||||||
let array = ScriptObject::array(
|
return Ok(ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes.array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
filter.matrix().iter().map(|&x| x.into()),
|
||||||
for (i, item) in filter.matrix().iter().copied().enumerate() {
|
)
|
||||||
array
|
.into());
|
||||||
.set_element(activation, i as i32, item.into())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
return Ok(array.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::avm1::error::Error;
|
||||||
use crate::avm1::object::bevel_filter::BevelFilterType;
|
use crate::avm1::object::bevel_filter::BevelFilterType;
|
||||||
use crate::avm1::object::gradient_bevel_filter::GradientBevelFilterObject;
|
use crate::avm1::object::gradient_bevel_filter::GradientBevelFilterObject;
|
||||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||||
use crate::avm1::{AvmString, Object, ScriptObject, TObject, Value};
|
use crate::avm1::{ArrayObject, AvmString, Object, TObject, Value};
|
||||||
use gc_arena::MutationContext;
|
use gc_arena::MutationContext;
|
||||||
|
|
||||||
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
||||||
|
@ -112,16 +112,12 @@ pub fn colors<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
if let Some(filter) = this.as_gradient_bevel_filter_object() {
|
if let Some(filter) = this.as_gradient_bevel_filter_object() {
|
||||||
let array = ScriptObject::array(
|
return Ok(ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes.array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
filter.colors().iter().map(|&x| x.into()),
|
||||||
for (i, item) in filter.colors().iter().copied().enumerate() {
|
)
|
||||||
array
|
.into());
|
||||||
.set_element(activation, i as i32, item.into())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
return Ok(array.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
|
@ -176,16 +172,12 @@ pub fn alphas<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
if let Some(filter) = this.as_gradient_bevel_filter_object() {
|
if let Some(filter) = this.as_gradient_bevel_filter_object() {
|
||||||
let array = ScriptObject::array(
|
return Ok(ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes.array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
filter.alphas().iter().map(|&x| x.into()),
|
||||||
for (i, item) in filter.alphas().iter().copied().enumerate() {
|
)
|
||||||
array
|
.into());
|
||||||
.set_element(activation, i as i32, item.into())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
return Ok(array.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
|
@ -231,16 +223,12 @@ pub fn ratios<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
if let Some(filter) = this.as_gradient_bevel_filter_object() {
|
if let Some(filter) = this.as_gradient_bevel_filter_object() {
|
||||||
let array = ScriptObject::array(
|
return Ok(ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes.array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
filter.ratios().iter().map(|&x| x.into()),
|
||||||
for (i, item) in filter.ratios().iter().copied().enumerate() {
|
)
|
||||||
array
|
.into());
|
||||||
.set_element(activation, i as i32, item.into())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
return Ok(array.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::avm1::error::Error;
|
||||||
use crate::avm1::object::bevel_filter::BevelFilterType;
|
use crate::avm1::object::bevel_filter::BevelFilterType;
|
||||||
use crate::avm1::object::gradient_glow_filter::GradientGlowFilterObject;
|
use crate::avm1::object::gradient_glow_filter::GradientGlowFilterObject;
|
||||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||||
use crate::avm1::{AvmString, Object, ScriptObject, TObject, Value};
|
use crate::avm1::{ArrayObject, AvmString, Object, TObject, Value};
|
||||||
use gc_arena::MutationContext;
|
use gc_arena::MutationContext;
|
||||||
|
|
||||||
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
||||||
|
@ -112,16 +112,12 @@ pub fn colors<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
if let Some(filter) = this.as_gradient_glow_filter_object() {
|
if let Some(filter) = this.as_gradient_glow_filter_object() {
|
||||||
let array = ScriptObject::array(
|
return Ok(ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes.array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
filter.colors().iter().map(|&x| x.into()),
|
||||||
for (i, item) in filter.colors().iter().copied().enumerate() {
|
)
|
||||||
array
|
.into());
|
||||||
.set_element(activation, i as i32, item.into())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
return Ok(array.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
|
@ -176,16 +172,12 @@ pub fn alphas<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
if let Some(filter) = this.as_gradient_glow_filter_object() {
|
if let Some(filter) = this.as_gradient_glow_filter_object() {
|
||||||
let array = ScriptObject::array(
|
return Ok(ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes.array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
filter.alphas().iter().map(|&x| x.into()),
|
||||||
for (i, item) in filter.alphas().iter().copied().enumerate() {
|
)
|
||||||
array
|
.into());
|
||||||
.set_element(activation, i as i32, item.into())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
return Ok(array.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
|
@ -231,16 +223,12 @@ pub fn ratios<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
if let Some(filter) = this.as_gradient_glow_filter_object() {
|
if let Some(filter) = this.as_gradient_glow_filter_object() {
|
||||||
let array = ScriptObject::array(
|
return Ok(ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes.array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
filter.ratios().iter().map(|&x| x.into()),
|
||||||
for (i, item) in filter.ratios().iter().copied().enumerate() {
|
)
|
||||||
array
|
.into());
|
||||||
.set_element(activation, i as i32, item.into())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
return Ok(array.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::avm1::object::script_object::ScriptObject;
|
||||||
use crate::avm1::object::TObject;
|
use crate::avm1::object::TObject;
|
||||||
use crate::avm1::property::Attribute;
|
use crate::avm1::property::Attribute;
|
||||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||||
use crate::avm1::{Object, Value};
|
use crate::avm1::{ArrayObject, Object, Value};
|
||||||
use crate::backend::navigator::RequestOptions;
|
use crate::backend::navigator::RequestOptions;
|
||||||
use crate::display_object::{DisplayObject, TDisplayObject};
|
use crate::display_object::{DisplayObject, TDisplayObject};
|
||||||
use gc_arena::MutationContext;
|
use gc_arena::MutationContext;
|
||||||
|
@ -23,9 +23,10 @@ pub fn constructor<'gc>(
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
let listeners = ScriptObject::array(
|
let listeners = ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes().array),
|
activation.context.avm1.prototypes().array,
|
||||||
|
[this.into()],
|
||||||
);
|
);
|
||||||
this.define_value(
|
this.define_value(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
|
@ -33,8 +34,6 @@ pub fn constructor<'gc>(
|
||||||
Value::Object(listeners.into()),
|
Value::Object(listeners.into()),
|
||||||
Attribute::DONT_ENUM,
|
Attribute::DONT_ENUM,
|
||||||
);
|
);
|
||||||
listeners.set_element(activation, 0, this.into()).unwrap();
|
|
||||||
|
|
||||||
Ok(this.into())
|
Ok(this.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -141,7 +141,7 @@ fn deserialize_value<'gc>(activation: &mut Activation<'_, 'gc, '_>, val: &AmfVal
|
||||||
let value = deserialize_value(activation, entry.value());
|
let value = deserialize_value(activation, entry.value());
|
||||||
|
|
||||||
if let Ok(i) = entry.name().parse::<i32>() {
|
if let Ok(i) = entry.name().parse::<i32>() {
|
||||||
let _ = obj.set_element(activation, i, value);
|
obj.set_element(activation, i, value).unwrap();
|
||||||
} else {
|
} else {
|
||||||
obj.define_value(
|
obj.define_value(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
|
@ -291,7 +291,7 @@ fn deserialize_array_json<'gc>(
|
||||||
for entry in json_obj.iter() {
|
for entry in json_obj.iter() {
|
||||||
let value = recursive_deserialize_json(entry.1.clone(), activation);
|
let value = recursive_deserialize_json(entry.1.clone(), activation);
|
||||||
if let Ok(i) = entry.0.parse::<i32>() {
|
if let Ok(i) = entry.0.parse::<i32>() {
|
||||||
let _ = obj.set_element(activation, i, value);
|
obj.set_element(activation, i, value).unwrap();
|
||||||
} else {
|
} else {
|
||||||
obj.define_value(
|
obj.define_value(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::avm1::function::{Executable, FunctionObject};
|
||||||
use crate::avm1::object::value_object::ValueObject;
|
use crate::avm1::object::value_object::ValueObject;
|
||||||
use crate::avm1::property::Attribute;
|
use crate::avm1::property::Attribute;
|
||||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||||
use crate::avm1::{AvmString, Object, ScriptObject, TObject, Value};
|
use crate::avm1::{ArrayObject, AvmString, Object, TObject, Value};
|
||||||
use crate::string_utils;
|
use crate::string_utils;
|
||||||
use gc_arena::MutationContext;
|
use gc_arena::MutationContext;
|
||||||
|
|
||||||
|
@ -304,44 +304,38 @@ fn split<'gc>(
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
let this_val = Value::from(this);
|
let this = Value::from(this).coerce_to_string(activation)?;
|
||||||
let this = this_val.coerce_to_string(activation)?;
|
let delimiter = args
|
||||||
let delimiter_val = args.get(0).unwrap_or(&Value::Undefined);
|
.get(0)
|
||||||
let delimiter = delimiter_val.coerce_to_string(activation)?;
|
.unwrap_or(&Value::Undefined)
|
||||||
let limit = match args.get(1) {
|
.coerce_to_string(activation)?;
|
||||||
None | Some(Value::Undefined) => usize::MAX,
|
let limit = match args.get(1).unwrap_or(&Value::Undefined) {
|
||||||
Some(n) => std::cmp::max(0, n.coerce_to_i32(activation)?) as usize,
|
Value::Undefined => usize::MAX,
|
||||||
|
limit => limit.coerce_to_i32(activation)?.max(0) as usize,
|
||||||
};
|
};
|
||||||
let array = ScriptObject::array(
|
if delimiter.is_empty() {
|
||||||
activation.context.gc_context,
|
// When using an empty delimiter, Rust's str::split adds an extra beginning and trailing item, but Flash does not.
|
||||||
Some(activation.context.avm1.prototypes.array),
|
|
||||||
);
|
|
||||||
if !delimiter.is_empty() {
|
|
||||||
for (i, token) in this.split(delimiter.as_ref()).take(limit).enumerate() {
|
|
||||||
array
|
|
||||||
.set_element(
|
|
||||||
activation,
|
|
||||||
i as i32,
|
|
||||||
AvmString::new(activation.context.gc_context, token.to_string()).into(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// When using an empty "" delimiter, Rust's str::split adds an extra beginning and trailing item, but Flash does not.
|
|
||||||
// e.g., split("foo", "") returns ["", "f", "o", "o", ""] in Rust but ["f, "o", "o"] in Flash.
|
// e.g., split("foo", "") returns ["", "f", "o", "o", ""] in Rust but ["f, "o", "o"] in Flash.
|
||||||
// Special case this to match Flash's behavior.
|
// Special case this to match Flash's behavior.
|
||||||
for (i, token) in this.chars().take(limit).enumerate() {
|
Ok(ArrayObject::new(
|
||||||
array
|
activation.context.gc_context,
|
||||||
.set_element(
|
activation.context.avm1.prototypes().array,
|
||||||
activation,
|
this.chars()
|
||||||
i as i32,
|
.take(limit)
|
||||||
AvmString::new(activation.context.gc_context, token.to_string()).into(),
|
.map(|c| AvmString::new(activation.context.gc_context, c.to_string()).into()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.into())
|
||||||
|
} else {
|
||||||
|
Ok(ArrayObject::new(
|
||||||
|
activation.context.gc_context,
|
||||||
|
activation.context.avm1.prototypes().array,
|
||||||
|
this.split(delimiter.as_ref())
|
||||||
|
.take(limit)
|
||||||
|
.map(|c| AvmString::new(activation.context.gc_context, c.to_string()).into()),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(array.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn substr<'gc>(
|
fn substr<'gc>(
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
|
|
@ -2,10 +2,9 @@
|
||||||
|
|
||||||
use crate::avm1::activation::Activation;
|
use crate::avm1::activation::Activation;
|
||||||
use crate::avm1::error::Error;
|
use crate::avm1::error::Error;
|
||||||
use crate::avm1::object::script_object::ScriptObject;
|
|
||||||
use crate::avm1::object::xml_object::XmlObject;
|
use crate::avm1::object::xml_object::XmlObject;
|
||||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||||
use crate::avm1::{AvmString, Object, TObject, Value};
|
use crate::avm1::{ArrayObject, AvmString, Object, TObject, Value};
|
||||||
use crate::avm_warn;
|
use crate::avm_warn;
|
||||||
use crate::backend::navigator::RequestOptions;
|
use crate::backend::navigator::RequestOptions;
|
||||||
use crate::xml;
|
use crate::xml;
|
||||||
|
@ -73,7 +72,7 @@ const XML_PROTO_DECLS: &[Declaration] = declare_properties! {
|
||||||
/// not. Those nodes are filtered from all attributes that return XML nodes to
|
/// not. Those nodes are filtered from all attributes that return XML nodes to
|
||||||
/// act as if those nodes did not exist. For example, `prevSibling` skips
|
/// act as if those nodes did not exist. For example, `prevSibling` skips
|
||||||
/// past incompatible nodes, etc.
|
/// past incompatible nodes, etc.
|
||||||
fn is_as2_compatible(node: XmlNode<'_>) -> bool {
|
fn is_as2_compatible(node: &XmlNode<'_>) -> bool {
|
||||||
node.is_document_root() || node.is_element() || node.is_text()
|
node.is_document_root() || node.is_element() || node.is_text()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,34 +356,19 @@ pub fn xmlnode_child_nodes<'gc>(
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
if let Some(node) = this.as_xml_node() {
|
if let Some(node) = this.as_xml_node() {
|
||||||
let array = ScriptObject::array(
|
return Ok(ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes.array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
node.children().filter(is_as2_compatible).map(|mut child| {
|
||||||
|
|
||||||
let mut compatible_nodes = 0;
|
|
||||||
for mut child in node.children() {
|
|
||||||
if !is_as2_compatible(child) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
array
|
|
||||||
.set_element(
|
|
||||||
activation,
|
|
||||||
compatible_nodes,
|
|
||||||
child
|
child
|
||||||
.script_object(
|
.script_object(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes.xml_node),
|
Some(activation.context.avm1.prototypes.xml_node),
|
||||||
)
|
)
|
||||||
.into(),
|
.into()
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.into());
|
||||||
|
|
||||||
compatible_nodes += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(array.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
|
@ -399,7 +383,7 @@ pub fn xmlnode_first_child<'gc>(
|
||||||
let mut children = node.children();
|
let mut children = node.children();
|
||||||
let mut next = children.next();
|
let mut next = children.next();
|
||||||
while let Some(my_next) = next {
|
while let Some(my_next) = next {
|
||||||
if is_as2_compatible(my_next) {
|
if is_as2_compatible(&my_next) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,7 +414,7 @@ pub fn xmlnode_last_child<'gc>(
|
||||||
let mut children = node.children();
|
let mut children = node.children();
|
||||||
let mut prev = children.next_back();
|
let mut prev = children.next_back();
|
||||||
while let Some(my_prev) = prev {
|
while let Some(my_prev) = prev {
|
||||||
if is_as2_compatible(my_prev) {
|
if is_as2_compatible(&my_prev) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,7 +465,7 @@ pub fn xmlnode_previous_sibling<'gc>(
|
||||||
if let Some(node) = this.as_xml_node() {
|
if let Some(node) = this.as_xml_node() {
|
||||||
let mut prev = node.prev_sibling();
|
let mut prev = node.prev_sibling();
|
||||||
while let Some(my_prev) = prev {
|
while let Some(my_prev) = prev {
|
||||||
if is_as2_compatible(my_prev) {
|
if is_as2_compatible(&my_prev) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,7 +494,7 @@ pub fn xmlnode_next_sibling<'gc>(
|
||||||
if let Some(node) = this.as_xml_node() {
|
if let Some(node) = this.as_xml_node() {
|
||||||
let mut next = node.next_sibling();
|
let mut next = node.next_sibling();
|
||||||
while let Some(my_next) = next {
|
while let Some(my_next) = next {
|
||||||
if is_as2_compatible(my_next) {
|
if is_as2_compatible(&my_next) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ use crate::avm1::object::value_object::ValueObject;
|
||||||
use crate::avm1::property::Attribute;
|
use crate::avm1::property::Attribute;
|
||||||
|
|
||||||
use crate::avm1::activation::Activation;
|
use crate::avm1::activation::Activation;
|
||||||
|
use crate::avm1::object::array_object::ArrayObject;
|
||||||
use crate::avm1::object::bevel_filter::BevelFilterObject;
|
use crate::avm1::object::bevel_filter::BevelFilterObject;
|
||||||
use crate::avm1::object::bitmap_data::BitmapDataObject;
|
use crate::avm1::object::bitmap_data::BitmapDataObject;
|
||||||
use crate::avm1::object::blur_filter::BlurFilterObject;
|
use crate::avm1::object::blur_filter::BlurFilterObject;
|
||||||
|
@ -33,6 +34,7 @@ use ruffle_macros::enum_trait_object;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
pub mod array_object;
|
||||||
pub mod bevel_filter;
|
pub mod bevel_filter;
|
||||||
pub mod bitmap_data;
|
pub mod bitmap_data;
|
||||||
pub mod blur_filter;
|
pub mod blur_filter;
|
||||||
|
@ -65,6 +67,7 @@ pub mod xml_object;
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub enum Object<'gc> {
|
pub enum Object<'gc> {
|
||||||
ScriptObject(ScriptObject<'gc>),
|
ScriptObject(ScriptObject<'gc>),
|
||||||
|
ArrayObject(ArrayObject<'gc>),
|
||||||
SoundObject(SoundObject<'gc>),
|
SoundObject(SoundObject<'gc>),
|
||||||
StageObject(StageObject<'gc>),
|
StageObject(StageObject<'gc>),
|
||||||
SuperObject(SuperObject<'gc>),
|
SuperObject(SuperObject<'gc>),
|
||||||
|
@ -144,22 +147,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(index) = name.parse::<i32>() {
|
|
||||||
return self.set_element(activation, index, value.to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
if name == "length" {
|
|
||||||
let length = value
|
|
||||||
.coerce_to_f64(activation)
|
|
||||||
.map(|v| v.abs() as i32)
|
|
||||||
.unwrap_or(0);
|
|
||||||
if length > 0 {
|
|
||||||
self.set_length(activation, length)?;
|
|
||||||
} else {
|
|
||||||
self.set_length(activation, 0)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let this = (*self).into();
|
let this = (*self).into();
|
||||||
if !self.has_own_property(activation, name) {
|
if !self.has_own_property(activation, name) {
|
||||||
// Before actually inserting a new property, we need to crawl the
|
// Before actually inserting a new property, we need to crawl the
|
||||||
|
@ -460,6 +447,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
||||||
/// Get the underlying script object, if it exists.
|
/// Get the underlying script object, if it exists.
|
||||||
fn as_script_object(&self) -> Option<ScriptObject<'gc>>;
|
fn as_script_object(&self) -> Option<ScriptObject<'gc>>;
|
||||||
|
|
||||||
|
/// Get the underlying array object, if it exists.
|
||||||
|
fn as_array_object(&self) -> Option<ArrayObject<'gc>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the underlying sound object, if it exists.
|
/// Get the underlying sound object, if it exists.
|
||||||
fn as_sound_object(&self) -> Option<SoundObject<'gc>> {
|
fn as_sound_object(&self) -> Option<SoundObject<'gc>> {
|
||||||
None
|
None
|
||||||
|
|
|
@ -0,0 +1,281 @@
|
||||||
|
use crate::avm1::property::Attribute;
|
||||||
|
use crate::avm1::{Activation, Error, Object, ObjectPtr, ScriptObject, TObject, Value};
|
||||||
|
use gc_arena::{Collect, GcCell, MutationContext};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Collect)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
pub struct ArrayObject<'gc>(GcCell<'gc, ScriptObject<'gc>>);
|
||||||
|
|
||||||
|
impl fmt::Debug for ArrayObject<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("ArrayObject").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> ArrayObject<'gc> {
|
||||||
|
pub fn empty(activation: &Activation<'_, 'gc, '_>) -> Self {
|
||||||
|
Self::new(
|
||||||
|
activation.context.gc_context,
|
||||||
|
activation.context.avm1.prototypes().array,
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty_with_proto(
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
proto: Option<Object<'gc>>,
|
||||||
|
) -> Self {
|
||||||
|
Self::new_internal(gc_context, proto, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
array_proto: Object<'gc>,
|
||||||
|
elements: impl IntoIterator<Item = Value<'gc>>,
|
||||||
|
) -> Self {
|
||||||
|
Self::new_internal(gc_context, Some(array_proto), elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_internal(
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
proto: Option<Object<'gc>>,
|
||||||
|
elements: impl IntoIterator<Item = Value<'gc>>,
|
||||||
|
) -> Self {
|
||||||
|
let base = ScriptObject::object(gc_context, proto);
|
||||||
|
let mut length: i32 = 0;
|
||||||
|
for value in elements.into_iter() {
|
||||||
|
base.define_value(gc_context, &length.to_string(), value, Attribute::empty());
|
||||||
|
length += 1;
|
||||||
|
}
|
||||||
|
base.define_value(
|
||||||
|
gc_context,
|
||||||
|
"length",
|
||||||
|
length.into(),
|
||||||
|
Attribute::DONT_ENUM | Attribute::DONT_DELETE,
|
||||||
|
);
|
||||||
|
Self(GcCell::allocate(gc_context, base))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> TObject<'gc> for ArrayObject<'gc> {
|
||||||
|
fn get_local(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Object<'gc>,
|
||||||
|
) -> Option<Result<Value<'gc>, Error<'gc>>> {
|
||||||
|
self.0.read().get_local(name, activation, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_local(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
value: Value<'gc>,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Object<'gc>,
|
||||||
|
base_proto: Option<Object<'gc>>,
|
||||||
|
) -> Result<(), Error<'gc>> {
|
||||||
|
if name == "length" {
|
||||||
|
let new_length = value.coerce_to_i32(activation)?;
|
||||||
|
self.set_length(activation, new_length)?;
|
||||||
|
} else if let Ok(index) = name.parse::<i32>() {
|
||||||
|
let length = self.length(activation)?;
|
||||||
|
if index >= length {
|
||||||
|
self.set_length(activation, index.wrapping_add(1))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.0
|
||||||
|
.read()
|
||||||
|
.set_local(name, value, activation, this, base_proto)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Object<'gc>,
|
||||||
|
base_proto: Option<Object<'gc>>,
|
||||||
|
args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
|
self.0.read().call(name, activation, this, base_proto, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_setter(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
value: Value<'gc>,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
) -> Option<Object<'gc>> {
|
||||||
|
self.0.read().call_setter(name, value, activation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_bare_object(
|
||||||
|
&self,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Object<'gc>,
|
||||||
|
) -> Result<Object<'gc>, Error<'gc>> {
|
||||||
|
Ok(Self::empty_with_proto(activation.context.gc_context, Some(this)).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
|
||||||
|
self.0.read().delete(activation, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_property(
|
||||||
|
&self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: &str,
|
||||||
|
get: Object<'gc>,
|
||||||
|
set: Option<Object<'gc>>,
|
||||||
|
attributes: Attribute,
|
||||||
|
) {
|
||||||
|
self.0
|
||||||
|
.read()
|
||||||
|
.add_property(gc_context, name, get, set, attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_property_with_case(
|
||||||
|
&self,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
name: &str,
|
||||||
|
get: Object<'gc>,
|
||||||
|
set: Option<Object<'gc>>,
|
||||||
|
attributes: Attribute,
|
||||||
|
) {
|
||||||
|
self.0
|
||||||
|
.read()
|
||||||
|
.add_property_with_case(activation, name, get, set, attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_watcher(
|
||||||
|
&self,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
name: Cow<str>,
|
||||||
|
callback: Object<'gc>,
|
||||||
|
user_data: Value<'gc>,
|
||||||
|
) {
|
||||||
|
self.0
|
||||||
|
.read()
|
||||||
|
.set_watcher(activation, name, callback, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_watcher(&self, activation: &mut Activation<'_, 'gc, '_>, name: Cow<str>) -> bool {
|
||||||
|
self.0.read().remove_watcher(activation, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn define_value(
|
||||||
|
&self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: &str,
|
||||||
|
value: Value<'gc>,
|
||||||
|
attributes: Attribute,
|
||||||
|
) {
|
||||||
|
self.0
|
||||||
|
.read()
|
||||||
|
.define_value(gc_context, name, value, attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_attributes(
|
||||||
|
&self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Option<&str>,
|
||||||
|
set_attributes: Attribute,
|
||||||
|
clear_attributes: Attribute,
|
||||||
|
) {
|
||||||
|
self.0
|
||||||
|
.read()
|
||||||
|
.set_attributes(gc_context, name, set_attributes, clear_attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proto(&self) -> Value<'gc> {
|
||||||
|
self.0.read().proto()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_proto(&self, gc_context: MutationContext<'gc, '_>, prototype: Value<'gc>) {
|
||||||
|
self.0.read().set_proto(gc_context, prototype);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_property(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
|
||||||
|
self.0.read().has_property(activation, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_own_property(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
|
||||||
|
self.0.read().has_own_property(activation, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_own_virtual(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
|
||||||
|
self.0.read().has_own_virtual(activation, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_property_enumerable(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
|
||||||
|
self.0.read().is_property_enumerable(activation, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_keys(&self, activation: &mut Activation<'_, 'gc, '_>) -> Vec<String> {
|
||||||
|
self.0.read().get_keys(activation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_of(&self) -> &'static str {
|
||||||
|
self.0.read().type_of()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interfaces(&self) -> Vec<Object<'gc>> {
|
||||||
|
self.0.read().interfaces()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_interfaces(&self, gc_context: MutationContext<'gc, '_>, iface_list: Vec<Object<'gc>>) {
|
||||||
|
self.0.read().set_interfaces(gc_context, iface_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_script_object(&self) -> Option<ScriptObject<'gc>> {
|
||||||
|
Some(*self.0.read())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_array_object(&self) -> Option<ArrayObject<'gc>> {
|
||||||
|
Some(*self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_ptr(&self) -> *const ObjectPtr {
|
||||||
|
self.0.read().as_ptr() as *const ObjectPtr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn length(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
|
||||||
|
self.0.read().length(activation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_length(
|
||||||
|
&self,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
length: i32,
|
||||||
|
) -> Result<(), Error<'gc>> {
|
||||||
|
self.0.read().set_length(activation, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
|
||||||
|
self.0.read().has_element(activation, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> Value<'gc> {
|
||||||
|
self.0.read().get_element(activation, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_element(
|
||||||
|
&self,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
index: i32,
|
||||||
|
value: Value<'gc>,
|
||||||
|
) -> Result<(), Error<'gc>> {
|
||||||
|
let length = self.length(activation)?;
|
||||||
|
if index >= length {
|
||||||
|
self.set_length(activation, index.wrapping_add(1))?;
|
||||||
|
}
|
||||||
|
self.0.read().set_element(activation, index, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
|
||||||
|
self.0.read().delete_element(activation, index)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,19 +4,13 @@ use crate::avm1::function::ExecutionReason;
|
||||||
use crate::avm1::property::{Attribute, Property};
|
use crate::avm1::property::{Attribute, Property};
|
||||||
use crate::avm1::property_map::{Entry, PropertyMap};
|
use crate::avm1::property_map::{Entry, PropertyMap};
|
||||||
use crate::avm1::{AvmString, Object, ObjectPtr, TObject, Value};
|
use crate::avm1::{AvmString, Object, ObjectPtr, TObject, Value};
|
||||||
|
use crate::ecma_conversions::f64_to_wrapping_i32;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use gc_arena::{Collect, GcCell, MutationContext};
|
use gc_arena::{Collect, GcCell, MutationContext};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub const TYPE_OF_OBJECT: &str = "object";
|
pub const TYPE_OF_OBJECT: &str = "object";
|
||||||
|
|
||||||
#[derive(Debug, Clone, Collect)]
|
|
||||||
#[collect(no_drop)]
|
|
||||||
pub enum ArrayStorage<'gc> {
|
|
||||||
Vector(Vec<Value<'gc>>),
|
|
||||||
Properties { length: i32 },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Collect)]
|
#[derive(Debug, Clone, Collect)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub struct Watcher<'gc> {
|
pub struct Watcher<'gc> {
|
||||||
|
@ -32,7 +26,6 @@ impl<'gc> Watcher<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn call(
|
pub fn call(
|
||||||
&self,
|
&self,
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
@ -78,7 +71,6 @@ pub struct ScriptObjectData<'gc> {
|
||||||
values: PropertyMap<Property<'gc>>,
|
values: PropertyMap<Property<'gc>>,
|
||||||
interfaces: Vec<Object<'gc>>,
|
interfaces: Vec<Object<'gc>>,
|
||||||
type_of: &'static str,
|
type_of: &'static str,
|
||||||
array: ArrayStorage<'gc>,
|
|
||||||
watchers: PropertyMap<Watcher<'gc>>,
|
watchers: PropertyMap<Watcher<'gc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +79,6 @@ impl fmt::Debug for ScriptObjectData<'_> {
|
||||||
f.debug_struct("Object")
|
f.debug_struct("Object")
|
||||||
.field("prototype", &self.prototype)
|
.field("prototype", &self.prototype)
|
||||||
.field("values", &self.values)
|
.field("values", &self.values)
|
||||||
.field("array", &self.array)
|
|
||||||
.field("watchers", &self.watchers)
|
.field("watchers", &self.watchers)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
@ -104,32 +95,12 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
prototype: proto.map_or(Value::Undefined, Value::Object),
|
prototype: proto.map_or(Value::Undefined, Value::Object),
|
||||||
type_of: TYPE_OF_OBJECT,
|
type_of: TYPE_OF_OBJECT,
|
||||||
values: PropertyMap::new(),
|
values: PropertyMap::new(),
|
||||||
array: ArrayStorage::Properties { length: 0 },
|
|
||||||
interfaces: vec![],
|
interfaces: vec![],
|
||||||
watchers: PropertyMap::new(),
|
watchers: PropertyMap::new(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn array(
|
|
||||||
gc_context: MutationContext<'gc, '_>,
|
|
||||||
proto: Option<Object<'gc>>,
|
|
||||||
) -> ScriptObject<'gc> {
|
|
||||||
let object = ScriptObject(GcCell::allocate(
|
|
||||||
gc_context,
|
|
||||||
ScriptObjectData {
|
|
||||||
prototype: proto.map_or(Value::Undefined, Value::Object),
|
|
||||||
type_of: TYPE_OF_OBJECT,
|
|
||||||
values: PropertyMap::new(),
|
|
||||||
array: ArrayStorage::Vector(Vec::new()),
|
|
||||||
interfaces: vec![],
|
|
||||||
watchers: PropertyMap::new(),
|
|
||||||
},
|
|
||||||
));
|
|
||||||
object.sync_native_property("length", gc_context, Some(0.into()), false);
|
|
||||||
object
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Constructs and allocates an empty but normal object in one go.
|
/// Constructs and allocates an empty but normal object in one go.
|
||||||
pub fn object_cell(
|
pub fn object_cell(
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
@ -141,7 +112,6 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
prototype: proto.map_or(Value::Undefined, Value::Object),
|
prototype: proto.map_or(Value::Undefined, Value::Object),
|
||||||
type_of: TYPE_OF_OBJECT,
|
type_of: TYPE_OF_OBJECT,
|
||||||
values: PropertyMap::new(),
|
values: PropertyMap::new(),
|
||||||
array: ArrayStorage::Properties { length: 0 },
|
|
||||||
interfaces: vec![],
|
interfaces: vec![],
|
||||||
watchers: PropertyMap::new(),
|
watchers: PropertyMap::new(),
|
||||||
},
|
},
|
||||||
|
@ -161,7 +131,6 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
prototype: Value::Undefined,
|
prototype: Value::Undefined,
|
||||||
type_of: TYPE_OF_OBJECT,
|
type_of: TYPE_OF_OBJECT,
|
||||||
values: PropertyMap::new(),
|
values: PropertyMap::new(),
|
||||||
array: ArrayStorage::Properties { length: 0 },
|
|
||||||
interfaces: vec![],
|
interfaces: vec![],
|
||||||
watchers: PropertyMap::new(),
|
watchers: PropertyMap::new(),
|
||||||
},
|
},
|
||||||
|
@ -172,52 +141,56 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
self.0.write(gc_context).type_of = type_of;
|
self.0.write(gc_context).type_of = type_of;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
/// Gets the value of a data property on this object.
|
||||||
pub fn sync_native_property(
|
///
|
||||||
|
/// Doesn't look up the prototype chain and ignores virtual properties, thus cannot cause
|
||||||
|
/// any side-effects.
|
||||||
|
fn get_data(&self, name: &str, activation: &mut Activation<'_, 'gc, '_>) -> Value<'gc> {
|
||||||
|
if let Some(Property::Stored { value, .. }) = self
|
||||||
|
.0
|
||||||
|
.read()
|
||||||
|
.values
|
||||||
|
.get(name, activation.is_case_sensitive())
|
||||||
|
{
|
||||||
|
value.to_owned()
|
||||||
|
} else {
|
||||||
|
Value::Undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a data property on this object.
|
||||||
|
///
|
||||||
|
/// Doesn't look up the prototype chain and ignores virtual properties, but still might
|
||||||
|
/// call to watchers.
|
||||||
|
fn set_data(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
gc_context: MutationContext<'gc, '_>,
|
value: Value<'gc>,
|
||||||
native_value: Option<Value<'gc>>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
is_enumerable: bool,
|
) -> Result<(), Error<'gc>> {
|
||||||
) {
|
// TODO: Call watchers.
|
||||||
match self.0.write(gc_context).values.entry(name, false) {
|
match self
|
||||||
|
.0
|
||||||
|
.write(activation.context.gc_context)
|
||||||
|
.values
|
||||||
|
.entry(name, activation.is_case_sensitive())
|
||||||
|
{
|
||||||
Entry::Occupied(mut entry) => {
|
Entry::Occupied(mut entry) => {
|
||||||
if let Property::Stored { value, .. } = entry.get_mut() {
|
entry.get_mut().set(value);
|
||||||
match native_value {
|
|
||||||
None => {
|
|
||||||
entry.remove_entry();
|
|
||||||
}
|
|
||||||
Some(native_value) => {
|
|
||||||
*value = native_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
if let Some(native_value) = native_value {
|
|
||||||
entry.insert(Property::Stored {
|
entry.insert(Property::Stored {
|
||||||
value: native_value,
|
value,
|
||||||
attributes: if is_enumerable {
|
attributes: Attribute::empty(),
|
||||||
Attribute::empty()
|
|
||||||
} else {
|
|
||||||
Attribute::DONT_ENUM
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||||
/// Get the value of a particular property on this object.
|
/// Get the value of a particular property on this object.
|
||||||
///
|
|
||||||
/// The `avm`, `context`, and `this` parameters exist so that this object
|
|
||||||
/// can call virtual properties. Furthermore, since some virtual properties
|
|
||||||
/// may resolve on the AVM stack, this function may return `None` instead
|
|
||||||
/// of a `Value`. *This is not equivalent to `undefined`.* Instead, it is a
|
|
||||||
/// signal that your value will be returned on the ActionScript stack, and
|
|
||||||
/// that you should register a stack continuation in order to get it.
|
|
||||||
fn get_local(
|
fn get_local(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
@ -360,28 +333,24 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
) -> Result<Object<'gc>, Error<'gc>> {
|
) -> Result<Object<'gc>, Error<'gc>> {
|
||||||
match self.0.read().array {
|
|
||||||
ArrayStorage::Vector(_) => {
|
|
||||||
Ok(ScriptObject::array(activation.context.gc_context, Some(this)).into())
|
|
||||||
}
|
|
||||||
ArrayStorage::Properties { .. } => {
|
|
||||||
Ok(ScriptObject::object(activation.context.gc_context, Some(this)).into())
|
Ok(ScriptObject::object(activation.context.gc_context, Some(this)).into())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete a named property from the object.
|
/// Delete a named property from the object.
|
||||||
///
|
///
|
||||||
/// Returns false if the property cannot be deleted.
|
/// Returns false if the property cannot be deleted.
|
||||||
fn delete(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
|
fn delete(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
|
||||||
let mut object = self.0.write(activation.context.gc_context);
|
if let Entry::Occupied(mut entry) = self
|
||||||
if let Some(prop) = object.values.get(name, activation.is_case_sensitive()) {
|
.0
|
||||||
if prop.can_delete() {
|
.write(activation.context.gc_context)
|
||||||
object.values.remove(name, activation.is_case_sensitive());
|
.values
|
||||||
|
.entry(name, activation.is_case_sensitive())
|
||||||
|
{
|
||||||
|
if entry.get().can_delete() {
|
||||||
|
entry.remove_entry();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -589,11 +558,9 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||||
self.0.as_ptr() as *const ObjectPtr
|
self.0.as_ptr() as *const ObjectPtr
|
||||||
}
|
}
|
||||||
|
|
||||||
fn length(&self, _activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
|
fn length(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
|
||||||
match &self.0.read().array {
|
self.get_data("length", activation)
|
||||||
ArrayStorage::Vector(vector) => Ok(vector.len() as i32),
|
.coerce_to_i32(activation)
|
||||||
ArrayStorage::Properties { length } => Ok(*length),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_length(
|
fn set_length(
|
||||||
|
@ -601,66 +568,20 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
new_length: i32,
|
new_length: i32,
|
||||||
) -> Result<(), Error<'gc>> {
|
) -> Result<(), Error<'gc>> {
|
||||||
let to_remove = match &mut self.0.write(activation.context.gc_context).array {
|
if let Value::Number(old_length) = self.get_data("length", activation) {
|
||||||
ArrayStorage::Vector(vector) => {
|
for i in new_length.max(0)..f64_to_wrapping_i32(old_length) {
|
||||||
if new_length >= 0 {
|
self.delete_element(activation, i);
|
||||||
let old_length = vector.len();
|
|
||||||
let new_length = new_length as usize;
|
|
||||||
vector.resize(new_length, Value::Undefined);
|
|
||||||
Some(new_length..old_length)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ArrayStorage::Properties { length } => {
|
self.set_data("length", new_length.into(), activation)
|
||||||
*length = new_length;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(to_remove) = to_remove {
|
|
||||||
for i in to_remove {
|
|
||||||
self.sync_native_property(
|
|
||||||
&i.to_string(),
|
|
||||||
activation.context.gc_context,
|
|
||||||
None,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.sync_native_property(
|
|
||||||
"length",
|
|
||||||
activation.context.gc_context,
|
|
||||||
Some(new_length.into()),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
|
fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
|
||||||
self.has_own_property(activation, &index.to_string())
|
self.has_own_property(activation, &index.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_element(&self, _activation: &mut Activation<'_, 'gc, '_>, index: i32) -> Value<'gc> {
|
fn get_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> Value<'gc> {
|
||||||
match &self.0.read().array {
|
self.get_data(&index.to_string(), activation)
|
||||||
ArrayStorage::Vector(vector) => {
|
|
||||||
let index = index as usize;
|
|
||||||
if let Some(value) = vector.get(index) {
|
|
||||||
value.to_owned()
|
|
||||||
} else {
|
|
||||||
Value::Undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ArrayStorage::Properties { length } => {
|
|
||||||
if index < *length {
|
|
||||||
if let Some(Property::Stored { value, .. }) =
|
|
||||||
self.0.read().values.get(&index.to_string(), false)
|
|
||||||
{
|
|
||||||
return value.to_owned();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_element(
|
fn set_element(
|
||||||
|
@ -669,54 +590,11 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||||
index: i32,
|
index: i32,
|
||||||
value: Value<'gc>,
|
value: Value<'gc>,
|
||||||
) -> Result<(), Error<'gc>> {
|
) -> Result<(), Error<'gc>> {
|
||||||
self.sync_native_property(
|
self.set_data(&index.to_string(), value, activation)
|
||||||
&index.to_string(),
|
|
||||||
activation.context.gc_context,
|
|
||||||
Some(value),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
let length = match &mut self.0.write(activation.context.gc_context).array {
|
|
||||||
ArrayStorage::Vector(vector) => {
|
|
||||||
if index >= 0 {
|
|
||||||
let index = index as usize;
|
|
||||||
if index >= vector.len() {
|
|
||||||
vector.resize(index + 1, Value::Undefined);
|
|
||||||
}
|
|
||||||
vector[index] = value;
|
|
||||||
Some(vector.len())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ArrayStorage::Properties { length: _ } => {
|
|
||||||
// TODO: Support Array-like case?
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(length) = length {
|
|
||||||
self.sync_native_property(
|
|
||||||
"length",
|
|
||||||
activation.context.gc_context,
|
|
||||||
Some(length.into()),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
|
fn delete_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
|
||||||
match &mut self.0.write(activation.context.gc_context).array {
|
self.delete(activation, &index.to_string())
|
||||||
ArrayStorage::Vector(vector) => {
|
|
||||||
let index = index as usize;
|
|
||||||
if index < vector.len() {
|
|
||||||
vector[index] = Value::Undefined;
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ArrayStorage::Properties { length: _ } => self.delete(activation, &index.to_string()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,8 @@ impl<V> PropertyMap<V> {
|
||||||
|
|
||||||
pub fn entry<'a>(&'a mut self, key: &'a str, case_sensitive: bool) -> Entry<'a, V> {
|
pub fn entry<'a>(&'a mut self, key: &'a str, case_sensitive: bool) -> Entry<'a, V> {
|
||||||
if case_sensitive {
|
if case_sensitive {
|
||||||
match self.0.get_full_mut(&CaseSensitiveStr(key)) {
|
match self.0.get_index_of(&CaseSensitiveStr(key)) {
|
||||||
Some((index, _, _)) => Entry::Occupied(OccupiedEntry {
|
Some(index) => Entry::Occupied(OccupiedEntry {
|
||||||
map: &mut self.0,
|
map: &mut self.0,
|
||||||
index,
|
index,
|
||||||
}),
|
}),
|
||||||
|
@ -42,8 +42,8 @@ impl<V> PropertyMap<V> {
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match self.0.get_full_mut(&CaseInsensitiveStr(key)) {
|
match self.0.get_index_of(&CaseInsensitiveStr(key)) {
|
||||||
Some((index, _, _)) => Entry::Occupied(OccupiedEntry {
|
Some(index) => Entry::Occupied(OccupiedEntry {
|
||||||
map: &mut self.0,
|
map: &mut self.0,
|
||||||
index,
|
index,
|
||||||
}),
|
}),
|
||||||
|
@ -65,7 +65,6 @@ impl<V> PropertyMap<V> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a mutable reference to the value for the specified property.
|
/// Gets a mutable reference to the value for the specified property.
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn get_mut(&mut self, key: &str, case_sensitive: bool) -> Option<&mut V> {
|
pub fn get_mut(&mut self, key: &str, case_sensitive: bool) -> Option<&mut V> {
|
||||||
if case_sensitive {
|
if case_sensitive {
|
||||||
self.0.get_mut(&CaseSensitiveStr(key))
|
self.0.get_mut(&CaseSensitiveStr(key))
|
||||||
|
@ -133,6 +132,10 @@ impl<'a, V> OccupiedEntry<'a, V> {
|
||||||
(k.0, v)
|
(k.0, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> &V {
|
||||||
|
self.map.get_index(self.index).unwrap().1
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self) -> &mut V {
|
pub fn get_mut(&mut self) -> &mut V {
|
||||||
self.map.get_index_mut(self.index).unwrap().1
|
self.map.get_index_mut(self.index).unwrap().1
|
||||||
}
|
}
|
||||||
|
|
|
@ -1321,7 +1321,7 @@ impl<'gc> EditText<'gc> {
|
||||||
if matches!(length, Ok(0)) {
|
if matches!(length, Ok(0)) {
|
||||||
// Add the TextField as its own listener to match Flash's behavior
|
// Add the TextField as its own listener to match Flash's behavior
|
||||||
// This makes it so that the TextField's handlers are called before other listeners'.
|
// This makes it so that the TextField's handlers are called before other listeners'.
|
||||||
let _ = listeners.set_element(activation, 0, object.into());
|
listeners.set_element(activation, 0, object.into()).unwrap();
|
||||||
} else {
|
} else {
|
||||||
log::warn!("_listeners should be empty");
|
log::warn!("_listeners should be empty");
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@ use crate::avm1::activation::{
|
||||||
use crate::avm1::object::TObject;
|
use crate::avm1::object::TObject;
|
||||||
use crate::avm1::Value as Avm1Value;
|
use crate::avm1::Value as Avm1Value;
|
||||||
use crate::avm1::{
|
use crate::avm1::{
|
||||||
AvmString as Avm1String, Object as Avm1Object, ScriptObject as Avm1ScriptObject,
|
ArrayObject as Avm1ArrayObject, AvmString as Avm1String, Error as Avm1Error,
|
||||||
|
Object as Avm1Object, ScriptObject as Avm1ScriptObject,
|
||||||
};
|
};
|
||||||
use crate::context::UpdateContext;
|
use crate::context::UpdateContext;
|
||||||
use gc_arena::Collect;
|
use gc_arena::Collect;
|
||||||
|
@ -117,22 +118,16 @@ impl Value {
|
||||||
pub fn from_avm1<'gc>(
|
pub fn from_avm1<'gc>(
|
||||||
activation: &mut Avm1Activation<'_, 'gc, '_>,
|
activation: &mut Avm1Activation<'_, 'gc, '_>,
|
||||||
value: Avm1Value<'gc>,
|
value: Avm1Value<'gc>,
|
||||||
) -> Result<Value, crate::avm1::error::Error<'gc>> {
|
) -> Result<Value, Avm1Error<'gc>> {
|
||||||
Ok(match value {
|
Ok(match value {
|
||||||
Avm1Value::Undefined | Avm1Value::Null => Value::Null,
|
Avm1Value::Undefined | Avm1Value::Null => Value::Null,
|
||||||
Avm1Value::Bool(value) => Value::Bool(value),
|
Avm1Value::Bool(value) => Value::Bool(value),
|
||||||
Avm1Value::Number(value) => Value::Number(value),
|
Avm1Value::Number(value) => Value::Number(value),
|
||||||
Avm1Value::String(value) => Value::String(value.to_string()),
|
Avm1Value::String(value) => Value::String(value.to_string()),
|
||||||
Avm1Value::Object(object) => {
|
Avm1Value::Object(object) => {
|
||||||
if activation
|
if object.as_array_object().is_some() {
|
||||||
.context
|
|
||||||
.avm1
|
|
||||||
.prototypes()
|
|
||||||
.array
|
|
||||||
.is_prototype_of(object)
|
|
||||||
{
|
|
||||||
let length = object.length(activation)?;
|
let length = object.length(activation)?;
|
||||||
let values: Result<Vec<_>, crate::avm1::error::Error<'gc>> = (0..length)
|
let values: Result<Vec<_>, Avm1Error<'gc>> = (0..length)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let element = object.get_element(activation, i);
|
let element = object.get_element(activation, i);
|
||||||
Value::from_avm1(activation, element)
|
Value::from_avm1(activation, element)
|
||||||
|
@ -170,17 +165,14 @@ impl Value {
|
||||||
}
|
}
|
||||||
object.into()
|
object.into()
|
||||||
}
|
}
|
||||||
Value::List(values) => {
|
Value::List(values) => Avm1ArrayObject::new(
|
||||||
let array = Avm1ScriptObject::array(
|
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes().array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
values
|
||||||
for (i, value) in values.iter().enumerate() {
|
.iter()
|
||||||
let element = value.to_owned().into_avm1(activation);
|
.map(|value| value.to_owned().into_avm1(activation)),
|
||||||
array.set_element(activation, i as i32, element).unwrap();
|
)
|
||||||
}
|
.into(),
|
||||||
array.into()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
use crate::avm1::activation::Activation as Avm1Activation;
|
use crate::avm1::activation::Activation as Avm1Activation;
|
||||||
use crate::avm1::{
|
use crate::avm1::{
|
||||||
AvmString, Object as Avm1Object, ScriptObject as Avm1ScriptObject, TObject as Avm1TObject,
|
ArrayObject as Avm1ArrayObject, AvmString, Object as Avm1Object,
|
||||||
Value as Avm1Value,
|
ScriptObject as Avm1ScriptObject, TObject as Avm1TObject, Value as Avm1Value,
|
||||||
};
|
};
|
||||||
use crate::avm2::{
|
use crate::avm2::{
|
||||||
Activation as Avm2Activation, ArrayObject as Avm2ArrayObject, Error as Avm2Error,
|
Activation as Avm2Activation, ArrayObject as Avm2ArrayObject, Error as Avm2Error,
|
||||||
|
@ -677,20 +677,17 @@ impl TextFormat {
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let tab_stops = if let Some(ts) = &self.tab_stops {
|
let tab_stops = self
|
||||||
let tab_stops = Avm1ScriptObject::array(
|
.tab_stops
|
||||||
|
.as_ref()
|
||||||
|
.map_or(Avm1Value::Null, |tab_stops| {
|
||||||
|
Avm1ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes().array),
|
activation.context.avm1.prototypes().array,
|
||||||
);
|
tab_stops.iter().map(|&x| x.into()),
|
||||||
for (i, &tab) in ts.iter().enumerate() {
|
)
|
||||||
tab_stops
|
.into()
|
||||||
.set_element(activation, i as i32, tab.into())
|
});
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
tab_stops.into()
|
|
||||||
} else {
|
|
||||||
Avm1Value::Null
|
|
||||||
};
|
|
||||||
object.set("tabStops", tab_stops, activation)?;
|
object.set("tabStops", tab_stops, activation)?;
|
||||||
Ok(object.into())
|
Ok(object.into())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1151,7 +1151,7 @@ impl<'gc> XmlNode<'gc> {
|
||||||
/// that yield `true` shall be printed.
|
/// that yield `true` shall be printed.
|
||||||
pub fn into_string<F>(self, filter: &mut F) -> Result<String, Error>
|
pub fn into_string<F>(self, filter: &mut F) -> Result<String, Error>
|
||||||
where
|
where
|
||||||
F: FnMut(XmlNode<'gc>) -> bool,
|
F: FnMut(&XmlNode<'gc>) -> bool,
|
||||||
{
|
{
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
let mut writer = Writer::new(Cursor::new(&mut buf));
|
let mut writer = Writer::new(Cursor::new(&mut buf));
|
||||||
|
@ -1173,9 +1173,9 @@ impl<'gc> XmlNode<'gc> {
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
W: Write,
|
W: Write,
|
||||||
F: FnMut(XmlNode<'gc>) -> bool,
|
F: FnMut(&XmlNode<'gc>) -> bool,
|
||||||
{
|
{
|
||||||
let children: Vec<_> = self.children().filter(|child| filter(*child)).collect();
|
let children: Vec<_> = self.children().filter(|child| filter(child)).collect();
|
||||||
let children_len = children.len();
|
let children_len = children.len();
|
||||||
|
|
||||||
match &*self.0.read() {
|
match &*self.0.read() {
|
||||||
|
|
Loading…
Reference in New Issue