avm1: Rewrite TObject array methods

This commit is contained in:
relrelb 2021-06-06 03:25:21 +03:00 committed by Mike Welsh
parent 3b215d6c76
commit 9c5b9b7072
25 changed files with 757 additions and 740 deletions

View File

@ -1465,7 +1465,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn action_init_array(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> { fn action_init_array(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
let num_elements = self.context.avm1.pop().coerce_to_f64(self)?; let num_elements = self.context.avm1.pop().coerce_to_f64(self)?;
let result = if num_elements < 0.0 || num_elements > std::i32::MAX as f64 { let result = if num_elements < 0.0 || num_elements > i32::MAX as f64 {
// 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 {
@ -1473,8 +1473,9 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
self.context.gc_context, self.context.gc_context,
Some(self.context.avm1.prototypes.array), Some(self.context.avm1.prototypes.array),
); );
for i in 0..num_elements as usize { for i in 0..num_elements as i32 {
array.set_array_element(i, self.context.avm1.pop(), self.context.gc_context); let element = self.context.avm1.pop();
array.set_element(self, i, element).unwrap();
} }
Value::Object(array.into()) Value::Object(array.into())
}; };
@ -1485,7 +1486,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn action_init_object(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> { fn action_init_object(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
let num_props = self.context.avm1.pop().coerce_to_f64(self)?; let num_props = self.context.avm1.pop().coerce_to_f64(self)?;
let result = if num_props < 0.0 || num_props > std::i32::MAX as f64 { let result = if num_props < 0.0 || num_props > i32::MAX as f64 {
// InitArray pops no args and pushes undefined if num_props is out of range. // InitArray pops no args and pushes undefined if num_props is out of range.
Value::Undefined Value::Undefined
} else { } else {

View File

@ -263,12 +263,10 @@ impl<'gc> Executable<'gc> {
); );
if !af.flags.contains(FunctionFlags::SUPPRESS_ARGUMENTS) { if !af.flags.contains(FunctionFlags::SUPPRESS_ARGUMENTS) {
for i in 0..args.len() { for (i, arg) in args.iter().enumerate() {
arguments.set_array_element( arguments
i, .set_element(activation, i as i32, arg.to_owned())
*args.get(i).unwrap(), .unwrap();
activation.context.gc_context,
);
} }
} }
@ -803,33 +801,37 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
self.base.as_ptr() self.base.as_ptr()
} }
fn length(&self) -> usize { fn length(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
self.base.length() self.base.length(activation)
} }
fn set_length(&self, gc_context: MutationContext<'gc, '_>, new_length: usize) { fn set_length(
self.base.set_length(gc_context, new_length)
}
fn array(&self) -> Vec<Value<'gc>> {
self.base.array()
}
fn array_element(&self, index: usize) -> Value<'gc> {
self.base.array_element(index)
}
fn set_array_element(
&self, &self,
index: usize, activation: &mut Activation<'_, 'gc, '_>,
value: Value<'gc>, length: i32,
gc_context: MutationContext<'gc, '_>, ) -> Result<(), Error<'gc>> {
) -> usize { self.base.set_length(activation, length)
self.base.set_array_element(index, value, gc_context)
} }
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) { fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
self.base.delete_array_element(index, gc_context) self.base.has_element(activation, index)
}
fn get_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> Value<'gc> {
self.base.get_element(activation, index)
}
fn set_element(
&self,
activation: &mut Activation<'_, 'gc, '_>,
index: i32,
value: Value<'gc>,
) -> Result<(), Error<'gc>> {
self.base.set_element(activation, index, value)
}
fn delete_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
self.base.delete_element(activation, index)
} }
} }

View File

@ -79,27 +79,19 @@ 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 mut consumed = false; if let [Value::Number(length)] = *args {
let length = if length.is_finite() && length >= i32::MIN.into() && length <= i32::MAX.into()
if args.len() == 1 { {
let arg = args.get(0).unwrap(); length as i32
if let Value::Number(length) = *arg { } else {
if length >= 0.0 { i32::MIN
this.set_length(activation.context.gc_context, length as usize); };
consumed = true; this.set_length(activation, length)?;
} else if !length.is_nan() { } else {
this.set_length(activation.context.gc_context, 0); for (i, &arg) in args.iter().enumerate() {
consumed = true; this.set_element(activation, i as i32, arg)?;
} }
} }
}
if !consumed {
for (i, arg) in args.iter().enumerate() {
this.set_array_element(i, arg.to_owned(), activation.context.gc_context);
}
}
Ok(this.into()) Ok(this.into())
} }
@ -109,31 +101,11 @@ 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 mut consumed = false; let array = ScriptObject::array(
activation.context.gc_context,
let prototype = activation.context.avm1.prototypes.array; Some(activation.context.avm1.prototypes.array),
let array_obj = prototype.create_bare_object(activation, prototype)?; );
constructor(activation, array.into(), args)
if args.len() == 1 {
let arg = args.get(0).unwrap();
if let Value::Number(length) = *arg {
if length >= 0.0 {
array_obj.set_length(activation.context.gc_context, length as usize);
consumed = true;
} else if !length.is_nan() {
array_obj.set_length(activation.context.gc_context, 0);
consumed = true;
}
}
}
if !consumed {
for (i, arg) in args.iter().enumerate() {
array_obj.set_array_element(i, arg.to_owned(), activation.context.gc_context);
}
}
Ok(array_obj.into())
} }
pub fn push<'gc>( pub fn push<'gc>(
@ -141,19 +113,14 @@ pub fn push<'gc>(
this: Object<'gc>, this: Object<'gc>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let old_length = this.length(); let old_length = this.length(activation)?;
let new_length = old_length + args.len(); for (i, &arg) in args.iter().enumerate() {
this.set_length(activation.context.gc_context, new_length); this.set_element(activation, old_length + i as i32, arg)?;
for i in 0..args.len() {
this.set_array_element(
old_length + i,
args.get(i).unwrap().to_owned(),
activation.context.gc_context,
);
} }
Ok((new_length as f64).into()) let new_length = old_length + args.len() as i32;
this.set_length(activation, new_length)?;
Ok(new_length.into())
} }
pub fn unshift<'gc>( pub fn unshift<'gc>(
@ -161,33 +128,25 @@ pub fn unshift<'gc>(
this: Object<'gc>, this: Object<'gc>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let old_length = this.length(); let old_length = this.length(activation)?;
let new_length = old_length + args.len(); let new_length = old_length + args.len() as i32;
let offset = args.len(); for i in 0..old_length {
let from = old_length - i - 1;
if old_length > 0 { let to = new_length - i - 1;
// Move all elements up by [offset], in reverse order. if this.has_element(activation, from) {
for i in (offset..new_length).rev() { let element = this.get_element(activation, from);
this.set_array_element( this.set_element(activation, to, element)?;
i, } else {
this.array_element(i - offset), this.delete_element(activation, to);
activation.context.gc_context,
);
} }
} }
for i in 0..args.len() { for (i, &arg) in args.iter().enumerate() {
// Put the new elements at the start of the array. this.set_element(activation, i as i32, arg)?;
this.set_array_element(
i,
args.get(i).unwrap().to_owned(),
activation.context.gc_context,
);
} }
this.set_length(activation.context.gc_context, new_length); this.set_length(activation, new_length)?;
Ok(new_length.into())
Ok((new_length as f64).into())
} }
pub fn shift<'gc>( pub fn shift<'gc>(
@ -195,25 +154,26 @@ pub fn shift<'gc>(
this: Object<'gc>, this: Object<'gc>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let old_length = this.length(); let length = this.length(activation)?;
if old_length == 0 { if length == 0 {
return Ok(Value::Undefined); return Ok(Value::Undefined);
} }
let new_length = old_length - 1; let first = this.get_element(activation, 0);
let removed = this.array_element(0); for i in 1..length {
if this.has_element(activation, i) {
for i in 0..new_length { let element = this.get_element(activation, i);
this.set_array_element(i, this.array_element(i + 1), activation.context.gc_context); this.set_element(activation, i - 1, element)?;
} else {
this.delete_element(activation, i - 1);
}
} }
this.delete_array_element(new_length, activation.context.gc_context); this.delete_element(activation, length - 1);
this.delete(activation, &new_length.to_string());
this.set_length(activation.context.gc_context, new_length); this.set_length(activation, length - 1)?;
Ok(first)
Ok(removed)
} }
pub fn pop<'gc>( pub fn pop<'gc>(
@ -221,20 +181,16 @@ pub fn pop<'gc>(
this: Object<'gc>, this: Object<'gc>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let old_length = this.length(); let old_length = this.length(activation)?;
if old_length == 0 { if old_length == 0 {
return Ok(Value::Undefined); return Ok(Value::Undefined);
} }
let new_length = old_length - 1; let new_length = old_length - 1;
let last = this.get_element(activation, new_length);
let removed = this.array_element(new_length); this.delete_element(activation, new_length);
this.delete_array_element(new_length, activation.context.gc_context); this.set_length(activation, new_length)?;
this.delete(activation, &new_length.to_string()); Ok(last)
this.set_length(activation.context.gc_context, new_length);
Ok(removed)
} }
pub fn reverse<'gc>( pub fn reverse<'gc>(
@ -242,11 +198,41 @@ pub fn reverse<'gc>(
this: Object<'gc>, this: Object<'gc>,
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let length = this.length(); let length = this.length(activation)?;
let mut values = this.array().to_vec(); for lower_index in 0..length / 2 {
let has_lower = this.has_element(activation, lower_index);
let lower_value = if has_lower {
this.get_element(activation, lower_index)
} else {
Value::Undefined
};
for i in 0..length { let upper_index = length - lower_index - 1;
this.set_array_element(i, values.pop().unwrap(), activation.context.gc_context); let has_upper = this.has_element(activation, upper_index);
let upper_value = if has_upper {
this.get_element(activation, upper_index)
} else {
Value::Undefined
};
match (has_lower, has_upper) {
(true, true) => {
this.set_element(activation, lower_index, upper_value)?;
this.set_element(activation, upper_index, lower_value)?;
}
(true, false) => {
this.delete_element(activation, lower_index);
this.set_element(activation, upper_index, lower_value)?;
}
(false, true) => {
this.set_element(activation, lower_index, upper_value)?;
this.delete_element(activation, upper_index);
}
(false, false) => {
this.delete_element(activation, lower_index);
this.delete_element(activation, upper_index);
}
}
} }
// Some docs incorrectly say reverse returns Void. // Some docs incorrectly say reverse returns Void.
@ -258,35 +244,35 @@ pub fn join<'gc>(
this: Object<'gc>, this: Object<'gc>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let separator = args let length = this.length(activation)?;
.get(0)
.and_then(|v| v.coerce_to_string(activation).ok())
.unwrap_or_else(|| ",".into());
let values: Vec<Value<'gc>> = this.array();
Ok(AvmString::new( let separator = if let Some(v) = args.get(0) {
activation.context.gc_context, v.coerce_to_string(activation)?
values } else {
.iter() ",".into()
.map(|v| { };
v.coerce_to_string(activation)
.unwrap_or_else(|_| "undefined".into()) if length <= 0 {
.to_string() return Ok("".into());
}
let parts: Result<Vec<_>, Error<'gc>> = (0..length)
.map(|i| {
let element = this.get_element(activation, i);
Ok(element.coerce_to_string(activation)?.to_string())
}) })
.collect::<Vec<String>>() .collect();
.join(&separator),
) Ok(AvmString::new(activation.context.gc_context, parts?.join(&separator)).into())
.into())
} }
/// Handles an index parameter that may be positive (starting from beginning) or negaitve (starting from end). /// Handles an index parameter that may be positive (starting from beginning) or negaitve (starting from end).
/// The returned index will be positive and clamped from [0, length]. /// The returned index will be positive and clamped from [0, length].
fn make_index_absolute(index: i32, length: usize) -> usize { fn make_index_absolute(index: i32, length: i32) -> i32 {
if index < 0 { if index < 0 {
let offset = index as isize; (index + length).max(0)
length.saturating_sub((-offset) as usize)
} else { } else {
(index as usize).min(length) index.min(length)
} }
} }
@ -295,32 +281,30 @@ pub fn slice<'gc>(
this: Object<'gc>, this: Object<'gc>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let start = args let length = this.length(activation)?;
.get(0)
.and_then(|v| v.coerce_to_f64(activation).ok()) let start = make_index_absolute(
.map(|v| make_index_absolute(v as i32, this.length())) args.get(0)
.unwrap_or(0); .unwrap_or(&Value::Undefined)
let end = args .coerce_to_i32(activation)?,
.get(1) length,
.and_then(|v| v.coerce_to_f64(activation).ok()) );
.map(|v| make_index_absolute(v as i32, this.length()))
.unwrap_or_else(|| this.length()); let end = args.get(1).unwrap_or(&Value::Undefined);
let end = if end == &Value::Undefined {
length
} else {
make_index_absolute(end.coerce_to_i32(activation)?, length)
};
let array = ScriptObject::array( let array = ScriptObject::array(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm1.prototypes.array), Some(activation.context.avm1.prototypes.array),
); );
for i in start..end {
if start < end { if this.has_element(activation, i) {
let length = end - start; let element = this.get_element(activation, i);
array.set_length(activation.context.gc_context, length); array.set_element(activation, i - start, element).unwrap();
for i in 0..length {
array.set_array_element(
i,
this.array_element(start + i),
activation.context.gc_context,
);
} }
} }
@ -336,72 +320,58 @@ pub fn splice<'gc>(
return Ok(Value::Undefined); return Ok(Value::Undefined);
} }
let old_length = this.length(); let length = this.length(activation)?;
let start = args let start = make_index_absolute(args.get(0).unwrap().coerce_to_i32(activation)?, length);
.get(0) let delete_count = if let Some(arg) = args.get(1) {
.and_then(|v| v.coerce_to_f64(activation).ok()) let delete_count = arg.coerce_to_i32(activation)?;
.map(|v| make_index_absolute(v as i32, old_length)) if delete_count < 0 {
.unwrap_or(0);
let count = args
.get(1)
.and_then(|v| v.coerce_to_f64(activation).ok())
.map(|v| v as i32)
.unwrap_or(old_length as i32);
if count < 0 {
return Ok(Value::Undefined); return Ok(Value::Undefined);
} }
delete_count.min(length - start)
} else {
length - start
};
let removed = ScriptObject::array( let result_array = ScriptObject::array(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm1.prototypes.array), Some(activation.context.avm1.prototypes.array),
); );
let to_remove = count.min(old_length as i32 - start as i32).max(0) as usize; for i in 0..delete_count {
let to_add = if args.len() > 2 { &args[2..] } else { &[] }; if this.has_element(activation, start + i) {
let offset = to_remove as i32 - to_add.len() as i32; let element = this.get_element(activation, start + i);
let new_length = old_length + to_add.len() - to_remove; result_array.set_element(activation, i, element).unwrap();
for i in start..start + to_remove {
removed.set_array_element(
i - start,
this.array_element(i),
activation.context.gc_context,
);
} }
removed.set_length(activation.context.gc_context, to_remove); }
result_array.set_length(activation, delete_count).unwrap();
if offset < 0 { let items = if args.len() > 2 { &args[2..] } else { &[] };
for i in (start + to_add.len()..new_length).rev() { // TODO: Avoid code duplication.
this.set_array_element( if items.len() as i32 > delete_count {
i, for i in (start + delete_count..length).rev() {
this.array_element((i as i32 + offset) as usize), if this.has_element(activation, i) {
activation.context.gc_context, let element = this.get_element(activation, i);
); this.set_element(activation, i - delete_count + items.len() as i32, element)?;
} else {
this.delete_element(activation, i - delete_count + items.len() as i32);
}
} }
} else { } else {
for i in start + to_add.len()..new_length { for i in start + delete_count..length {
this.set_array_element( if this.has_element(activation, i) {
i, let element = this.get_element(activation, i);
this.array_element((i as i32 + offset) as usize), this.set_element(activation, i - delete_count + items.len() as i32, element)?;
activation.context.gc_context, } else {
); this.delete_element(activation, i - delete_count + items.len() as i32);
}
} }
} }
for i in 0..to_add.len() { for (i, &item) in items.iter().enumerate() {
this.set_array_element( this.set_element(activation, start + i as i32, item)?;
start + i,
to_add.get(i).unwrap().to_owned(),
activation.context.gc_context,
);
} }
this.set_length(activation, length - delete_count + items.len() as i32)?;
for i in new_length..old_length { Ok(result_array.into())
this.delete_array_element(i, activation.context.gc_context);
this.delete(activation, &i.to_string());
}
this.set_length(activation.context.gc_context, new_length);
Ok(removed.into())
} }
pub fn concat<'gc>( pub fn concat<'gc>(
@ -409,25 +379,14 @@ 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 array = ScriptObject::array( let result_array = ScriptObject::array(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm1.prototypes.array), Some(activation.context.avm1.prototypes.array),
); );
let mut length = 0;
for i in 0..this.length() { let mut index = 0;
let old = this for &value in [this.into()].iter().chain(args) {
.get(&i.to_string(), activation) let array_object = if let Value::Object(object) = value {
.unwrap_or(Value::Undefined);
array.set_array_element(length, old, activation.context.gc_context);
length += 1;
}
for arg in args {
let mut added = false;
if let Value::Object(object) = arg {
let object = *object;
if activation if activation
.context .context
.avm1 .avm1
@ -435,24 +394,32 @@ pub fn concat<'gc>(
.array .array
.is_prototype_of(object) .is_prototype_of(object)
{ {
added = true; Some(object)
for i in 0..object.length() { } else {
let old = object None
.get(&i.to_string(), activation)
.unwrap_or(Value::Undefined);
array.set_array_element(length, old, activation.context.gc_context);
length += 1;
} }
} else {
None
};
if let Some(array_object) = array_object {
let length = array_object.length(activation)?;
for i in 0..length {
if array_object.has_element(activation, i) {
let element = array_object.get_element(activation, i);
result_array
.set_element(activation, index, element)
.unwrap();
index += 1;
}
}
} else {
result_array.set_element(activation, index, value).unwrap();
index += 1;
} }
} }
if !added { Ok(result_array.into())
array.set_array_element(length, *arg, activation.context.gc_context);
length += 1;
}
}
Ok(array.into())
} }
pub fn to_string<'gc>( pub fn to_string<'gc>(
@ -518,11 +485,16 @@ fn sort_on<'gc>(
let fields = match args.get(0) { let fields = match args.get(0) {
Some(Value::Object(array)) => { Some(Value::Object(array)) => {
// Array of field names. // Array of field names.
let mut field_names = vec![]; let length = array.length(activation)?;
for name in array.array() { let field_names: Result<Vec<_>, Error<'gc>> = (0..length)
field_names.push(name.coerce_to_string(activation)?.to_string()); .map(|i| {
} Ok(array
field_names .get_element(activation, i)
.coerce_to_string(activation)?
.to_string())
})
.collect();
field_names?
} }
Some(field_name) => { Some(field_name) => {
// Single field. // Single field.
@ -539,14 +511,16 @@ fn sort_on<'gc>(
let flags = match args.get(1) { let flags = match args.get(1) {
Some(Value::Object(array)) => { Some(Value::Object(array)) => {
// Array of field names. // Array of field names.
if array.length() == fields.len() { let length = array.length(activation)?;
let mut flags = vec![]; if length as usize == fields.len() {
for flag in array.array() { let flags: Result<Vec<_>, Error<'gc>> = (0..length)
flags.push(SortFlags::from_bits_truncate( .map(|i| {
flag.coerce_to_i32(activation)?, Ok(SortFlags::from_bits_truncate(
)); array.get_element(activation, i).coerce_to_i32(activation)?,
} ))
flags })
.collect();
flags?
} else { } else {
// If the lengths of the flags and fields array do not match, the flags array is ignored. // If the lengths of the flags and fields array do not match, the flags array is ignored.
std::iter::repeat(SortFlags::empty()) std::iter::repeat(SortFlags::empty())
@ -598,13 +572,14 @@ fn sort_with_function<'gc>(
mut compare_fn: impl FnMut(&mut Activation<'_, 'gc, '_>, &Value<'gc>, &Value<'gc>) -> Ordering, mut compare_fn: impl FnMut(&mut Activation<'_, 'gc, '_>, &Value<'gc>, &Value<'gc>) -> Ordering,
flags: SortFlags, flags: SortFlags,
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let length = this.length(); let length = this.length(activation)?;
let mut values: Vec<(usize, Value<'gc>)> = this.array().into_iter().enumerate().collect(); let mut values: Vec<_> = (0..length)
let array_proto = activation.context.avm1.prototypes.array; .map(|i| (i, this.get_element(activation, i)))
.collect();
let mut is_unique = true; let mut is_unique = true;
values.sort_unstable_by(|a, b| { values.sort_unstable_by(|(_, a), (_, b)| {
let mut ret = compare_fn(activation, &a.1, &b.1); let mut ret = compare_fn(activation, a, b);
if flags.contains(SortFlags::DESCENDING) { if flags.contains(SortFlags::DESCENDING) {
ret = ret.reverse(); ret = ret.reverse();
} }
@ -622,22 +597,24 @@ 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(activation.context.gc_context, Some(array_proto)); let array = ScriptObject::array(
array.set_length(activation.context.gc_context, length);
for (i, value) in values.into_iter().enumerate() {
array.set_array_element(
i,
Value::Number(value.0 as f64),
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm1.prototypes.array),
); );
for (i, (index, _)) in values.into_iter().enumerate() {
array
.set_element(activation, i as i32, index.into())
.unwrap();
} }
array.set_length(activation, length).unwrap();
Ok(array.into()) 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.
for (i, value) in values.into_iter().enumerate() { for (i, (_, value)) in values.into_iter().enumerate() {
this.set_array_element(i, value.1, activation.context.gc_context); this.set_element(activation, i as i32, value)?;
} }
this.set_length(activation, length)?;
Ok(this.into()) Ok(this.into())
} }
} }

View File

@ -68,20 +68,20 @@ pub fn add_listener<'gc>(
let listeners = this.get("_listeners", activation)?; let listeners = this.get("_listeners", activation)?;
if let Value::Object(listeners) = listeners { if let Value::Object(listeners) = listeners {
let length = listeners.length(); let length = listeners.length(activation)?;
let mut position = None;
let mut position = None;
for i in 0..length { for i in 0..length {
let other_listener = listeners.array_element(i); let other_listener = listeners.get_element(activation, i);
if new_listener == other_listener { if new_listener == other_listener {
position = Some(i); position = Some(i);
break; break;
} }
} }
if position == None { if position.is_none() {
listeners.set_length(activation.context.gc_context, length + 1); listeners.set_element(activation, length, new_listener)?;
listeners.set_array_element(length, new_listener, activation.context.gc_context); listeners.set_length(activation, length + 1)?;
} }
} }
@ -96,13 +96,12 @@ pub fn remove_listener<'gc>(
let old_listener = args.get(0).cloned().unwrap_or(Value::Undefined); let old_listener = args.get(0).cloned().unwrap_or(Value::Undefined);
let listeners = this.get("_listeners", activation)?; let listeners = this.get("_listeners", activation)?;
let mut removed = false;
if let Value::Object(listeners) = listeners { if let Value::Object(listeners) = listeners {
let length = listeners.length(); let length = listeners.length(activation)?;
let mut position = None;
let mut position = None;
for i in 0..length { for i in 0..length {
let other_listener = listeners.array_element(i); let other_listener = listeners.get_element(activation, i);
if old_listener == other_listener { if old_listener == other_listener {
position = Some(i); position = Some(i);
break; break;
@ -113,24 +112,19 @@ pub fn remove_listener<'gc>(
if length > 0 { if length > 0 {
let new_length = length - 1; let new_length = length - 1;
for i in position..new_length { for i in position..new_length {
listeners.set_array_element( let element = listeners.get_element(activation, i + 1);
i, listeners.set_element(activation, i, element)?;
listeners.array_element(i + 1),
activation.context.gc_context,
);
} }
listeners.delete_array_element(new_length, activation.context.gc_context); listeners.delete_element(activation, new_length);
listeners.delete(activation, &new_length.to_string()); listeners.set_length(activation, new_length)?;
listeners.set_length(activation.context.gc_context, new_length); return Ok(true.into());
removed = true;
} }
} }
} }
Ok(removed.into()) Ok(false.into())
} }
pub fn broadcast_message<'gc>( pub fn broadcast_message<'gc>(
@ -157,16 +151,16 @@ pub fn broadcast_internal<'gc>(
let listeners = this.get("_listeners", activation)?; let listeners = this.get("_listeners", activation)?;
if let Value::Object(listeners) = listeners { if let Value::Object(listeners) = listeners {
let len = listeners.length(); let length = listeners.length(activation)?;
for i in 0..len { for i in 0..length {
let listener = listeners.array_element(i); let listener = listeners.get_element(activation, i);
if let Value::Object(listener) = listener { if let Value::Object(listener) = listener {
listener.call_method(method_name, call_args, activation)?; listener.call_method(method_name, call_args, activation)?;
} }
} }
Ok(len > 0) Ok(length > 0)
} else { } else {
Ok(false) Ok(false)
} }

View File

@ -685,16 +685,18 @@ pub fn perlin_noise<'gc>(
.unwrap_or(&Value::Undefined) .unwrap_or(&Value::Undefined)
.coerce_to_object(activation); .coerce_to_object(activation);
let mut octave_offsets = vec![]; let octave_offsets: Result<Vec<_>, Error<'gc>> = (0..num_octaves)
for i in 0..num_octaves { .map(|i| {
octave_offsets.push(if let Value::Object(e) = offsets.array_element(i) { if let Value::Object(e) = offsets.get_element(activation, i as i32) {
let x = e.get("x", activation)?.coerce_to_f64(activation)?; let x = e.get("x", activation)?.coerce_to_f64(activation)?;
let y = e.get("y", activation)?.coerce_to_f64(activation)?; let y = e.get("y", activation)?.coerce_to_f64(activation)?;
(x, y) Ok((x, y))
} else { } else {
(0.0, 0.0) Ok((0.0, 0.0))
});
} }
})
.collect();
let octave_offsets = octave_offsets?;
bitmap_data bitmap_data
.bitmap_data() .bitmap_data()
@ -996,7 +998,8 @@ pub fn palette_map<'gc>(
let mut array = [0_u32; 256]; let mut array = [0_u32; 256];
for (i, item) in array.iter_mut().enumerate() { for (i, item) in array.iter_mut().enumerate() {
*item = if let Value::Object(arg) = arg { *item = if let Value::Object(arg) = arg {
arg.array_element(i).coerce_to_u32(activation)? arg.get_element(activation, i as i32)
.coerce_to_u32(activation)?
} else { } else {
// This is an "identity mapping", fulfilling the part of the spec that // This is an "identity mapping", fulfilling the part of the spec that
// says that channels which have no array provided are simply copied. // says that channels which have no array provided are simply copied.

View File

@ -31,13 +31,11 @@ pub fn matrix<'gc>(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm1.prototypes.array), Some(activation.context.avm1.prototypes.array),
); );
for (i, item) in filter.matrix().iter().copied().enumerate() {
let arr = filter.matrix(); array
.set_element(activation, i as i32, item.into())
for (index, item) in arr.iter().copied().enumerate() { .unwrap();
array.set_array_element(index, item.into(), activation.context.gc_context);
} }
return Ok(array.into()); return Ok(array.into());
} }
@ -52,12 +50,13 @@ pub fn set_matrix<'gc>(
let matrix = args.get(0).unwrap_or(&Value::Undefined); let matrix = args.get(0).unwrap_or(&Value::Undefined);
if let Value::Object(obj) = matrix { if let Value::Object(obj) = matrix {
let arr_len = obj.length().min(20); let length = obj.length(activation)?.min(20);
let mut arr = [0.0; 4 * 5]; let mut arr = [0.0; 4 * 5];
for (index, item) in arr.iter_mut().enumerate().take(arr_len) { for (i, item) in arr.iter_mut().enumerate().take(length as usize) {
let elem = obj.array_element(index).coerce_to_f64(activation)?; *item = obj
*item = elem; .get_element(activation, i as i32)
.coerce_to_f64(activation)?;
} }
if let Some(filter) = this.as_color_matrix_filter_object() { if let Some(filter) = this.as_color_matrix_filter_object() {

View File

@ -111,12 +111,9 @@ pub fn copy<'gc>(
.get("customItems", activation)? .get("customItems", activation)?
.coerce_to_object(activation); .coerce_to_object(activation);
for i in 0..custom_items.length() { for i in 0..custom_items.length(activation)? {
custom_items_copy.set_array_element( let element = custom_items.get_element(activation, i);
i, custom_items_copy.set_element(activation, i, element)?;
custom_items.array_element(i),
activation.context.gc_context,
);
} }
Ok(copy.into()) Ok(copy.into())
@ -230,7 +227,9 @@ pub fn make_context_menu_state<'gc>(
if let Some(menu) = menu { if let Some(menu) = menu {
if let Ok(Value::Object(custom_items)) = menu.get("customItems", activation) { if let Ok(Value::Object(custom_items)) = menu.get("customItems", activation) {
for (i, item) in custom_items.array().iter().enumerate() { if let Ok(length) = custom_items.length(activation) {
for i in 0..length {
let item = custom_items.get_element(activation, i);
if let Value::Object(item) = item { if let Value::Object(item) = item {
let caption = let caption =
if let Ok(Value::String(caption)) = item.get("caption", activation) { if let Ok(Value::String(caption)) = item.get("caption", activation) {
@ -238,8 +237,9 @@ pub fn make_context_menu_state<'gc>(
} else { } else {
continue; continue;
}; };
let on_select = let on_select = if let Ok(Value::Object(on_select)) =
if let Ok(Value::Object(on_select)) = item.get("onSelect", activation) { item.get("onSelect", activation)
{
on_select on_select
} else { } else {
continue; continue;
@ -248,7 +248,8 @@ pub fn make_context_menu_state<'gc>(
let visible = let visible =
!matches!(item.get("visible", activation), Ok(Value::Bool(false))); !matches!(item.get("visible", activation), Ok(Value::Bool(false)));
// true if `true`, everything else is false // true if `true`, everything else is false
let enabled = matches!(item.get("enabled", activation), Ok(Value::Bool(true))); let enabled =
matches!(item.get("enabled", activation), Ok(Value::Bool(true)));
let separator_before = matches!( let separator_before = matches!(
item.get("separatorBefore", activation), item.get("separatorBefore", activation),
Ok(Value::Bool(true)) Ok(Value::Bool(true))
@ -266,7 +267,7 @@ pub fn make_context_menu_state<'gc>(
checked: false, checked: false,
}, },
context_menu::ContextMenuCallback::Avm1 { context_menu::ContextMenuCallback::Avm1 {
item: *item, item,
callback: on_select, callback: on_select,
}, },
); );
@ -274,6 +275,7 @@ pub fn make_context_menu_state<'gc>(
} }
} }
} }
}
result result
} }

View File

@ -193,13 +193,11 @@ pub fn matrix<'gc>(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm1.prototypes.array), Some(activation.context.avm1.prototypes.array),
); );
for (i, item) in filter.matrix().iter().copied().enumerate() {
let arr = filter.matrix(); array
.set_element(activation, i as i32, item.into())
for (index, item) in arr.iter().copied().enumerate() { .unwrap();
array.set_array_element(index, item.into(), activation.context.gc_context);
} }
return Ok(array.into()); return Ok(array.into());
} }
@ -215,14 +213,15 @@ pub fn set_matrix<'gc>(
if let Some(filter) = this.as_convolution_filter_object() { if let Some(filter) = this.as_convolution_filter_object() {
if let Value::Object(obj) = matrix { if let Value::Object(obj) = matrix {
let arr_len = obj let length = obj.length(activation)? as usize;
.length()
.max((filter.matrix_x() * filter.matrix_y()) as usize);
let mut new_matrix = (0..arr_len).map(|_| 0.0).collect::<Vec<_>>(); let arr_len = length.max(filter.matrix_x() as usize * filter.matrix_y() as usize);
let mut new_matrix = vec![0.0; arr_len];
for (index, item) in new_matrix.iter_mut().enumerate().take(obj.length()) { for (i, item) in new_matrix.iter_mut().enumerate().take(length) {
*item = obj.array_element(index).coerce_to_f64(activation)?; *item = obj
.get_element(activation, i as i32)
.coerce_to_f64(activation)?;
} }
filter.set_matrix(activation.context.gc_context, new_matrix); filter.set_matrix(activation.context.gc_context, new_matrix);

View File

@ -116,13 +116,11 @@ pub fn colors<'gc>(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm1.prototypes.array), Some(activation.context.avm1.prototypes.array),
); );
for (i, item) in filter.colors().iter().copied().enumerate() {
let arr = filter.colors(); array
.set_element(activation, i as i32, item.into())
for (index, item) in arr.iter().copied().enumerate() { .unwrap();
array.set_array_element(index, item.into(), activation.context.gc_context);
} }
return Ok(array.into()); return Ok(array.into());
} }
@ -138,16 +136,18 @@ pub fn set_colors<'gc>(
if let Value::Object(obj) = colors { if let Value::Object(obj) = colors {
if let Some(filter) = this.as_gradient_bevel_filter_object() { if let Some(filter) = this.as_gradient_bevel_filter_object() {
let arr_len = obj.length(); let arr_len = obj.length(activation)? as usize;
let mut colors_arr = Vec::with_capacity(arr_len); let mut colors_arr = Vec::with_capacity(arr_len);
let old_alphas = filter.alphas(); let old_alphas = filter.alphas();
let mut alphas_arr = Vec::with_capacity(arr_len); let mut alphas_arr = Vec::with_capacity(arr_len);
for index in 0..arr_len { for i in 0..arr_len {
let col = obj.array_element(index).coerce_to_u32(activation)?; let col = obj
.get_element(activation, i as i32)
.coerce_to_u32(activation)?;
let alpha = if let Some(alpha) = old_alphas.get(index) { let alpha = if let Some(alpha) = old_alphas.get(i) {
*alpha *alpha
} else if col >> 24 == 0 { } else if col >> 24 == 0 {
0.0 0.0
@ -180,13 +180,11 @@ pub fn alphas<'gc>(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm1.prototypes.array), Some(activation.context.avm1.prototypes.array),
); );
for (i, item) in filter.alphas().iter().copied().enumerate() {
let arr = filter.alphas(); array
.set_element(activation, i as i32, item.into())
for (index, item) in arr.iter().copied().enumerate() { .unwrap();
array.set_array_element(index, item.into(), activation.context.gc_context);
} }
return Ok(array.into()); return Ok(array.into());
} }
@ -202,25 +200,25 @@ pub fn set_alphas<'gc>(
if let Value::Object(obj) = alphas { if let Value::Object(obj) = alphas {
if let Some(filter) = this.as_gradient_bevel_filter_object() { if let Some(filter) = this.as_gradient_bevel_filter_object() {
let arr_len = obj.length().min(filter.colors().len()); let length = (obj.length(activation)? as usize).min(filter.colors().len());
let mut arr = Vec::with_capacity(arr_len);
for index in 0..arr_len { let alphas: Result<Vec<_>, Error<'gc>> = (0..length)
arr.push( .map(|i| {
obj.array_element(index) Ok(obj
.get_element(activation, i as i32)
.coerce_to_f64(activation)? .coerce_to_f64(activation)?
.max(0.0) .clamp(0.0, 1.0))
.min(1.0), })
); .collect();
} let alphas = alphas?;
let colors = filter.colors().into_iter().take(arr_len).collect(); let colors = filter.colors().into_iter().take(length).collect();
filter.set_colors(activation.context.gc_context, colors); filter.set_colors(activation.context.gc_context, colors);
let ratios = filter.ratios().into_iter().take(arr_len).collect(); let ratios = filter.ratios().into_iter().take(length).collect();
filter.set_ratios(activation.context.gc_context, ratios); filter.set_ratios(activation.context.gc_context, ratios);
filter.set_alphas(activation.context.gc_context, arr); filter.set_alphas(activation.context.gc_context, alphas);
} }
} }
@ -237,13 +235,11 @@ pub fn ratios<'gc>(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm1.prototypes.array), Some(activation.context.avm1.prototypes.array),
); );
for (i, item) in filter.ratios().iter().copied().enumerate() {
let arr = filter.ratios(); array
.set_element(activation, i as i32, item.into())
for (index, item) in arr.iter().copied().enumerate() { .unwrap();
array.set_array_element(index, item.into(), activation.context.gc_context);
} }
return Ok(array.into()); return Ok(array.into());
} }
@ -259,25 +255,25 @@ pub fn set_ratios<'gc>(
if let Value::Object(obj) = ratios { if let Value::Object(obj) = ratios {
if let Some(filter) = this.as_gradient_bevel_filter_object() { if let Some(filter) = this.as_gradient_bevel_filter_object() {
let arr_len = obj.length().min(filter.colors().len()); let length = (obj.length(activation)? as usize).min(filter.colors().len());
let mut arr = Vec::with_capacity(arr_len);
for index in 0..arr_len { let ratios: Result<Vec<_>, Error<'gc>> = (0..length)
arr.push( .map(|i| {
obj.array_element(index) Ok(obj
.get_element(activation, i as i32)
.coerce_to_i32(activation)? .coerce_to_i32(activation)?
.max(0) .clamp(0, 255) as u8)
.min(255) as u8, })
); .collect();
} let ratios = ratios?;
let colors = filter.colors().into_iter().take(arr_len).collect(); let colors = filter.colors().into_iter().take(length).collect();
filter.set_colors(activation.context.gc_context, colors); filter.set_colors(activation.context.gc_context, colors);
let alphas = filter.alphas().into_iter().take(arr_len).collect(); let alphas = filter.alphas().into_iter().take(length).collect();
filter.set_alphas(activation.context.gc_context, alphas); filter.set_alphas(activation.context.gc_context, alphas);
filter.set_ratios(activation.context.gc_context, arr); filter.set_ratios(activation.context.gc_context, ratios);
} }
} }

View File

@ -116,13 +116,11 @@ pub fn colors<'gc>(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm1.prototypes.array), Some(activation.context.avm1.prototypes.array),
); );
for (i, item) in filter.colors().iter().copied().enumerate() {
let arr = filter.colors(); array
.set_element(activation, i as i32, item.into())
for (index, item) in arr.iter().copied().enumerate() { .unwrap();
array.set_array_element(index, item.into(), activation.context.gc_context);
} }
return Ok(array.into()); return Ok(array.into());
} }
@ -138,16 +136,18 @@ pub fn set_colors<'gc>(
if let Value::Object(obj) = colors { if let Value::Object(obj) = colors {
if let Some(filter) = this.as_gradient_glow_filter_object() { if let Some(filter) = this.as_gradient_glow_filter_object() {
let arr_len = obj.length(); let arr_len = obj.length(activation)? as usize;
let mut colors_arr = Vec::with_capacity(arr_len); let mut colors_arr = Vec::with_capacity(arr_len);
let old_alphas = filter.alphas(); let old_alphas = filter.alphas();
let mut alphas_arr = Vec::with_capacity(arr_len); let mut alphas_arr = Vec::with_capacity(arr_len);
for index in 0..arr_len { for i in 0..arr_len {
let col = obj.array_element(index).coerce_to_u32(activation)?; let col = obj
.get_element(activation, i as i32)
.coerce_to_u32(activation)?;
let alpha = if let Some(alpha) = old_alphas.get(index) { let alpha = if let Some(alpha) = old_alphas.get(i) {
*alpha *alpha
} else if col >> 24 == 0 { } else if col >> 24 == 0 {
0.0 0.0
@ -180,13 +180,11 @@ pub fn alphas<'gc>(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm1.prototypes.array), Some(activation.context.avm1.prototypes.array),
); );
for (i, item) in filter.alphas().iter().copied().enumerate() {
let arr = filter.alphas(); array
.set_element(activation, i as i32, item.into())
for (index, item) in arr.iter().copied().enumerate() { .unwrap();
array.set_array_element(index, item.into(), activation.context.gc_context);
} }
return Ok(array.into()); return Ok(array.into());
} }
@ -202,25 +200,25 @@ pub fn set_alphas<'gc>(
if let Value::Object(obj) = alphas { if let Value::Object(obj) = alphas {
if let Some(filter) = this.as_gradient_glow_filter_object() { if let Some(filter) = this.as_gradient_glow_filter_object() {
let arr_len = obj.length().min(filter.colors().len()); let length = (obj.length(activation)? as usize).min(filter.colors().len());
let mut arr = Vec::with_capacity(arr_len);
for index in 0..arr_len { let alphas: Result<Vec<_>, Error<'gc>> = (0..length)
arr.push( .map(|i| {
obj.array_element(index) Ok(obj
.get_element(activation, i as i32)
.coerce_to_f64(activation)? .coerce_to_f64(activation)?
.max(0.0) .clamp(0.0, 1.0))
.min(1.0), })
); .collect();
} let alphas = alphas?;
let colors = filter.colors().into_iter().take(arr_len).collect(); let colors = filter.colors().into_iter().take(length).collect();
filter.set_colors(activation.context.gc_context, colors); filter.set_colors(activation.context.gc_context, colors);
let ratios = filter.ratios().into_iter().take(arr_len).collect(); let ratios = filter.ratios().into_iter().take(length).collect();
filter.set_ratios(activation.context.gc_context, ratios); filter.set_ratios(activation.context.gc_context, ratios);
filter.set_alphas(activation.context.gc_context, arr); filter.set_alphas(activation.context.gc_context, alphas);
} }
} }
@ -237,13 +235,11 @@ pub fn ratios<'gc>(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm1.prototypes.array), Some(activation.context.avm1.prototypes.array),
); );
for (i, item) in filter.ratios().iter().copied().enumerate() {
let arr = filter.ratios(); array
.set_element(activation, i as i32, item.into())
for (index, item) in arr.iter().copied().enumerate() { .unwrap();
array.set_array_element(index, item.into(), activation.context.gc_context);
} }
return Ok(array.into()); return Ok(array.into());
} }
@ -259,25 +255,25 @@ pub fn set_ratios<'gc>(
if let Value::Object(obj) = ratios { if let Value::Object(obj) = ratios {
if let Some(filter) = this.as_gradient_glow_filter_object() { if let Some(filter) = this.as_gradient_glow_filter_object() {
let arr_len = obj.length().min(filter.colors().len()); let length = (obj.length(activation)? as usize).min(filter.colors().len());
let mut arr = Vec::with_capacity(arr_len);
for index in 0..arr_len { let ratios: Result<Vec<_>, Error<'gc>> = (0..length)
arr.push( .map(|i| {
obj.array_element(index) Ok(obj
.get_element(activation, i as i32)
.coerce_to_i32(activation)? .coerce_to_i32(activation)?
.max(0) .clamp(0, 255) as u8)
.min(255) as u8, })
); .collect();
} let ratios = ratios?;
let colors = filter.colors().into_iter().take(arr_len).collect(); let colors = filter.colors().into_iter().take(length).collect();
filter.set_colors(activation.context.gc_context, colors); filter.set_colors(activation.context.gc_context, colors);
let alphas = filter.alphas().into_iter().take(arr_len).collect(); let alphas = filter.alphas().into_iter().take(length).collect();
filter.set_alphas(activation.context.gc_context, alphas); filter.set_alphas(activation.context.gc_context, alphas);
filter.set_ratios(activation.context.gc_context, arr); filter.set_ratios(activation.context.gc_context, ratios);
} }
} }

View File

@ -350,27 +350,40 @@ fn begin_gradient_fill<'gc>(
args.get(4), args.get(4),
) { ) {
let method = method.coerce_to_string(activation)?; let method = method.coerce_to_string(activation)?;
let colors = colors.coerce_to_object(activation).array(); let colors_object = colors.coerce_to_object(activation);
let alphas = alphas.coerce_to_object(activation).array(); let colors_length = colors_object.length(activation)?;
let ratios = ratios.coerce_to_object(activation).array(); let alphas_object = alphas.coerce_to_object(activation);
let alphas_length = alphas_object.length(activation)?;
let ratios_object = ratios.coerce_to_object(activation);
let ratios_length = ratios_object.length(activation)?;
let matrix_object = matrix.coerce_to_object(activation); let matrix_object = matrix.coerce_to_object(activation);
if colors.len() != alphas.len() || colors.len() != ratios.len() { if colors_length != alphas_length || colors_length != ratios_length {
avm_warn!( avm_warn!(
activation, activation,
"beginGradientFill() received different sized arrays for colors, alphas and ratios" "beginGradientFill() received different sized arrays for colors, alphas and ratios"
); );
return Ok(Value::Undefined); return Ok(Value::Undefined);
} }
let mut records = Vec::with_capacity(colors.len()); let records: Result<Vec<_>, Error<'gc>> = (0..colors_length)
for i in 0..colors.len() { .map(|i| {
let ratio = ratios[i].coerce_to_f64(activation)?.min(255.0).max(0.0); let ratio = ratios_object
let rgb = colors[i].coerce_to_u32(activation)?; .get_element(activation, i)
let alpha = alphas[i].coerce_to_f64(activation)?.min(100.0).max(0.0); .coerce_to_f64(activation)?
records.push(GradientRecord { .clamp(0.0, 255.0) as u8;
ratio: ratio as u8, let rgb = colors_object
.get_element(activation, i)
.coerce_to_u32(activation)?;
let alpha = alphas_object
.get_element(activation, i)
.coerce_to_f64(activation)?
.clamp(0.0, 100.0);
Ok(GradientRecord {
ratio,
color: Color::from_rgb(rgb, (alpha / 100.0 * 255.0) as u8), color: Color::from_rgb(rgb, (alpha / 100.0 * 255.0) as u8),
}); })
} })
.collect();
let records = records?;
let matrix = gradient_object_to_matrix(matrix_object, activation)?; let matrix = gradient_object_to_matrix(matrix_object, activation)?;
let spread = match args let spread = match args
.get(5) .get(5)

View File

@ -33,7 +33,7 @@ pub fn constructor<'gc>(
Value::Object(listeners.into()), Value::Object(listeners.into()),
Attribute::DONT_ENUM, Attribute::DONT_ENUM,
); );
listeners.set_array_element(0, Value::Object(this), activation.context.gc_context); listeners.set_element(activation, 0, this.into()).unwrap();
Ok(this.into()) Ok(this.into())
} }

View File

@ -76,10 +76,11 @@ fn serialize_value<'gc>(
{ {
if o.is_instance_of(activation, o, array).unwrap_or_default() { if o.is_instance_of(activation, o, array).unwrap_or_default() {
let mut values = Vec::new(); let mut values = Vec::new();
let len = o.length();
recursive_serialize(activation, o, &mut values); recursive_serialize(activation, o, &mut values);
Some(AmfValue::ECMAArray(vec![], values, len as u32)) // TODO: What happens if an exception is thrown here?
let length = o.length(activation).unwrap();
Some(AmfValue::ECMAArray(vec![], values, length as u32))
} else if o.is_instance_of(activation, o, xml).unwrap_or_default() { } else if o.is_instance_of(activation, o, xml).unwrap_or_default() {
o.as_xml_node().and_then(|xml_node| { o.as_xml_node().and_then(|xml_node| {
xml_node xml_node
@ -139,8 +140,8 @@ fn deserialize_value<'gc>(activation: &mut Activation<'_, 'gc, '_>, val: &AmfVal
for entry in associative { for entry in associative {
let value = deserialize_value(activation, entry.value()); let value = deserialize_value(activation, entry.value());
if let Ok(i) = entry.name().parse::<usize>() { if let Ok(i) = entry.name().parse::<i32>() {
obj.set_array_element(i, value, activation.context.gc_context); let _ = obj.set_element(activation, i, value);
} else { } else {
obj.define_value( obj.define_value(
activation.context.gc_context, activation.context.gc_context,
@ -289,8 +290,8 @@ 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::<usize>() { if let Ok(i) = entry.0.parse::<i32>() {
obj.set_array_element(i, value, activation.context.gc_context); let _ = obj.set_element(activation, i, value);
} else { } else {
obj.define_value( obj.define_value(
activation.context.gc_context, activation.context.gc_context,

View File

@ -4,6 +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::object::value_object::ValueObject; use crate::avm1::object::value_object::ValueObject;
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::{AvmString, Object, ScriptObject, TObject, Value};
use crate::string_utils; use crate::string_utils;
@ -43,7 +44,12 @@ pub fn string<'gc>(
if let Some(mut vbox) = this.as_value_object() { if let Some(mut vbox) = this.as_value_object() {
let len = value.encode_utf16().count(); let len = value.encode_utf16().count();
vbox.set_length(activation.context.gc_context, len); vbox.define_value(
activation.context.gc_context,
"length",
len.into(),
Attribute::empty(),
);
vbox.replace_value(activation.context.gc_context, value.into()); vbox.replace_value(activation.context.gc_context, value.into());
} }
@ -312,22 +318,26 @@ fn split<'gc>(
); );
if !delimiter.is_empty() { if !delimiter.is_empty() {
for (i, token) in this.split(delimiter.as_ref()).take(limit).enumerate() { for (i, token) in this.split(delimiter.as_ref()).take(limit).enumerate() {
array.set_array_element( array
i, .set_element(
activation,
i as i32,
AvmString::new(activation.context.gc_context, token.to_string()).into(), AvmString::new(activation.context.gc_context, token.to_string()).into(),
activation.context.gc_context, )
); .unwrap();
} }
} else { } else {
// When using an empty "" delimiter, Rust's str::split adds an extra beginning and trailing item, but Flash does not. // 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() { for (i, token) in this.chars().take(limit).enumerate() {
array.set_array_element( array
i, .set_element(
activation,
i as i32,
AvmString::new(activation.context.gc_context, token.to_string()).into(), AvmString::new(activation.context.gc_context, token.to_string()).into(),
activation.context.gc_context, )
); .unwrap();
} }
} }
Ok(array.into()) Ok(array.into())

View File

@ -368,16 +368,18 @@ pub fn xmlnode_child_nodes<'gc>(
continue; continue;
} }
array.set_array_element( array
compatible_nodes as usize, .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(),
activation.context.gc_context, )
); .unwrap();
compatible_nodes += 1; compatible_nodes += 1;
} }

View File

@ -144,9 +144,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
return Ok(()); return Ok(());
} }
if let Ok(index) = name.parse::<usize>() { if let Ok(index) = name.parse::<i32>() {
self.set_array_element(index, value.to_owned(), activation.context.gc_context); return self.set_element(activation, index, value.to_owned());
return Ok(());
} }
if name == "length" { if name == "length" {
@ -155,9 +154,9 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
.map(|v| v.abs() as i32) .map(|v| v.abs() as i32)
.unwrap_or(0); .unwrap_or(0);
if length > 0 { if length > 0 {
self.set_length(activation.context.gc_context, length as usize); self.set_length(activation, length)?;
} else { } else {
self.set_length(activation.context.gc_context, 0); self.set_length(activation, 0)?;
} }
} }
@ -583,40 +582,32 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
false false
} }
/// Get the length of this object, as if it were an array. /// Gets the length of this object, as if it were an array.
fn length(&self) -> usize; fn length(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>>;
/// Gets a copy of the array storage behind this object.
fn array(&self) -> Vec<Value<'gc>>;
/// Sets the length of this object, as if it were an array. /// Sets the length of this object, as if it were an array.
/// fn set_length(
/// Increasing this value will fill the gap with Value::Undefined.
/// Decreasing this value will remove affected items from both the array and properties storage.
fn set_length(&self, gc_context: MutationContext<'gc, '_>, length: usize);
/// Gets a property of this object as if it were an array.
///
/// Array element lookups do not respect the prototype chain, and will ignore virtual properties.
fn array_element(&self, index: usize) -> Value<'gc>;
/// Sets a property of this object as if it were an array.
///
/// This will increase the "length" of this object to encompass the index, and return the new length.
/// Any gap created by increasing the length will be filled with Value::Undefined, both in array
/// and property storage.
fn set_array_element(
&self, &self,
index: usize, activation: &mut Activation<'_, 'gc, '_>,
length: i32,
) -> Result<(), Error<'gc>>;
/// Checks if this object has an element.
fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool;
/// Gets a property of this object, as if it were an array.
fn get_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> Value<'gc>;
/// Sets a property of this object, as if it were an array.
fn set_element(
&self,
activation: &mut Activation<'_, 'gc, '_>,
index: i32,
value: Value<'gc>, value: Value<'gc>,
gc_context: MutationContext<'gc, '_>, ) -> Result<(), Error<'gc>>;
) -> usize;
/// Deletes a property of this object as if it were an array. /// Deletes a property of this object as if it were an array.
/// fn delete_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool;
/// This will not rearrange the array or adjust the length, nor will it affect the properties
/// storage.
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>);
} }
pub enum ObjectPtr {} pub enum ObjectPtr {}

View File

@ -227,40 +227,28 @@ macro_rules! impl_custom_object {
self.0.as_ptr() as *const crate::avm1::ObjectPtr self.0.as_ptr() as *const crate::avm1::ObjectPtr
} }
fn length(&self) -> usize { fn length(&self, activation: &mut crate::avm1::Activation<'_, 'gc, '_>) -> Result<i32, crate::avm1::Error<'gc>> {
self.0.read().$field.length() self.0.read().$field.length(activation)
} }
fn array(&self) -> Vec<crate::avm1::Value<'gc>> { fn set_length(&self, activation: &mut crate::avm1::Activation<'_, 'gc, '_>, length: i32) -> Result<(), crate::avm1::Error<'gc>> {
self.0.read().$field.array() self.0.read().$field.set_length(activation, length)
} }
fn set_length(&self, gc_context: gc_arena::MutationContext<'gc, '_>, length: usize) { fn has_element(&self, activation: &mut crate::avm1::Activation<'_, 'gc, '_>, index: i32) -> bool {
self.0.read().$field.set_length(gc_context, length) self.0.read().$field.has_element(activation, index)
} }
fn array_element(&self, index: usize) -> crate::avm1::Value<'gc> { fn get_element(&self, activation: &mut crate::avm1::Activation<'_, 'gc, '_>, index: i32) -> crate::avm1::Value<'gc> {
self.0.read().$field.array_element(index) self.0.read().$field.get_element(activation, index)
} }
fn set_array_element( fn set_element(&self, activation: &mut crate::avm1::Activation<'_, 'gc, '_>, index: i32, value: crate::avm1::Value<'gc>) -> Result<(), crate::avm1::Error<'gc>> {
&self, self.0.read().$field.set_element(activation, index, value)
index: usize,
value: crate::avm1::Value<'gc>,
gc_context: gc_arena::MutationContext<'gc, '_>,
) -> usize {
self.0
.read()
.$field
.set_array_element(index, value, gc_context)
} }
fn delete_array_element( fn delete_element(&self, activation: &mut crate::avm1::Activation<'_, 'gc, '_>, index: i32) -> bool {
&self, self.0.read().$field.delete_element(activation, index)
index: usize,
gc_context: gc_arena::MutationContext<'gc, '_>,
) {
self.0.read().$field.delete_array_element(index, gc_context)
} }
fn set_watcher( fn set_watcher(

View File

@ -14,7 +14,7 @@ pub const TYPE_OF_OBJECT: &str = "object";
#[collect(no_drop)] #[collect(no_drop)]
pub enum ArrayStorage<'gc> { pub enum ArrayStorage<'gc> {
Vector(Vec<Value<'gc>>), Vector(Vec<Value<'gc>>),
Properties { length: usize }, Properties { length: i32 },
} }
#[derive(Debug, Clone, Collect)] #[derive(Debug, Clone, Collect)]
@ -41,7 +41,7 @@ impl<'gc> Watcher<'gc> {
new_value: Value<'gc>, new_value: Value<'gc>,
this: Object<'gc>, this: Object<'gc>,
base_proto: Option<Object<'gc>>, base_proto: Option<Object<'gc>>,
) -> Result<Value<'gc>, crate::avm1::error::Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let args = [ let args = [
Value::String(AvmString::new( Value::String(AvmString::new(
activation.context.gc_context, activation.context.gc_context,
@ -589,52 +589,61 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
self.0.as_ptr() as *const ObjectPtr self.0.as_ptr() as *const ObjectPtr
} }
fn length(&self) -> usize { fn length(&self, _activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
match &self.0.read().array { match &self.0.read().array {
ArrayStorage::Vector(vector) => vector.len(), ArrayStorage::Vector(vector) => Ok(vector.len() as i32),
ArrayStorage::Properties { length } => *length, ArrayStorage::Properties { length } => Ok(*length),
} }
} }
fn set_length(&self, gc_context: MutationContext<'gc, '_>, new_length: usize) { fn set_length(
let mut to_remove = None; &self,
activation: &mut Activation<'_, 'gc, '_>,
match &mut self.0.write(gc_context).array { new_length: i32,
) -> Result<(), Error<'gc>> {
let to_remove = match &mut self.0.write(activation.context.gc_context).array {
ArrayStorage::Vector(vector) => { ArrayStorage::Vector(vector) => {
if new_length >= 0 {
let old_length = vector.len(); let old_length = vector.len();
let new_length = new_length as usize;
vector.resize(new_length, Value::Undefined); vector.resize(new_length, Value::Undefined);
if new_length < old_length { Some(new_length..old_length)
to_remove = Some(new_length..old_length); } else {
None
} }
} }
ArrayStorage::Properties { length } => { ArrayStorage::Properties { length } => {
*length = new_length; *length = new_length;
None
} }
} };
if let Some(to_remove) = to_remove { if let Some(to_remove) = to_remove {
for i in to_remove { for i in to_remove {
self.sync_native_property(&i.to_string(), gc_context, None, true); self.sync_native_property(
&i.to_string(),
activation.context.gc_context,
None,
true,
);
} }
} }
self.sync_native_property("length", gc_context, Some(new_length.into()), false); self.sync_native_property(
"length",
activation.context.gc_context,
Some(new_length.into()),
false,
);
Ok(())
} }
fn array(&self) -> Vec<Value<'gc>> { fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
match &self.0.read().array { self.has_own_property(activation, &index.to_string())
ArrayStorage::Vector(vector) => vector.to_owned(),
ArrayStorage::Properties { length } => {
let mut values = Vec::new();
for i in 0..*length {
values.push(self.array_element(i));
}
values
}
}
} }
fn array_element(&self, index: usize) -> Value<'gc> { fn get_element(&self, _activation: &mut Activation<'_, 'gc, '_>, index: i32) -> Value<'gc> {
match &self.0.read().array { match &self.0.read().array {
ArrayStorage::Vector(vector) => { ArrayStorage::Vector(vector) => {
let index = index as usize;
if let Some(value) = vector.get(index) { if let Some(value) = vector.get(index) {
value.to_owned() value.to_owned()
} else { } else {
@ -654,37 +663,60 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
} }
} }
fn set_array_element( fn set_element(
&self, &self,
index: usize, activation: &mut Activation<'_, 'gc, '_>,
index: i32,
value: Value<'gc>, value: Value<'gc>,
gc_context: MutationContext<'gc, '_>, ) -> Result<(), Error<'gc>> {
) -> usize { self.sync_native_property(
self.sync_native_property(&index.to_string(), gc_context, Some(value), true); &index.to_string(),
let mut adjust_length = false; activation.context.gc_context,
let length = match &mut self.0.write(gc_context).array { Some(value),
true,
);
let length = match &mut self.0.write(activation.context.gc_context).array {
ArrayStorage::Vector(vector) => { ArrayStorage::Vector(vector) => {
if index >= 0 {
let index = index as usize;
if index >= vector.len() { if index >= vector.len() {
vector.resize(index + 1, Value::Undefined); vector.resize(index + 1, Value::Undefined);
} }
vector[index] = value; vector[index] = value;
adjust_length = true; Some(vector.len())
vector.len() } else {
None
}
}
ArrayStorage::Properties { length: _ } => {
// TODO: Support Array-like case?
None
} }
ArrayStorage::Properties { length } => *length,
}; };
if adjust_length { if let Some(length) = length {
self.sync_native_property("length", gc_context, Some(length.into()), false); self.sync_native_property(
"length",
activation.context.gc_context,
Some(length.into()),
false,
);
} }
length Ok(())
} }
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) { fn delete_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
if let ArrayStorage::Vector(vector) = &mut self.0.write(gc_context).array { match &mut self.0.write(activation.context.gc_context).array {
ArrayStorage::Vector(vector) => {
let index = index as usize;
if index < vector.len() { if index < vector.len() {
vector[index] = Value::Undefined; vector[index] = Value::Undefined;
true
} else {
false
} }
} }
ArrayStorage::Properties { length: _ } => self.delete(activation, &index.to_string()),
}
} }
} }

View File

@ -414,36 +414,37 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
keys keys
} }
fn length(&self) -> usize { fn length(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
self.0.read().base.length() self.0.read().base.length(activation)
} }
fn set_length(&self, gc_context: MutationContext<'gc, '_>, new_length: usize) { fn set_length(
self.0.read().base.set_length(gc_context, new_length)
}
fn array(&self) -> Vec<Value<'gc>> {
self.0.read().base.array()
}
fn array_element(&self, index: usize) -> Value<'gc> {
self.0.read().base.array_element(index)
}
fn set_array_element(
&self, &self,
index: usize, activation: &mut Activation<'_, 'gc, '_>,
value: Value<'gc>, length: i32,
gc_context: MutationContext<'gc, '_>, ) -> Result<(), Error<'gc>> {
) -> usize { self.0.read().base.set_length(activation, length)
self.0
.read()
.base
.set_array_element(index, value, gc_context)
} }
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) { fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
self.0.read().base.delete_array_element(index, gc_context) self.0.read().base.has_element(activation, index)
}
fn get_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> Value<'gc> {
self.0.read().base.get_element(activation, index)
}
fn set_element(
&self,
activation: &mut Activation<'_, 'gc, '_>,
index: i32,
value: Value<'gc>,
) -> Result<(), Error<'gc>> {
self.0.read().base.set_element(activation, index, value)
}
fn delete_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
self.0.read().base.delete_element(activation, index)
} }
fn interfaces(&self) -> Vec<Object<'gc>> { fn interfaces(&self) -> Vec<Object<'gc>> {

View File

@ -252,30 +252,38 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
TYPE_OF_OBJECT TYPE_OF_OBJECT
} }
fn length(&self) -> usize { fn length(&self, _activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
0 Ok(0)
} }
fn set_length(&self, _gc_context: MutationContext<'gc, '_>, _new_length: usize) {} fn set_length(
&self,
fn array(&self) -> Vec<Value<'gc>> { _activation: &mut Activation<'_, 'gc, '_>,
vec![] _length: i32,
) -> Result<(), Error<'gc>> {
Ok(())
} }
fn array_element(&self, _index: usize) -> Value<'gc> { fn has_element(&self, _activation: &mut Activation<'_, 'gc, '_>, _index: i32) -> bool {
false
}
fn get_element(&self, _activation: &mut Activation<'_, 'gc, '_>, _index: i32) -> Value<'gc> {
Value::Undefined Value::Undefined
} }
fn set_array_element( fn set_element(
&self, &self,
_index: usize, _activation: &mut Activation<'_, 'gc, '_>,
_index: i32,
_value: Value<'gc>, _value: Value<'gc>,
_gc_context: MutationContext<'gc, '_>, ) -> Result<(), Error<'gc>> {
) -> usize { Ok(())
0
} }
fn delete_array_element(&self, _index: usize, _gc_context: MutationContext<'gc, '_>) {} fn delete_element(&self, _activation: &mut Activation<'_, 'gc, '_>, _index: i32) -> bool {
false
}
fn interfaces(&self) -> Vec<Object<'gc>> { fn interfaces(&self) -> Vec<Object<'gc>> {
//`super` does not implement interfaces //`super` does not implement interfaces

View File

@ -235,32 +235,36 @@ impl<'gc> TObject<'gc> for XmlAttributesObject<'gc> {
self.base().as_ptr() as *const ObjectPtr self.base().as_ptr() as *const ObjectPtr
} }
fn length(&self) -> usize { fn length(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
self.base().length() self.base().length(activation)
} }
fn array(&self) -> Vec<Value<'gc>> { fn set_length(
self.base().array()
}
fn set_length(&self, gc_context: MutationContext<'gc, '_>, length: usize) {
self.base().set_length(gc_context, length)
}
fn array_element(&self, index: usize) -> Value<'gc> {
self.base().array_element(index)
}
fn set_array_element(
&self, &self,
index: usize, activation: &mut Activation<'_, 'gc, '_>,
value: Value<'gc>, length: i32,
gc_context: MutationContext<'gc, '_>, ) -> Result<(), Error<'gc>> {
) -> usize { self.base().set_length(activation, length)
self.base().set_array_element(index, value, gc_context)
} }
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) { fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
self.base().delete_array_element(index, gc_context) self.base().has_element(activation, index)
}
fn get_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> Value<'gc> {
self.base().get_element(activation, index)
}
fn set_element(
&self,
activation: &mut Activation<'_, 'gc, '_>,
index: i32,
value: Value<'gc>,
) -> Result<(), Error<'gc>> {
self.base().set_element(activation, index, value)
}
fn delete_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
self.base().delete_element(activation, index)
} }
} }

View File

@ -235,32 +235,36 @@ impl<'gc> TObject<'gc> for XmlIdMapObject<'gc> {
self.base().as_ptr() as *const ObjectPtr self.base().as_ptr() as *const ObjectPtr
} }
fn length(&self) -> usize { fn length(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
self.base().length() self.base().length(activation)
} }
fn array(&self) -> Vec<Value<'gc>> { fn set_length(
self.base().array()
}
fn set_length(&self, gc_context: MutationContext<'gc, '_>, length: usize) {
self.base().set_length(gc_context, length)
}
fn array_element(&self, index: usize) -> Value<'gc> {
self.base().array_element(index)
}
fn set_array_element(
&self, &self,
index: usize, activation: &mut Activation<'_, 'gc, '_>,
value: Value<'gc>, length: i32,
gc_context: MutationContext<'gc, '_>, ) -> Result<(), Error<'gc>> {
) -> usize { self.base().set_length(activation, length)
self.base().set_array_element(index, value, gc_context)
} }
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) { fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
self.base().delete_array_element(index, gc_context) self.base().has_element(activation, index)
}
fn get_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> Value<'gc> {
self.base().get_element(activation, index)
}
fn set_element(
&self,
activation: &mut Activation<'_, 'gc, '_>,
index: i32,
value: Value<'gc>,
) -> Result<(), Error<'gc>> {
self.base().set_element(activation, index, value)
}
fn delete_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
self.base().delete_element(activation, index)
} }
} }

View File

@ -1227,15 +1227,13 @@ impl<'gc> EditText<'gc> {
); );
if let Ok(Avm1Value::Object(listeners)) = object.get("_listeners", activation) { if let Ok(Avm1Value::Object(listeners)) = object.get("_listeners", activation) {
if listeners.length() == 0 { let length = listeners.length(activation);
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'.
listeners.set_array_element(0, object.into(), activation.context.gc_context); let _ = listeners.set_element(activation, 0, object.into());
} else { } else {
log::warn!( log::warn!("_listeners should be empty");
"_listeners should be empty, but its length is {}",
listeners.length()
);
} }
} }
} }

View File

@ -131,11 +131,14 @@ impl Value {
.array .array
.is_prototype_of(object) .is_prototype_of(object)
{ {
let mut values = Vec::new(); let length = object.length(activation)?;
for value in object.array() { let values: Result<Vec<_>, crate::avm1::error::Error<'gc>> = (0..length)
values.push(Value::from_avm1(activation, value)?); .map(|i| {
} let element = object.get_element(activation, i);
Value::List(values) Value::from_avm1(activation, element)
})
.collect();
Value::List(values?)
} else { } else {
let keys = object.get_keys(activation); let keys = object.get_keys(activation);
let mut values = BTreeMap::new(); let mut values = BTreeMap::new();
@ -172,12 +175,9 @@ impl Value {
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm1.prototypes().array), Some(activation.context.avm1.prototypes().array),
); );
for value in values { for (i, value) in values.iter().enumerate() {
array.set_array_element( let element = value.to_owned().into_avm1(activation);
array.length(), array.set_element(activation, i as i32, element).unwrap();
value.into_avm1(activation),
activation.context.gc_context,
);
} }
array.into() array.into()
} }

View File

@ -179,14 +179,12 @@ fn getfloatarray_from_avm1_object<'gc>(
Avm1Value::Undefined => None, Avm1Value::Undefined => None,
Avm1Value::Null => None, Avm1Value::Null => None,
v => { v => {
let mut output = Vec::new();
let v = v.coerce_to_object(activation); let v = v.coerce_to_object(activation);
let length = v.length(activation)?;
for i in 0..v.length() { let output: Result<Vec<_>, crate::avm1::error::Error<'gc>> = (0..length)
output.push(v.array_element(i).coerce_to_f64(activation)?); .map(|i| v.get_element(activation, i).coerce_to_f64(activation))
} .collect();
Some(output?)
Some(output)
} }
}) })
} }
@ -679,23 +677,21 @@ impl TextFormat {
activation, activation,
)?; )?;
if let Some(ts) = &self.tab_stops { let tab_stops = if let Some(ts) = &self.tab_stops {
let tab_stops = Avm1ScriptObject::array( let tab_stops = Avm1ScriptObject::array(
activation.context.gc_context, activation.context.gc_context,
Some(activation.context.avm1.prototypes().array), Some(activation.context.avm1.prototypes().array),
); );
for (i, &tab) in ts.iter().enumerate() {
tab_stops.set_length(activation.context.gc_context, ts.len()); tab_stops
.set_element(activation, i as i32, tab.into())
for (index, tab) in ts.iter().enumerate() { .unwrap();
tab_stops.set_array_element(index, (*tab).into(), activation.context.gc_context);
} }
tab_stops.into()
object.set("tabStops", tab_stops.into(), activation)?;
} else { } else {
object.set("tabStops", Avm1Value::Null, activation)?; Avm1Value::Null
} };
object.set("tabStops", tab_stops, activation)?;
Ok(object.into()) Ok(object.into())
} }