avm1: Extract ArrayObject

This commit is contained in:
relrelb 2021-06-18 17:24:05 +03:00 committed by Mike Welsh
parent b7de03b5c8
commit 95c30b85e1
21 changed files with 555 additions and 489 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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(
activation.context.gc_context, let arguments = if af.flags.contains(FunctionFlags::SUPPRESS_ARGUMENTS) {
Some(activation.context.avm1.prototypes().array), ArrayObject::empty(activation)
); } else {
ArrayObject::new(
activation.context.gc_context,
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) {

View File

@ -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 {
activation.context.gc_context, let length = if length.is_finite() && length >= i32::MIN.into() && length <= i32::MAX.into()
Some(activation.context.avm1.prototypes.array), {
); length as i32
constructor(activation, array.into(), args) } 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.avm1.prototypes().array,
args.iter().cloned(),
)
.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); elements.push(element);
result_array
.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>(

View File

@ -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",

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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())
} }

View File

@ -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,

View File

@ -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,43 +304,37 @@ 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>(

View File

@ -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| {
child
let mut compatible_nodes = 0; .script_object(
for mut child in node.children() { activation.context.gc_context,
if !is_as2_compatible(child) { Some(activation.context.avm1.prototypes.xml_node),
continue; )
} .into()
}),
array )
.set_element( .into());
activation,
compatible_nodes,
child
.script_object(
activation.context.gc_context,
Some(activation.context.avm1.prototypes.xml_node),
)
.into(),
)
.unwrap();
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;
} }

View File

@ -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

View File

@ -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)
}
}

View File

@ -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,
value: native_value, attributes: Attribute::empty(),
attributes: if is_enumerable { });
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 { Ok(ScriptObject::object(activation.context.gc_context, Some(this)).into())
ArrayStorage::Vector(_) => {
Ok(ScriptObject::array(activation.context.gc_context, Some(this)).into())
}
ArrayStorage::Properties { .. } => {
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 } => {
*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( self.set_data("length", new_length.into(), activation)
"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()),
}
} }
} }

View File

@ -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
} }

View File

@ -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");
} }

View File

@ -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, activation.context.avm1.prototypes().array,
Some(activation.context.avm1.prototypes().array), values
); .iter()
for (i, value) in values.iter().enumerate() { .map(|value| value.to_owned().into_avm1(activation)),
let element = value.to_owned().into_avm1(activation); )
array.set_element(activation, i as i32, element).unwrap(); .into(),
}
array.into()
}
} }
} }
} }

View File

@ -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
activation.context.gc_context, .as_ref()
Some(activation.context.avm1.prototypes().array), .map_or(Avm1Value::Null, |tab_stops| {
); Avm1ArrayObject::new(
for (i, &tab) in ts.iter().enumerate() { activation.context.gc_context,
tab_stops activation.context.avm1.prototypes().array,
.set_element(activation, i as i32, tab.into()) tab_stops.iter().map(|&x| x.into()),
.unwrap(); )
} .into()
tab_stops.into() });
} else {
Avm1Value::Null
};
object.set("tabStops", tab_stops, activation)?; object.set("tabStops", tab_stops, activation)?;
Ok(object.into()) Ok(object.into())
} }

View File

@ -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() {