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>> {
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.
Value::Undefined
} else {
@ -1473,8 +1473,9 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
self.context.gc_context,
Some(self.context.avm1.prototypes.array),
);
for i in 0..num_elements as usize {
array.set_array_element(i, self.context.avm1.pop(), self.context.gc_context);
for i in 0..num_elements as i32 {
let element = self.context.avm1.pop();
array.set_element(self, i, element).unwrap();
}
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>> {
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.
Value::Undefined
} else {

View File

@ -263,12 +263,10 @@ impl<'gc> Executable<'gc> {
);
if !af.flags.contains(FunctionFlags::SUPPRESS_ARGUMENTS) {
for i in 0..args.len() {
arguments.set_array_element(
i,
*args.get(i).unwrap(),
activation.context.gc_context,
);
for (i, arg) in args.iter().enumerate() {
arguments
.set_element(activation, i as i32, arg.to_owned())
.unwrap();
}
}
@ -803,33 +801,37 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
self.base.as_ptr()
}
fn length(&self) -> usize {
self.base.length()
fn length(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
self.base.length(activation)
}
fn set_length(&self, gc_context: MutationContext<'gc, '_>, new_length: usize) {
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(
fn set_length(
&self,
index: usize,
value: Value<'gc>,
gc_context: MutationContext<'gc, '_>,
) -> usize {
self.base.set_array_element(index, value, gc_context)
activation: &mut Activation<'_, 'gc, '_>,
length: i32,
) -> Result<(), Error<'gc>> {
self.base.set_length(activation, length)
}
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) {
self.base.delete_array_element(index, gc_context)
fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
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>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let mut consumed = false;
if args.len() == 1 {
let arg = args.get(0).unwrap();
if let Value::Number(length) = *arg {
if length >= 0.0 {
this.set_length(activation.context.gc_context, length as usize);
consumed = true;
} else if !length.is_nan() {
this.set_length(activation.context.gc_context, 0);
consumed = true;
}
if let [Value::Number(length)] = *args {
let length = if length.is_finite() && length >= i32::MIN.into() && length <= i32::MAX.into()
{
length as i32
} else {
i32::MIN
};
this.set_length(activation, length)?;
} else {
for (i, &arg) in args.iter().enumerate() {
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())
}
@ -109,31 +101,11 @@ pub fn array_function<'gc>(
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let mut consumed = false;
let prototype = activation.context.avm1.prototypes.array;
let array_obj = prototype.create_bare_object(activation, prototype)?;
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())
let array = ScriptObject::array(
activation.context.gc_context,
Some(activation.context.avm1.prototypes.array),
);
constructor(activation, array.into(), args)
}
pub fn push<'gc>(
@ -141,19 +113,14 @@ pub fn push<'gc>(
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let old_length = this.length();
let new_length = old_length + args.len();
this.set_length(activation.context.gc_context, new_length);
for i in 0..args.len() {
this.set_array_element(
old_length + i,
args.get(i).unwrap().to_owned(),
activation.context.gc_context,
);
let old_length = this.length(activation)?;
for (i, &arg) in args.iter().enumerate() {
this.set_element(activation, old_length + i as i32, arg)?;
}
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>(
@ -161,33 +128,25 @@ pub fn unshift<'gc>(
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let old_length = this.length();
let new_length = old_length + args.len();
let offset = args.len();
if old_length > 0 {
// Move all elements up by [offset], in reverse order.
for i in (offset..new_length).rev() {
this.set_array_element(
i,
this.array_element(i - offset),
activation.context.gc_context,
);
let old_length = this.length(activation)?;
let new_length = old_length + args.len() as i32;
for i in 0..old_length {
let from = old_length - i - 1;
let to = new_length - i - 1;
if this.has_element(activation, from) {
let element = this.get_element(activation, from);
this.set_element(activation, to, element)?;
} else {
this.delete_element(activation, to);
}
}
for i in 0..args.len() {
// Put the new elements at the start of the array.
this.set_array_element(
i,
args.get(i).unwrap().to_owned(),
activation.context.gc_context,
);
for (i, &arg) in args.iter().enumerate() {
this.set_element(activation, i as i32, arg)?;
}
this.set_length(activation.context.gc_context, new_length);
Ok((new_length as f64).into())
this.set_length(activation, new_length)?;
Ok(new_length.into())
}
pub fn shift<'gc>(
@ -195,25 +154,26 @@ pub fn shift<'gc>(
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let old_length = this.length();
if old_length == 0 {
let length = this.length(activation)?;
if length == 0 {
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 0..new_length {
this.set_array_element(i, this.array_element(i + 1), activation.context.gc_context);
for i in 1..length {
if this.has_element(activation, i) {
let element = this.get_element(activation, i);
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(activation, &new_length.to_string());
this.delete_element(activation, length - 1);
this.set_length(activation.context.gc_context, new_length);
Ok(removed)
this.set_length(activation, length - 1)?;
Ok(first)
}
pub fn pop<'gc>(
@ -221,20 +181,16 @@ pub fn pop<'gc>(
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let old_length = this.length();
let old_length = this.length(activation)?;
if old_length == 0 {
return Ok(Value::Undefined);
}
let new_length = old_length - 1;
let removed = this.array_element(new_length);
this.delete_array_element(new_length, activation.context.gc_context);
this.delete(activation, &new_length.to_string());
this.set_length(activation.context.gc_context, new_length);
Ok(removed)
let last = this.get_element(activation, new_length);
this.delete_element(activation, new_length);
this.set_length(activation, new_length)?;
Ok(last)
}
pub fn reverse<'gc>(
@ -242,11 +198,41 @@ pub fn reverse<'gc>(
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let length = this.length();
let mut values = this.array().to_vec();
let length = this.length(activation)?;
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 {
this.set_array_element(i, values.pop().unwrap(), activation.context.gc_context);
let upper_index = length - lower_index - 1;
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.
@ -258,35 +244,35 @@ pub fn join<'gc>(
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let separator = args
.get(0)
.and_then(|v| v.coerce_to_string(activation).ok())
.unwrap_or_else(|| ",".into());
let values: Vec<Value<'gc>> = this.array();
let length = this.length(activation)?;
Ok(AvmString::new(
activation.context.gc_context,
values
.iter()
.map(|v| {
v.coerce_to_string(activation)
.unwrap_or_else(|_| "undefined".into())
.to_string()
})
.collect::<Vec<String>>()
.join(&separator),
)
.into())
let separator = if let Some(v) = args.get(0) {
v.coerce_to_string(activation)?
} else {
",".into()
};
if length <= 0 {
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();
Ok(AvmString::new(activation.context.gc_context, parts?.join(&separator)).into())
}
/// 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].
fn make_index_absolute(index: i32, length: usize) -> usize {
fn make_index_absolute(index: i32, length: i32) -> i32 {
if index < 0 {
let offset = index as isize;
length.saturating_sub((-offset) as usize)
(index + length).max(0)
} else {
(index as usize).min(length)
index.min(length)
}
}
@ -295,32 +281,30 @@ pub fn slice<'gc>(
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let start = args
.get(0)
.and_then(|v| v.coerce_to_f64(activation).ok())
.map(|v| make_index_absolute(v as i32, this.length()))
.unwrap_or(0);
let end = args
.get(1)
.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 length = this.length(activation)?;
let start = make_index_absolute(
args.get(0)
.unwrap_or(&Value::Undefined)
.coerce_to_i32(activation)?,
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(
activation.context.gc_context,
Some(activation.context.avm1.prototypes.array),
);
if start < end {
let length = end - start;
array.set_length(activation.context.gc_context, length);
for i in 0..length {
array.set_array_element(
i,
this.array_element(start + i),
activation.context.gc_context,
);
for i in start..end {
if this.has_element(activation, i) {
let element = this.get_element(activation, i);
array.set_element(activation, i - start, element).unwrap();
}
}
@ -336,72 +320,58 @@ pub fn splice<'gc>(
return Ok(Value::Undefined);
}
let old_length = this.length();
let start = args
.get(0)
.and_then(|v| v.coerce_to_f64(activation).ok())
.map(|v| make_index_absolute(v as i32, old_length))
.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);
}
let length = this.length(activation)?;
let start = make_index_absolute(args.get(0).unwrap().coerce_to_i32(activation)?, length);
let delete_count = if let Some(arg) = args.get(1) {
let delete_count = arg.coerce_to_i32(activation)?;
if delete_count < 0 {
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,
Some(activation.context.avm1.prototypes.array),
);
let to_remove = count.min(old_length as i32 - start as i32).max(0) as usize;
let to_add = if args.len() > 2 { &args[2..] } else { &[] };
let offset = to_remove as i32 - to_add.len() as i32;
let new_length = old_length + to_add.len() - to_remove;
for i in start..start + to_remove {
removed.set_array_element(
i - start,
this.array_element(i),
activation.context.gc_context,
);
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();
}
}
removed.set_length(activation.context.gc_context, to_remove);
result_array.set_length(activation, delete_count).unwrap();
if offset < 0 {
for i in (start + to_add.len()..new_length).rev() {
this.set_array_element(
i,
this.array_element((i as i32 + offset) as usize),
activation.context.gc_context,
);
let items = if args.len() > 2 { &args[2..] } else { &[] };
// TODO: Avoid code duplication.
if items.len() as i32 > delete_count {
for i in (start + delete_count..length).rev() {
if this.has_element(activation, i) {
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 {
for i in start + to_add.len()..new_length {
this.set_array_element(
i,
this.array_element((i as i32 + offset) as usize),
activation.context.gc_context,
);
for i in start + delete_count..length {
if this.has_element(activation, i) {
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);
}
}
}
for i in 0..to_add.len() {
this.set_array_element(
start + i,
to_add.get(i).unwrap().to_owned(),
activation.context.gc_context,
);
for (i, &item) in items.iter().enumerate() {
this.set_element(activation, start + i as i32, item)?;
}
this.set_length(activation, length - delete_count + items.len() as i32)?;
for i in new_length..old_length {
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())
Ok(result_array.into())
}
pub fn concat<'gc>(
@ -409,25 +379,14 @@ pub fn concat<'gc>(
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let array = ScriptObject::array(
let result_array = ScriptObject::array(
activation.context.gc_context,
Some(activation.context.avm1.prototypes.array),
);
let mut length = 0;
for i in 0..this.length() {
let old = this
.get(&i.to_string(), activation)
.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;
let mut index = 0;
for &value in [this.into()].iter().chain(args) {
let array_object = if let Value::Object(object) = value {
if activation
.context
.avm1
@ -435,24 +394,32 @@ pub fn concat<'gc>(
.array
.is_prototype_of(object)
{
added = true;
for i in 0..object.length() {
let old = object
.get(&i.to_string(), activation)
.unwrap_or(Value::Undefined);
array.set_array_element(length, old, activation.context.gc_context);
length += 1;
Some(object)
} else {
None
}
} 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;
}
}
}
if !added {
array.set_array_element(length, *arg, activation.context.gc_context);
length += 1;
} else {
result_array.set_element(activation, index, value).unwrap();
index += 1;
}
}
Ok(array.into())
Ok(result_array.into())
}
pub fn to_string<'gc>(
@ -518,11 +485,16 @@ fn sort_on<'gc>(
let fields = match args.get(0) {
Some(Value::Object(array)) => {
// Array of field names.
let mut field_names = vec![];
for name in array.array() {
field_names.push(name.coerce_to_string(activation)?.to_string());
}
field_names
let length = array.length(activation)?;
let field_names: Result<Vec<_>, Error<'gc>> = (0..length)
.map(|i| {
Ok(array
.get_element(activation, i)
.coerce_to_string(activation)?
.to_string())
})
.collect();
field_names?
}
Some(field_name) => {
// Single field.
@ -539,14 +511,16 @@ fn sort_on<'gc>(
let flags = match args.get(1) {
Some(Value::Object(array)) => {
// Array of field names.
if array.length() == fields.len() {
let mut flags = vec![];
for flag in array.array() {
flags.push(SortFlags::from_bits_truncate(
flag.coerce_to_i32(activation)?,
));
}
flags
let length = array.length(activation)?;
if length as usize == fields.len() {
let flags: Result<Vec<_>, Error<'gc>> = (0..length)
.map(|i| {
Ok(SortFlags::from_bits_truncate(
array.get_element(activation, i).coerce_to_i32(activation)?,
))
})
.collect();
flags?
} else {
// If the lengths of the flags and fields array do not match, the flags array is ignored.
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,
flags: SortFlags,
) -> Result<Value<'gc>, Error<'gc>> {
let length = this.length();
let mut values: Vec<(usize, Value<'gc>)> = this.array().into_iter().enumerate().collect();
let array_proto = activation.context.avm1.prototypes.array;
let length = this.length(activation)?;
let mut values: Vec<_> = (0..length)
.map(|i| (i, this.get_element(activation, i)))
.collect();
let mut is_unique = true;
values.sort_unstable_by(|a, b| {
let mut ret = compare_fn(activation, &a.1, &b.1);
values.sort_unstable_by(|(_, a), (_, b)| {
let mut ret = compare_fn(activation, a, b);
if flags.contains(SortFlags::DESCENDING) {
ret = ret.reverse();
}
@ -622,22 +597,24 @@ fn sort_with_function<'gc>(
if flags.contains(SortFlags::RETURN_INDEXED_ARRAY) {
// Array.RETURNINDEXEDARRAY returns an array containing the sorted indices, and does not modify
// the original array.
let array = ScriptObject::array(activation.context.gc_context, Some(array_proto));
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,
);
let array = ScriptObject::array(
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())
} else {
// Standard sort modifies the original array, and returns it.
// AS2 reference incorrectly states this returns nothing, but it returns the original array, sorted.
for (i, value) in values.into_iter().enumerate() {
this.set_array_element(i, value.1, activation.context.gc_context);
for (i, (_, value)) in values.into_iter().enumerate() {
this.set_element(activation, i as i32, value)?;
}
this.set_length(activation, length)?;
Ok(this.into())
}
}

View File

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

View File

@ -685,16 +685,18 @@ pub fn perlin_noise<'gc>(
.unwrap_or(&Value::Undefined)
.coerce_to_object(activation);
let mut octave_offsets = vec![];
for i in 0..num_octaves {
octave_offsets.push(if let Value::Object(e) = offsets.array_element(i) {
let x = e.get("x", activation)?.coerce_to_f64(activation)?;
let y = e.get("y", activation)?.coerce_to_f64(activation)?;
(x, y)
} else {
(0.0, 0.0)
});
}
let octave_offsets: Result<Vec<_>, Error<'gc>> = (0..num_octaves)
.map(|i| {
if let Value::Object(e) = offsets.get_element(activation, i as i32) {
let x = e.get("x", activation)?.coerce_to_f64(activation)?;
let y = e.get("y", activation)?.coerce_to_f64(activation)?;
Ok((x, y))
} else {
Ok((0.0, 0.0))
}
})
.collect();
let octave_offsets = octave_offsets?;
bitmap_data
.bitmap_data()
@ -996,7 +998,8 @@ pub fn palette_map<'gc>(
let mut array = [0_u32; 256];
for (i, item) in array.iter_mut().enumerate() {
*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 {
// This is an "identity mapping", fulfilling the part of the spec that
// 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,
Some(activation.context.avm1.prototypes.array),
);
let arr = filter.matrix();
for (index, item) in arr.iter().copied().enumerate() {
array.set_array_element(index, item.into(), activation.context.gc_context);
for (i, item) in filter.matrix().iter().copied().enumerate() {
array
.set_element(activation, i as i32, item.into())
.unwrap();
}
return Ok(array.into());
}
@ -52,12 +50,13 @@ pub fn set_matrix<'gc>(
let matrix = args.get(0).unwrap_or(&Value::Undefined);
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];
for (index, item) in arr.iter_mut().enumerate().take(arr_len) {
let elem = obj.array_element(index).coerce_to_f64(activation)?;
*item = elem;
for (i, item) in arr.iter_mut().enumerate().take(length as usize) {
*item = obj
.get_element(activation, i as i32)
.coerce_to_f64(activation)?;
}
if let Some(filter) = this.as_color_matrix_filter_object() {

View File

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

View File

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

View File

@ -116,13 +116,11 @@ pub fn colors<'gc>(
activation.context.gc_context,
Some(activation.context.avm1.prototypes.array),
);
let arr = filter.colors();
for (index, item) in arr.iter().copied().enumerate() {
array.set_array_element(index, item.into(), activation.context.gc_context);
for (i, item) in filter.colors().iter().copied().enumerate() {
array
.set_element(activation, i as i32, item.into())
.unwrap();
}
return Ok(array.into());
}
@ -138,16 +136,18 @@ pub fn set_colors<'gc>(
if let Value::Object(obj) = colors {
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 old_alphas = filter.alphas();
let mut alphas_arr = Vec::with_capacity(arr_len);
for index in 0..arr_len {
let col = obj.array_element(index).coerce_to_u32(activation)?;
for i in 0..arr_len {
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
} else if col >> 24 == 0 {
0.0
@ -180,13 +180,11 @@ pub fn alphas<'gc>(
activation.context.gc_context,
Some(activation.context.avm1.prototypes.array),
);
let arr = filter.alphas();
for (index, item) in arr.iter().copied().enumerate() {
array.set_array_element(index, item.into(), activation.context.gc_context);
for (i, item) in filter.alphas().iter().copied().enumerate() {
array
.set_element(activation, i as i32, item.into())
.unwrap();
}
return Ok(array.into());
}
@ -202,25 +200,25 @@ pub fn set_alphas<'gc>(
if let Value::Object(obj) = alphas {
if let Some(filter) = this.as_gradient_bevel_filter_object() {
let arr_len = obj.length().min(filter.colors().len());
let mut arr = Vec::with_capacity(arr_len);
let length = (obj.length(activation)? as usize).min(filter.colors().len());
for index in 0..arr_len {
arr.push(
obj.array_element(index)
let alphas: Result<Vec<_>, Error<'gc>> = (0..length)
.map(|i| {
Ok(obj
.get_element(activation, i as i32)
.coerce_to_f64(activation)?
.max(0.0)
.min(1.0),
);
}
.clamp(0.0, 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);
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_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,
Some(activation.context.avm1.prototypes.array),
);
let arr = filter.ratios();
for (index, item) in arr.iter().copied().enumerate() {
array.set_array_element(index, item.into(), activation.context.gc_context);
for (i, item) in filter.ratios().iter().copied().enumerate() {
array
.set_element(activation, i as i32, item.into())
.unwrap();
}
return Ok(array.into());
}
@ -259,25 +255,25 @@ pub fn set_ratios<'gc>(
if let Value::Object(obj) = ratios {
if let Some(filter) = this.as_gradient_bevel_filter_object() {
let arr_len = obj.length().min(filter.colors().len());
let mut arr = Vec::with_capacity(arr_len);
let length = (obj.length(activation)? as usize).min(filter.colors().len());
for index in 0..arr_len {
arr.push(
obj.array_element(index)
let ratios: Result<Vec<_>, Error<'gc>> = (0..length)
.map(|i| {
Ok(obj
.get_element(activation, i as i32)
.coerce_to_i32(activation)?
.max(0)
.min(255) as u8,
);
}
.clamp(0, 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);
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_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,
Some(activation.context.avm1.prototypes.array),
);
let arr = filter.colors();
for (index, item) in arr.iter().copied().enumerate() {
array.set_array_element(index, item.into(), activation.context.gc_context);
for (i, item) in filter.colors().iter().copied().enumerate() {
array
.set_element(activation, i as i32, item.into())
.unwrap();
}
return Ok(array.into());
}
@ -138,16 +136,18 @@ pub fn set_colors<'gc>(
if let Value::Object(obj) = colors {
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 old_alphas = filter.alphas();
let mut alphas_arr = Vec::with_capacity(arr_len);
for index in 0..arr_len {
let col = obj.array_element(index).coerce_to_u32(activation)?;
for i in 0..arr_len {
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
} else if col >> 24 == 0 {
0.0
@ -180,13 +180,11 @@ pub fn alphas<'gc>(
activation.context.gc_context,
Some(activation.context.avm1.prototypes.array),
);
let arr = filter.alphas();
for (index, item) in arr.iter().copied().enumerate() {
array.set_array_element(index, item.into(), activation.context.gc_context);
for (i, item) in filter.alphas().iter().copied().enumerate() {
array
.set_element(activation, i as i32, item.into())
.unwrap();
}
return Ok(array.into());
}
@ -202,25 +200,25 @@ pub fn set_alphas<'gc>(
if let Value::Object(obj) = alphas {
if let Some(filter) = this.as_gradient_glow_filter_object() {
let arr_len = obj.length().min(filter.colors().len());
let mut arr = Vec::with_capacity(arr_len);
let length = (obj.length(activation)? as usize).min(filter.colors().len());
for index in 0..arr_len {
arr.push(
obj.array_element(index)
let alphas: Result<Vec<_>, Error<'gc>> = (0..length)
.map(|i| {
Ok(obj
.get_element(activation, i as i32)
.coerce_to_f64(activation)?
.max(0.0)
.min(1.0),
);
}
.clamp(0.0, 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);
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_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,
Some(activation.context.avm1.prototypes.array),
);
let arr = filter.ratios();
for (index, item) in arr.iter().copied().enumerate() {
array.set_array_element(index, item.into(), activation.context.gc_context);
for (i, item) in filter.ratios().iter().copied().enumerate() {
array
.set_element(activation, i as i32, item.into())
.unwrap();
}
return Ok(array.into());
}
@ -259,25 +255,25 @@ pub fn set_ratios<'gc>(
if let Value::Object(obj) = ratios {
if let Some(filter) = this.as_gradient_glow_filter_object() {
let arr_len = obj.length().min(filter.colors().len());
let mut arr = Vec::with_capacity(arr_len);
let length = (obj.length(activation)? as usize).min(filter.colors().len());
for index in 0..arr_len {
arr.push(
obj.array_element(index)
let ratios: Result<Vec<_>, Error<'gc>> = (0..length)
.map(|i| {
Ok(obj
.get_element(activation, i as i32)
.coerce_to_i32(activation)?
.max(0)
.min(255) as u8,
);
}
.clamp(0, 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);
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_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),
) {
let method = method.coerce_to_string(activation)?;
let colors = colors.coerce_to_object(activation).array();
let alphas = alphas.coerce_to_object(activation).array();
let ratios = ratios.coerce_to_object(activation).array();
let colors_object = colors.coerce_to_object(activation);
let colors_length = colors_object.length(activation)?;
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);
if colors.len() != alphas.len() || colors.len() != ratios.len() {
if colors_length != alphas_length || colors_length != ratios_length {
avm_warn!(
activation,
"beginGradientFill() received different sized arrays for colors, alphas and ratios"
);
return Ok(Value::Undefined);
}
let mut records = Vec::with_capacity(colors.len());
for i in 0..colors.len() {
let ratio = ratios[i].coerce_to_f64(activation)?.min(255.0).max(0.0);
let rgb = colors[i].coerce_to_u32(activation)?;
let alpha = alphas[i].coerce_to_f64(activation)?.min(100.0).max(0.0);
records.push(GradientRecord {
ratio: ratio as u8,
color: Color::from_rgb(rgb, (alpha / 100.0 * 255.0) as u8),
});
}
let records: Result<Vec<_>, Error<'gc>> = (0..colors_length)
.map(|i| {
let ratio = ratios_object
.get_element(activation, i)
.coerce_to_f64(activation)?
.clamp(0.0, 255.0) 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),
})
})
.collect();
let records = records?;
let matrix = gradient_object_to_matrix(matrix_object, activation)?;
let spread = match args
.get(5)

View File

@ -33,7 +33,7 @@ pub fn constructor<'gc>(
Value::Object(listeners.into()),
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())
}

View File

@ -76,10 +76,11 @@ fn serialize_value<'gc>(
{
if o.is_instance_of(activation, o, array).unwrap_or_default() {
let mut values = Vec::new();
let len = o.length();
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() {
o.as_xml_node().and_then(|xml_node| {
xml_node
@ -139,8 +140,8 @@ fn deserialize_value<'gc>(activation: &mut Activation<'_, 'gc, '_>, val: &AmfVal
for entry in associative {
let value = deserialize_value(activation, entry.value());
if let Ok(i) = entry.name().parse::<usize>() {
obj.set_array_element(i, value, activation.context.gc_context);
if let Ok(i) = entry.name().parse::<i32>() {
let _ = obj.set_element(activation, i, value);
} else {
obj.define_value(
activation.context.gc_context,
@ -289,8 +290,8 @@ fn deserialize_array_json<'gc>(
for entry in json_obj.iter() {
let value = recursive_deserialize_json(entry.1.clone(), activation);
if let Ok(i) = entry.0.parse::<usize>() {
obj.set_array_element(i, value, activation.context.gc_context);
if let Ok(i) = entry.0.parse::<i32>() {
let _ = obj.set_element(activation, i, value);
} else {
obj.define_value(
activation.context.gc_context,

View File

@ -4,6 +4,7 @@ use crate::avm1::activation::Activation;
use crate::avm1::error::Error;
use crate::avm1::function::{Executable, FunctionObject};
use crate::avm1::object::value_object::ValueObject;
use crate::avm1::property::Attribute;
use crate::avm1::property_decl::{define_properties_on, Declaration};
use crate::avm1::{AvmString, Object, ScriptObject, TObject, Value};
use crate::string_utils;
@ -43,7 +44,12 @@ pub fn string<'gc>(
if let Some(mut vbox) = this.as_value_object() {
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());
}
@ -312,22 +318,26 @@ fn split<'gc>(
);
if !delimiter.is_empty() {
for (i, token) in this.split(delimiter.as_ref()).take(limit).enumerate() {
array.set_array_element(
i,
AvmString::new(activation.context.gc_context, token.to_string()).into(),
activation.context.gc_context,
);
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.
// Special case this to match Flash's behavior.
for (i, token) in this.chars().take(limit).enumerate() {
array.set_array_element(
i,
AvmString::new(activation.context.gc_context, token.to_string()).into(),
activation.context.gc_context,
);
array
.set_element(
activation,
i as i32,
AvmString::new(activation.context.gc_context, token.to_string()).into(),
)
.unwrap();
}
}
Ok(array.into())

View File

@ -368,16 +368,18 @@ pub fn xmlnode_child_nodes<'gc>(
continue;
}
array.set_array_element(
compatible_nodes as usize,
child
.script_object(
activation.context.gc_context,
Some(activation.context.avm1.prototypes.xml_node),
)
.into(),
activation.context.gc_context,
);
array
.set_element(
activation,
compatible_nodes,
child
.script_object(
activation.context.gc_context,
Some(activation.context.avm1.prototypes.xml_node),
)
.into(),
)
.unwrap();
compatible_nodes += 1;
}

View File

@ -144,9 +144,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
return Ok(());
}
if let Ok(index) = name.parse::<usize>() {
self.set_array_element(index, value.to_owned(), activation.context.gc_context);
return Ok(());
if let Ok(index) = name.parse::<i32>() {
return self.set_element(activation, index, value.to_owned());
}
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)
.unwrap_or(0);
if length > 0 {
self.set_length(activation.context.gc_context, length as usize);
self.set_length(activation, length)?;
} 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
}
/// Get the length of this object, as if it were an array.
fn length(&self) -> usize;
/// Gets a copy of the array storage behind this object.
fn array(&self) -> Vec<Value<'gc>>;
/// Gets the length of this object, as if it were an array.
fn length(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>>;
/// Sets the length of this object, as if it were an array.
///
/// 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(
fn set_length(
&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>,
gc_context: MutationContext<'gc, '_>,
) -> usize;
) -> Result<(), Error<'gc>>;
/// Deletes a property of this object as if it were an array.
///
/// 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, '_>);
fn delete_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool;
}
pub enum ObjectPtr {}

View File

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

View File

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

@ -414,36 +414,37 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
keys
}
fn length(&self) -> usize {
self.0.read().base.length()
fn length(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
self.0.read().base.length(activation)
}
fn set_length(&self, gc_context: MutationContext<'gc, '_>, new_length: usize) {
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(
fn set_length(
&self,
index: usize,
value: Value<'gc>,
gc_context: MutationContext<'gc, '_>,
) -> usize {
self.0
.read()
.base
.set_array_element(index, value, gc_context)
activation: &mut Activation<'_, 'gc, '_>,
length: i32,
) -> Result<(), Error<'gc>> {
self.0.read().base.set_length(activation, length)
}
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) {
self.0.read().base.delete_array_element(index, gc_context)
fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
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>> {

View File

@ -252,30 +252,38 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
TYPE_OF_OBJECT
}
fn length(&self) -> usize {
0
fn length(&self, _activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
Ok(0)
}
fn set_length(&self, _gc_context: MutationContext<'gc, '_>, _new_length: usize) {}
fn array(&self) -> Vec<Value<'gc>> {
vec![]
fn set_length(
&self,
_activation: &mut Activation<'_, 'gc, '_>,
_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
}
fn set_array_element(
fn set_element(
&self,
_index: usize,
_activation: &mut Activation<'_, 'gc, '_>,
_index: i32,
_value: Value<'gc>,
_gc_context: MutationContext<'gc, '_>,
) -> usize {
0
) -> Result<(), Error<'gc>> {
Ok(())
}
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>> {
//`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
}
fn length(&self) -> usize {
self.base().length()
fn length(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
self.base().length(activation)
}
fn array(&self) -> Vec<Value<'gc>> {
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(
fn set_length(
&self,
index: usize,
value: Value<'gc>,
gc_context: MutationContext<'gc, '_>,
) -> usize {
self.base().set_array_element(index, value, gc_context)
activation: &mut Activation<'_, 'gc, '_>,
length: i32,
) -> Result<(), Error<'gc>> {
self.base().set_length(activation, length)
}
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) {
self.base().delete_array_element(index, gc_context)
fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
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
}
fn length(&self) -> usize {
self.base().length()
fn length(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
self.base().length(activation)
}
fn array(&self) -> Vec<Value<'gc>> {
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(
fn set_length(
&self,
index: usize,
value: Value<'gc>,
gc_context: MutationContext<'gc, '_>,
) -> usize {
self.base().set_array_element(index, value, gc_context)
activation: &mut Activation<'_, 'gc, '_>,
length: i32,
) -> Result<(), Error<'gc>> {
self.base().set_length(activation, length)
}
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) {
self.base().delete_array_element(index, gc_context)
fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
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 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
// 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 {
log::warn!(
"_listeners should be empty, but its length is {}",
listeners.length()
);
log::warn!("_listeners should be empty");
}
}
}

View File

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

View File

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