avm1: Rewrite TObject array methods
This commit is contained in:
parent
3b215d6c76
commit
9c5b9b7072
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
})
|
}
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(&separator),
|
let parts: Result<Vec<_>, Error<'gc>> = (0..length)
|
||||||
)
|
.map(|i| {
|
||||||
.into())
|
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).
|
/// 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);
|
return Ok(Value::Undefined);
|
||||||
let count = args
|
}
|
||||||
.get(1)
|
delete_count.min(length - start)
|
||||||
.and_then(|v| v.coerce_to_f64(activation).ok())
|
} else {
|
||||||
.map(|v| v as i32)
|
length - start
|
||||||
.unwrap_or(old_length as i32);
|
};
|
||||||
if count < 0 {
|
|
||||||
return Ok(Value::Undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
} else {
|
||||||
array.set_array_element(length, old, activation.context.gc_context);
|
None
|
||||||
length += 1;
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
if !added {
|
index += 1;
|
||||||
array.set_array_element(length, *arg, activation.context.gc_context);
|
|
||||||
length += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(array.into())
|
Ok(result_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);
|
activation.context.gc_context,
|
||||||
for (i, value) in values.into_iter().enumerate() {
|
Some(activation.context.avm1.prototypes.array),
|
||||||
array.set_array_element(
|
);
|
||||||
i,
|
for (i, (index, _)) in values.into_iter().enumerate() {
|
||||||
Value::Number(value.0 as f64),
|
array
|
||||||
activation.context.gc_context,
|
.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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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,46 +227,51 @@ 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) {
|
||||||
if let Value::Object(item) = item {
|
for i in 0..length {
|
||||||
let caption =
|
let item = custom_items.get_element(activation, i);
|
||||||
if let Ok(Value::String(caption)) = item.get("caption", activation) {
|
if let Value::Object(item) = item {
|
||||||
caption
|
let caption =
|
||||||
} else {
|
if let Ok(Value::String(caption)) = item.get("caption", activation) {
|
||||||
continue;
|
caption
|
||||||
};
|
} else {
|
||||||
let on_select =
|
continue;
|
||||||
if let Ok(Value::Object(on_select)) = item.get("onSelect", activation) {
|
};
|
||||||
|
let on_select = if let Ok(Value::Object(on_select)) =
|
||||||
|
item.get("onSelect", activation)
|
||||||
|
{
|
||||||
on_select
|
on_select
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
// false if `false`, everything else is true
|
// false if `false`, everything else is true
|
||||||
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 =
|
||||||
let separator_before = matches!(
|
matches!(item.get("enabled", activation), Ok(Value::Bool(true)));
|
||||||
item.get("separatorBefore", activation),
|
let separator_before = matches!(
|
||||||
Ok(Value::Bool(true))
|
item.get("separatorBefore", activation),
|
||||||
);
|
Ok(Value::Bool(true))
|
||||||
|
);
|
||||||
|
|
||||||
if !visible {
|
if !visible {
|
||||||
continue;
|
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,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
color: Color::from_rgb(rgb, (alpha / 100.0 * 255.0) as u8),
|
.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 matrix = gradient_object_to_matrix(matrix_object, activation)?;
|
||||||
let spread = match args
|
let spread = match args
|
||||||
.get(5)
|
.get(5)
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(
|
||||||
AvmString::new(activation.context.gc_context, token.to_string()).into(),
|
activation,
|
||||||
activation.context.gc_context,
|
i as i32,
|
||||||
);
|
AvmString::new(activation.context.gc_context, token.to_string()).into(),
|
||||||
|
)
|
||||||
|
.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(
|
||||||
AvmString::new(activation.context.gc_context, token.to_string()).into(),
|
activation,
|
||||||
activation.context.gc_context,
|
i as i32,
|
||||||
);
|
AvmString::new(activation.context.gc_context, token.to_string()).into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(array.into())
|
Ok(array.into())
|
||||||
|
|
|
@ -368,16 +368,18 @@ pub fn xmlnode_child_nodes<'gc>(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
array.set_array_element(
|
array
|
||||||
compatible_nodes as usize,
|
.set_element(
|
||||||
child
|
activation,
|
||||||
.script_object(
|
compatible_nodes,
|
||||||
activation.context.gc_context,
|
child
|
||||||
Some(activation.context.avm1.prototypes.xml_node),
|
.script_object(
|
||||||
)
|
activation.context.gc_context,
|
||||||
.into(),
|
Some(activation.context.avm1.prototypes.xml_node),
|
||||||
activation.context.gc_context,
|
)
|
||||||
);
|
.into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
compatible_nodes += 1;
|
compatible_nodes += 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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) => {
|
||||||
let old_length = vector.len();
|
if new_length >= 0 {
|
||||||
vector.resize(new_length, Value::Undefined);
|
let old_length = vector.len();
|
||||||
if new_length < old_length {
|
let new_length = new_length as usize;
|
||||||
to_remove = Some(new_length..old_length);
|
vector.resize(new_length, Value::Undefined);
|
||||||
|
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,36 +663,59 @@ 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 >= vector.len() {
|
if index >= 0 {
|
||||||
vector.resize(index + 1, Value::Undefined);
|
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 {
|
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 {
|
||||||
if index < vector.len() {
|
ArrayStorage::Vector(vector) => {
|
||||||
vector[index] = Value::Undefined;
|
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()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>> {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue