core: Made arrays a storage property of objects, not a unique object type. Added more corner case tests.
This commit is contained in:
parent
3bdf710af6
commit
31b84c5f19
|
@ -18,7 +18,6 @@ use crate::tag_utils::SwfSlice;
|
||||||
mod test_utils;
|
mod test_utils;
|
||||||
|
|
||||||
mod activation;
|
mod activation;
|
||||||
pub mod array_object;
|
|
||||||
mod fscommand;
|
mod fscommand;
|
||||||
mod function;
|
mod function;
|
||||||
pub mod globals;
|
pub mod globals;
|
||||||
|
@ -34,8 +33,6 @@ mod value;
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use activation::Activation;
|
use activation::Activation;
|
||||||
pub use array_object::ArrayObject;
|
|
||||||
use enumset::EnumSet;
|
|
||||||
pub use globals::SystemPrototypes;
|
pub use globals::SystemPrototypes;
|
||||||
pub use object::{Object, ObjectPtr, TObject};
|
pub use object::{Object, ObjectPtr, TObject};
|
||||||
use scope::Scope;
|
use scope::Scope;
|
||||||
|
@ -1282,19 +1279,12 @@ impl<'gc> Avm1<'gc> {
|
||||||
|
|
||||||
fn action_init_array(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
fn action_init_array(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
||||||
let num_elements = self.pop()?.as_i64()?;
|
let num_elements = self.pop()?.as_i64()?;
|
||||||
let array = ArrayObject::array(context.gc_context, Some(self.prototypes.array));
|
let array = ScriptObject::array(context.gc_context, Some(self.prototypes.array));
|
||||||
|
|
||||||
for i in 0..num_elements {
|
for i in 0..num_elements {
|
||||||
array.define_value(
|
array.set_array_element(i as usize, self.pop()?, context.gc_context);
|
||||||
context.gc_context,
|
|
||||||
&(i as i32).to_string(),
|
|
||||||
self.pop()?,
|
|
||||||
EnumSet::empty(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
array.set_length(context.gc_context, num_elements as i32);
|
|
||||||
|
|
||||||
self.push(Value::Object(array.into()));
|
self.push(Value::Object(array.into()));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,191 +0,0 @@
|
||||||
use crate::avm1::function::Executable;
|
|
||||||
use crate::avm1::property::Attribute;
|
|
||||||
use crate::avm1::return_value::ReturnValue;
|
|
||||||
use crate::avm1::{Avm1, Error, Object, ObjectPtr, ScriptObject, TObject, Value};
|
|
||||||
use crate::context::UpdateContext;
|
|
||||||
use crate::display_object::DisplayObject;
|
|
||||||
|
|
||||||
use enumset::EnumSet;
|
|
||||||
use gc_arena::{Collect, GcCell, MutationContext};
|
|
||||||
use std::collections::hash_map::RandomState;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Collect)]
|
|
||||||
#[collect(no_drop)]
|
|
||||||
pub struct ArrayObject<'gc>(GcCell<'gc, ArrayObjectData<'gc>>);
|
|
||||||
|
|
||||||
#[derive(Debug, Collect)]
|
|
||||||
#[collect(no_drop)]
|
|
||||||
pub struct ArrayObjectData<'gc> {
|
|
||||||
base: ScriptObject<'gc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_length<'gc>(
|
|
||||||
_avm: &mut Avm1<'gc>,
|
|
||||||
_action_context: &mut UpdateContext<'_, 'gc, '_>,
|
|
||||||
this: Object<'gc>,
|
|
||||||
_args: &[Value<'gc>],
|
|
||||||
) -> Result<ReturnValue<'gc>, Error> {
|
|
||||||
Ok(this.get_length().into())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'gc> ArrayObject<'gc> {
|
|
||||||
pub fn array(
|
|
||||||
gc_context: MutationContext<'gc, '_>,
|
|
||||||
proto: Option<Object<'gc>>,
|
|
||||||
) -> ArrayObject<'gc> {
|
|
||||||
let base = ScriptObject::object(gc_context, proto);
|
|
||||||
base.add_property(
|
|
||||||
gc_context,
|
|
||||||
"length",
|
|
||||||
Executable::Native(get_length),
|
|
||||||
None,
|
|
||||||
Attribute::DontDelete | Attribute::DontEnum,
|
|
||||||
);
|
|
||||||
|
|
||||||
ArrayObject(GcCell::allocate(gc_context, ArrayObjectData { base }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'gc> TObject<'gc> for ArrayObject<'gc> {
|
|
||||||
fn get_local(
|
|
||||||
&self,
|
|
||||||
name: &str,
|
|
||||||
avm: &mut Avm1<'gc>,
|
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
||||||
this: Object<'gc>,
|
|
||||||
) -> Result<ReturnValue<'gc>, Error> {
|
|
||||||
self.0.read().base.get_local(name, avm, context, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set(
|
|
||||||
&self,
|
|
||||||
name: &str,
|
|
||||||
value: Value<'gc>,
|
|
||||||
avm: &mut Avm1<'gc>,
|
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
if let Ok(index) = name.parse::<i32>() {
|
|
||||||
if index >= self.get_length() {
|
|
||||||
self.set_length(context.gc_context, index + 1);
|
|
||||||
}
|
|
||||||
} else if name == "length" {
|
|
||||||
self.set_length(
|
|
||||||
context.gc_context,
|
|
||||||
value
|
|
||||||
.as_number(avm, context)
|
|
||||||
.map(|v| v.abs() as i32)
|
|
||||||
.unwrap_or(0),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.0.read().base.set(name, value, avm, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(
|
|
||||||
&self,
|
|
||||||
avm: &mut Avm1<'gc>,
|
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
||||||
this: Object<'gc>,
|
|
||||||
args: &[Value<'gc>],
|
|
||||||
) -> Result<ReturnValue<'gc>, Error> {
|
|
||||||
self.0.read().base.call(avm, context, this, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::new_ret_no_self)]
|
|
||||||
fn new(
|
|
||||||
&self,
|
|
||||||
_avm: &mut Avm1<'gc>,
|
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
||||||
this: Object<'gc>,
|
|
||||||
_args: &[Value<'gc>],
|
|
||||||
) -> Result<Object<'gc>, Error> {
|
|
||||||
Ok(ArrayObject::array(context.gc_context, Some(this)).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete(&self, gc_context: MutationContext<'gc, '_>, name: &str) -> bool {
|
|
||||||
self.0.read().base.delete(gc_context, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn proto(&self) -> Option<Object<'gc>> {
|
|
||||||
self.0.read().base.proto()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn define_value(
|
|
||||||
&self,
|
|
||||||
gc_context: MutationContext<'gc, '_>,
|
|
||||||
name: &str,
|
|
||||||
value: Value<'gc>,
|
|
||||||
attributes: EnumSet<Attribute>,
|
|
||||||
) {
|
|
||||||
self.0
|
|
||||||
.read()
|
|
||||||
.base
|
|
||||||
.define_value(gc_context, name, value, attributes)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_property(
|
|
||||||
&self,
|
|
||||||
gc_context: MutationContext<'gc, '_>,
|
|
||||||
name: &str,
|
|
||||||
get: Executable<'gc>,
|
|
||||||
set: Option<Executable<'gc>>,
|
|
||||||
attributes: EnumSet<Attribute>,
|
|
||||||
) {
|
|
||||||
self.0
|
|
||||||
.read()
|
|
||||||
.base
|
|
||||||
.add_property(gc_context, name, get, set, attributes)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_property(&self, name: &str) -> bool {
|
|
||||||
self.0.read().base.has_property(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_own_property(&self, name: &str) -> bool {
|
|
||||||
self.0.read().base.has_own_property(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_property_overwritable(&self, name: &str) -> bool {
|
|
||||||
self.0.read().base.is_property_overwritable(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_property_enumerable(&self, name: &str) -> bool {
|
|
||||||
self.0.read().base.is_property_enumerable(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_keys(&self) -> HashSet<String, RandomState> {
|
|
||||||
self.0.read().base.get_keys()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_length(&self) -> i32 {
|
|
||||||
self.0.read().base.get_length()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_length(&self, gc_context: MutationContext<'gc, '_>, length: i32) {
|
|
||||||
self.0.read().base.set_length(gc_context, length)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_string(&self) -> String {
|
|
||||||
self.0.read().base.as_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn type_of(&self) -> &'static str {
|
|
||||||
self.0.read().base.type_of()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_script_object(&self) -> Option<ScriptObject<'gc>> {
|
|
||||||
self.0.read().base.as_script_object()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_display_object(&self) -> Option<DisplayObject<'gc>> {
|
|
||||||
self.0.read().base.as_display_object()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_executable(&self) -> Option<Executable<'gc>> {
|
|
||||||
self.0.read().base.as_executable()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_ptr(&self) -> *const ObjectPtr {
|
|
||||||
self.0.read().base.as_ptr()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@
|
||||||
use crate::avm1::property::Attribute;
|
use crate::avm1::property::Attribute;
|
||||||
|
|
||||||
use crate::avm1::return_value::ReturnValue;
|
use crate::avm1::return_value::ReturnValue;
|
||||||
use crate::avm1::{ArrayObject, Avm1, Error, Object, TObject, UpdateContext, Value};
|
use crate::avm1::{Avm1, Error, Object, ScriptObject, TObject, UpdateContext, Value};
|
||||||
|
|
||||||
use enumset::EnumSet;
|
use enumset::EnumSet;
|
||||||
use gc_arena::MutationContext;
|
use gc_arena::MutationContext;
|
||||||
|
@ -20,10 +20,12 @@ pub fn constructor<'gc>(
|
||||||
if args.len() == 1 {
|
if args.len() == 1 {
|
||||||
let arg = args.get(0).unwrap();
|
let arg = args.get(0).unwrap();
|
||||||
if let Ok(length) = arg.as_number(avm, context) {
|
if let Ok(length) = arg.as_number(avm, context) {
|
||||||
this.set_length(context.gc_context, length as i32);
|
if length >= 0.0 {
|
||||||
|
this.set_length(context.gc_context, length as usize);
|
||||||
consumed = true;
|
consumed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !consumed {
|
if !consumed {
|
||||||
let mut length = 0;
|
let mut length = 0;
|
||||||
|
@ -49,80 +51,67 @@ pub fn push<'gc>(
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Result<ReturnValue<'gc>, Error> {
|
) -> Result<ReturnValue<'gc>, Error> {
|
||||||
let old_length = this.get_length();
|
let old_length = this.get_length();
|
||||||
let new_length = old_length + args.len() as i32;
|
let new_length = old_length + args.len();
|
||||||
|
this.set_length(context.gc_context, new_length);
|
||||||
|
|
||||||
for i in 0..args.len() {
|
for i in 0..args.len() {
|
||||||
this.define_value(
|
this.set_array_element(
|
||||||
context.gc_context,
|
old_length + i,
|
||||||
&(old_length + i as i32).to_string(),
|
|
||||||
args.get(i).unwrap().to_owned(),
|
args.get(i).unwrap().to_owned(),
|
||||||
EnumSet::empty(),
|
context.gc_context,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.set_length(context.gc_context, new_length);
|
Ok((new_length as f64).into())
|
||||||
|
|
||||||
Ok(new_length.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unshift<'gc>(
|
pub fn unshift<'gc>(
|
||||||
avm: &mut Avm1<'gc>,
|
_avm: &mut Avm1<'gc>,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Result<ReturnValue<'gc>, Error> {
|
) -> Result<ReturnValue<'gc>, Error> {
|
||||||
let old_length = this.get_length();
|
let old_length = this.get_length();
|
||||||
let new_length = old_length + args.len() as i32;
|
let new_length = old_length + args.len();
|
||||||
let offset = new_length - old_length;
|
let offset = new_length - old_length;
|
||||||
|
|
||||||
for i in (old_length - 1..new_length).rev() {
|
for i in (old_length - 1..new_length).rev() {
|
||||||
let old = this
|
this.set_array_element(
|
||||||
.get(&(i - offset).to_string(), avm, context)
|
i,
|
||||||
.and_then(|v| v.resolve(avm, context))
|
this.get_array_element(dbg!(i - offset)),
|
||||||
.unwrap_or(Value::Undefined);
|
context.gc_context,
|
||||||
this.define_value(context.gc_context, &i.to_string(), old, EnumSet::empty());
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..args.len() {
|
for i in 0..args.len() {
|
||||||
this.define_value(
|
this.set_array_element(i, args.get(i).unwrap().to_owned(), context.gc_context);
|
||||||
context.gc_context,
|
|
||||||
&(i as i32).to_string(),
|
|
||||||
args.get(i).unwrap().to_owned(),
|
|
||||||
EnumSet::empty(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.set_length(context.gc_context, new_length);
|
this.set_length(context.gc_context, new_length);
|
||||||
|
|
||||||
Ok(new_length.into())
|
Ok((new_length as f64).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shift<'gc>(
|
pub fn shift<'gc>(
|
||||||
avm: &mut Avm1<'gc>,
|
_avm: &mut Avm1<'gc>,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<ReturnValue<'gc>, Error> {
|
) -> Result<ReturnValue<'gc>, Error> {
|
||||||
let old_length = this.get_length();
|
let old_length = this.get_length();
|
||||||
if old_length <= 0 {
|
if old_length == 0 {
|
||||||
return Ok(Value::Undefined.into());
|
return Ok(Value::Undefined.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_length = old_length - 1;
|
let new_length = old_length - 1;
|
||||||
|
|
||||||
let removed = this
|
let removed = this.get_array_element(0);
|
||||||
.get("0", avm, context)
|
|
||||||
.and_then(|v| v.resolve(avm, context))
|
|
||||||
.unwrap_or(Value::Undefined);
|
|
||||||
|
|
||||||
for i in 0..new_length {
|
for i in 0..new_length {
|
||||||
let old = this
|
this.set_array_element(i, this.get_array_element(i + 1), context.gc_context);
|
||||||
.get(&(i + 1).to_string(), avm, context)
|
|
||||||
.and_then(|v| v.resolve(avm, context))
|
|
||||||
.unwrap_or(Value::Undefined);
|
|
||||||
this.define_value(context.gc_context, &i.to_string(), old, EnumSet::empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.delete_array_element(new_length, context.gc_context);
|
||||||
this.delete(context.gc_context, &new_length.to_string());
|
this.delete(context.gc_context, &new_length.to_string());
|
||||||
|
|
||||||
this.set_length(context.gc_context, new_length);
|
this.set_length(context.gc_context, new_length);
|
||||||
|
@ -131,22 +120,20 @@ pub fn shift<'gc>(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop<'gc>(
|
pub fn pop<'gc>(
|
||||||
avm: &mut Avm1<'gc>,
|
_avm: &mut Avm1<'gc>,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<ReturnValue<'gc>, Error> {
|
) -> Result<ReturnValue<'gc>, Error> {
|
||||||
let old_length = this.get_length();
|
let old_length = this.get_length();
|
||||||
if old_length <= 0 {
|
if old_length == 0 {
|
||||||
return Ok(Value::Undefined.into());
|
return Ok(Value::Undefined.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_length = old_length - 1;
|
let new_length = old_length - 1;
|
||||||
|
|
||||||
let removed = this
|
let removed = this.get_array_element(new_length);
|
||||||
.get(&new_length.to_string(), avm, context)
|
this.delete_array_element(new_length, context.gc_context);
|
||||||
.and_then(|v| v.resolve(avm, context))
|
|
||||||
.unwrap_or(Value::Undefined);
|
|
||||||
this.delete(context.gc_context, &new_length.to_string());
|
this.delete(context.gc_context, &new_length.to_string());
|
||||||
|
|
||||||
this.set_length(context.gc_context, new_length);
|
this.set_length(context.gc_context, new_length);
|
||||||
|
@ -155,29 +142,16 @@ pub fn pop<'gc>(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reverse<'gc>(
|
pub fn reverse<'gc>(
|
||||||
avm: &mut Avm1<'gc>,
|
_avm: &mut Avm1<'gc>,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<ReturnValue<'gc>, Error> {
|
) -> Result<ReturnValue<'gc>, Error> {
|
||||||
let length = this.get_length();
|
let length = this.get_length();
|
||||||
let mut values = Vec::with_capacity(length as usize);
|
let mut values = this.get_array().to_vec();
|
||||||
|
|
||||||
for i in 0..length {
|
for i in 0..length {
|
||||||
values.push(
|
this.set_array_element(i, values.pop().unwrap(), context.gc_context);
|
||||||
this.get(&i.to_string(), avm, context)
|
|
||||||
.and_then(|v| v.resolve(avm, context))
|
|
||||||
.unwrap_or(Value::Undefined),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0..length {
|
|
||||||
this.define_value(
|
|
||||||
context.gc_context,
|
|
||||||
&i.to_string(),
|
|
||||||
values.pop().unwrap(),
|
|
||||||
EnumSet::empty(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Undefined.into())
|
Ok(Value::Undefined.into())
|
||||||
|
@ -189,27 +163,33 @@ pub fn join<'gc>(
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Result<ReturnValue<'gc>, Error> {
|
) -> Result<ReturnValue<'gc>, Error> {
|
||||||
let length = this.get_length();
|
|
||||||
if length < 0 {
|
|
||||||
return Ok("".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let separator = args
|
let separator = args
|
||||||
.get(0)
|
.get(0)
|
||||||
.and_then(|v| v.to_owned().coerce_to_string(avm, context).ok())
|
.and_then(|v| v.to_owned().coerce_to_string(avm, context).ok())
|
||||||
.unwrap_or_else(|| ",".to_owned());
|
.unwrap_or_else(|| ",".to_owned());
|
||||||
let mut values = Vec::with_capacity(length as usize);
|
let values: Vec<Value<'gc>> = this.get_array();
|
||||||
|
|
||||||
for i in 0..length {
|
Ok(values
|
||||||
values.push(
|
.iter()
|
||||||
this.get(&i.to_string(), avm, context)
|
.map(|v| {
|
||||||
.and_then(|v| v.resolve(avm, context))
|
v.to_owned()
|
||||||
.and_then(|v| v.coerce_to_string(avm, context))
|
.coerce_to_string(avm, context)
|
||||||
.unwrap_or_else(|_| "undefined".to_string()),
|
.unwrap_or_else(|_| "undefined".to_string())
|
||||||
);
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(&separator)
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(values.join(&separator).into())
|
fn make_index_absolute(mut index: i32, length: usize) -> usize {
|
||||||
|
if index < 0 {
|
||||||
|
index += length as i32;
|
||||||
|
}
|
||||||
|
if index < 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
index as usize
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn slice<'gc>(
|
pub fn slice<'gc>(
|
||||||
|
@ -218,34 +198,26 @@ pub fn slice<'gc>(
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Result<ReturnValue<'gc>, Error> {
|
) -> Result<ReturnValue<'gc>, Error> {
|
||||||
let mut start = args
|
let start = args
|
||||||
.get(0)
|
.get(0)
|
||||||
.and_then(|v| v.as_number(avm, context).ok())
|
.and_then(|v| v.as_number(avm, context).ok())
|
||||||
.map(|v| v as i32)
|
.map(|v| make_index_absolute(v as i32, this.get_length()))
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let mut end = args
|
let end = args
|
||||||
.get(1)
|
.get(1)
|
||||||
.and_then(|v| v.as_number(avm, context).ok())
|
.and_then(|v| v.as_number(avm, context).ok())
|
||||||
.map(|v| v as i32)
|
.map(|v| make_index_absolute(v as i32, this.get_length()))
|
||||||
.unwrap_or_else(|| this.get_length());
|
.unwrap_or_else(|| this.get_length());
|
||||||
|
|
||||||
if start < 0 {
|
let array = ScriptObject::array(context.gc_context, Some(avm.prototypes.array));
|
||||||
start += this.get_length();
|
|
||||||
}
|
|
||||||
if end < 0 {
|
|
||||||
end += this.get_length();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if start < end {
|
||||||
let length = end - start;
|
let length = end - start;
|
||||||
let array = ArrayObject::array(context.gc_context, Some(avm.prototypes.array));
|
|
||||||
array.set_length(context.gc_context, length);
|
array.set_length(context.gc_context, length);
|
||||||
|
|
||||||
for i in 0..length {
|
for i in 0..length {
|
||||||
let old = this
|
array.set_array_element(i, this.get_array_element(start + i), context.gc_context);
|
||||||
.get(&(start + i).to_string(), avm, context)
|
}
|
||||||
.and_then(|v| v.resolve(avm, context))
|
|
||||||
.unwrap_or(Value::Undefined);
|
|
||||||
array.define_value(context.gc_context, &i.to_string(), old, EnumSet::empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::Object(array.into()).into())
|
Ok(Value::Object(array.into()).into())
|
||||||
|
@ -257,7 +229,7 @@ pub fn concat<'gc>(
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Result<ReturnValue<'gc>, Error> {
|
) -> Result<ReturnValue<'gc>, Error> {
|
||||||
let array = ArrayObject::array(context.gc_context, Some(avm.prototypes.array));
|
let array = ScriptObject::array(context.gc_context, Some(avm.prototypes.array));
|
||||||
let mut length = 0;
|
let mut length = 0;
|
||||||
|
|
||||||
for i in 0..this.get_length() {
|
for i in 0..this.get_length() {
|
||||||
|
@ -327,7 +299,7 @@ pub fn create_proto<'gc>(
|
||||||
proto: Object<'gc>,
|
proto: Object<'gc>,
|
||||||
fn_proto: Object<'gc>,
|
fn_proto: Object<'gc>,
|
||||||
) -> Object<'gc> {
|
) -> Object<'gc> {
|
||||||
let array = ArrayObject::array(gc_context, Some(proto));
|
let array = ScriptObject::array(gc_context, Some(proto));
|
||||||
let mut object = array.as_script_object().unwrap();
|
let mut object = array.as_script_object().unwrap();
|
||||||
|
|
||||||
object.force_set_function(
|
object.force_set_function(
|
||||||
|
|
|
@ -17,23 +17,26 @@ pub fn constructor<'gc>(
|
||||||
|
|
||||||
/// Implements `Object.prototype.addProperty`
|
/// Implements `Object.prototype.addProperty`
|
||||||
pub fn add_property<'gc>(
|
pub fn add_property<'gc>(
|
||||||
_avm: &mut Avm1<'gc>,
|
avm: &mut Avm1<'gc>,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Result<ReturnValue<'gc>, Error> {
|
) -> Result<ReturnValue<'gc>, Error> {
|
||||||
let name = args.get(0).unwrap_or(&Value::Undefined);
|
let name = args
|
||||||
|
.get(0)
|
||||||
|
.and_then(|v| v.to_owned().coerce_to_string(avm, context).ok())
|
||||||
|
.unwrap_or_else(|| "undefined".to_string());
|
||||||
let getter = args.get(1).unwrap_or(&Value::Undefined);
|
let getter = args.get(1).unwrap_or(&Value::Undefined);
|
||||||
let setter = args.get(2).unwrap_or(&Value::Undefined);
|
let setter = args.get(2).unwrap_or(&Value::Undefined);
|
||||||
|
|
||||||
match (name, getter) {
|
match getter {
|
||||||
(Value::String(name), Value::Object(get)) if !name.is_empty() => {
|
Value::Object(get) if !name.is_empty() => {
|
||||||
if let Some(get_func) = get.as_executable() {
|
if let Some(get_func) = get.as_executable() {
|
||||||
if let Value::Object(set) = setter {
|
if let Value::Object(set) = setter {
|
||||||
if let Some(set_func) = set.as_executable() {
|
if let Some(set_func) = set.as_executable() {
|
||||||
this.add_property(
|
this.add_property(
|
||||||
context.gc_context,
|
context.gc_context,
|
||||||
name,
|
&name,
|
||||||
get_func,
|
get_func,
|
||||||
Some(set_func),
|
Some(set_func),
|
||||||
EnumSet::empty(),
|
EnumSet::empty(),
|
||||||
|
@ -42,7 +45,7 @@ pub fn add_property<'gc>(
|
||||||
return Ok(false.into());
|
return Ok(false.into());
|
||||||
}
|
}
|
||||||
} else if let Value::Null = setter {
|
} else if let Value::Null = setter {
|
||||||
this.add_property(context.gc_context, name, get_func, None, ReadOnly.into());
|
this.add_property(context.gc_context, &name, get_func, None, ReadOnly.into());
|
||||||
} else {
|
} else {
|
||||||
return Ok(false.into());
|
return Ok(false.into());
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use crate::avm1::function::Executable;
|
use crate::avm1::function::Executable;
|
||||||
use crate::avm1::property::Attribute;
|
use crate::avm1::property::Attribute;
|
||||||
use crate::avm1::return_value::ReturnValue;
|
use crate::avm1::return_value::ReturnValue;
|
||||||
use crate::avm1::{ArrayObject, Avm1, Error, ScriptObject, StageObject, UpdateContext, Value};
|
use crate::avm1::{Avm1, Error, ScriptObject, StageObject, UpdateContext, Value};
|
||||||
use crate::display_object::DisplayObject;
|
use crate::display_object::DisplayObject;
|
||||||
use enumset::EnumSet;
|
use enumset::EnumSet;
|
||||||
use gc_arena::{Collect, MutationContext};
|
use gc_arena::{Collect, MutationContext};
|
||||||
|
@ -19,7 +19,6 @@ use std::fmt::Debug;
|
||||||
pub enum Object<'gc> {
|
pub enum Object<'gc> {
|
||||||
ScriptObject(ScriptObject<'gc>),
|
ScriptObject(ScriptObject<'gc>),
|
||||||
StageObject(StageObject<'gc>),
|
StageObject(StageObject<'gc>),
|
||||||
ArrayObject(ArrayObject<'gc>),
|
|
||||||
}
|
}
|
||||||
)]
|
)]
|
||||||
pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy {
|
pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy {
|
||||||
|
@ -160,12 +159,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
||||||
/// Enumerate the object.
|
/// Enumerate the object.
|
||||||
fn get_keys(&self) -> HashSet<String>;
|
fn get_keys(&self) -> HashSet<String>;
|
||||||
|
|
||||||
/// Get the length of this object, as if it were an array.
|
|
||||||
fn get_length(&self) -> i32;
|
|
||||||
|
|
||||||
/// Sets the length of this object, as if it were an array.
|
|
||||||
fn set_length(&self, gc_context: MutationContext<'gc, '_>, length: i32);
|
|
||||||
|
|
||||||
/// Coerce the object into a string.
|
/// Coerce the object into a string.
|
||||||
fn as_string(&self) -> String;
|
fn as_string(&self) -> String;
|
||||||
|
|
||||||
|
@ -197,6 +190,41 @@ 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.
|
||||||
|
fn get_length(&self) -> usize;
|
||||||
|
|
||||||
|
/// Gets a copy of the array storage behind this object.
|
||||||
|
fn get_array(&self) -> Vec<Value<'gc>>;
|
||||||
|
|
||||||
|
/// Sets the length of this object, as if it were an array.
|
||||||
|
///
|
||||||
|
/// Increasing this value will fill the gap with Value::Undefined.
|
||||||
|
/// Decreasing this value will remove affected items from both the array and properties storage.
|
||||||
|
fn set_length(&self, gc_context: MutationContext<'gc, '_>, length: usize);
|
||||||
|
|
||||||
|
/// Gets a property of this object as if it were an array.
|
||||||
|
///
|
||||||
|
/// Array element lookups do not respect the prototype chain, and will ignore virtual properties.
|
||||||
|
fn get_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,
|
||||||
|
index: usize,
|
||||||
|
value: Value<'gc>,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
) -> usize;
|
||||||
|
|
||||||
|
/// Deletes a property of this object as if it were an array.
|
||||||
|
///
|
||||||
|
/// This will not rearrange the array or adjust the length, nor will it affect the properties
|
||||||
|
/// storage.
|
||||||
|
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ObjectPtr {}
|
pub enum ObjectPtr {}
|
||||||
|
|
|
@ -12,6 +12,13 @@ use std::collections::{HashMap, HashSet};
|
||||||
pub const TYPE_OF_OBJECT: &str = "object";
|
pub const TYPE_OF_OBJECT: &str = "object";
|
||||||
pub const TYPE_OF_FUNCTION: &str = "function";
|
pub const TYPE_OF_FUNCTION: &str = "function";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Collect)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
pub enum ArrayStorage<'gc> {
|
||||||
|
Vector(Vec<Value<'gc>>),
|
||||||
|
Properties { length: usize },
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Collect)]
|
#[derive(Debug, Copy, Clone, Collect)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
|
pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
|
||||||
|
@ -21,7 +28,7 @@ pub struct ScriptObjectData<'gc> {
|
||||||
values: HashMap<String, Property<'gc>>,
|
values: HashMap<String, Property<'gc>>,
|
||||||
function: Option<Executable<'gc>>,
|
function: Option<Executable<'gc>>,
|
||||||
type_of: &'static str,
|
type_of: &'static str,
|
||||||
length: i32,
|
array: ArrayStorage<'gc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<'gc> Collect for ScriptObjectData<'gc> {
|
unsafe impl<'gc> Collect for ScriptObjectData<'gc> {
|
||||||
|
@ -29,6 +36,7 @@ unsafe impl<'gc> Collect for ScriptObjectData<'gc> {
|
||||||
self.prototype.trace(cc);
|
self.prototype.trace(cc);
|
||||||
self.values.trace(cc);
|
self.values.trace(cc);
|
||||||
self.function.trace(cc);
|
self.function.trace(cc);
|
||||||
|
self.array.trace(cc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +46,7 @@ impl fmt::Debug for ScriptObjectData<'_> {
|
||||||
.field("prototype", &self.prototype)
|
.field("prototype", &self.prototype)
|
||||||
.field("values", &self.values)
|
.field("values", &self.values)
|
||||||
.field("function", &self.function.is_some())
|
.field("function", &self.function.is_some())
|
||||||
.field("length", &self.length)
|
.field("array", &self.array)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,11 +63,29 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
type_of: TYPE_OF_OBJECT,
|
type_of: TYPE_OF_OBJECT,
|
||||||
values: HashMap::new(),
|
values: HashMap::new(),
|
||||||
function: None,
|
function: None,
|
||||||
length: 0,
|
array: ArrayStorage::Properties { length: 0 },
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn array(
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
proto: Option<Object<'gc>>,
|
||||||
|
) -> ScriptObject<'gc> {
|
||||||
|
let object = ScriptObject(GcCell::allocate(
|
||||||
|
gc_context,
|
||||||
|
ScriptObjectData {
|
||||||
|
prototype: proto,
|
||||||
|
type_of: TYPE_OF_OBJECT,
|
||||||
|
values: HashMap::new(),
|
||||||
|
function: None,
|
||||||
|
array: ArrayStorage::Vector(Vec::new()),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
object.sync_native_property("length", gc_context, Some(0.into()));
|
||||||
|
object
|
||||||
|
}
|
||||||
|
|
||||||
/// Constructs and allocates an empty but normal object in one go.
|
/// Constructs and allocates an empty but normal object in one go.
|
||||||
pub fn object_cell(
|
pub fn object_cell(
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
@ -72,7 +98,7 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
type_of: TYPE_OF_OBJECT,
|
type_of: TYPE_OF_OBJECT,
|
||||||
values: HashMap::new(),
|
values: HashMap::new(),
|
||||||
function: None,
|
function: None,
|
||||||
length: 0,
|
array: ArrayStorage::Properties { length: 0 },
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.into()
|
.into()
|
||||||
|
@ -91,7 +117,7 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
type_of: TYPE_OF_OBJECT,
|
type_of: TYPE_OF_OBJECT,
|
||||||
values: HashMap::new(),
|
values: HashMap::new(),
|
||||||
function: None,
|
function: None,
|
||||||
length: 0,
|
array: ArrayStorage::Properties { length: 0 },
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -109,7 +135,7 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
type_of: TYPE_OF_FUNCTION,
|
type_of: TYPE_OF_FUNCTION,
|
||||||
function: Some(function.into()),
|
function: Some(function.into()),
|
||||||
values: HashMap::new(),
|
values: HashMap::new(),
|
||||||
length: 0,
|
array: ArrayStorage::Properties { length: 0 },
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -172,6 +198,37 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
pub fn set_type_of(&mut self, gc_context: MutationContext<'gc, '_>, type_of: &'static str) {
|
pub fn set_type_of(&mut self, gc_context: MutationContext<'gc, '_>, type_of: &'static str) {
|
||||||
self.0.write(gc_context).type_of = type_of;
|
self.0.write(gc_context).type_of = type_of;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||||
|
pub fn sync_native_property(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
native_value: Option<Value<'gc>>,
|
||||||
|
) {
|
||||||
|
match self.0.write(gc_context).values.entry(name.to_string()) {
|
||||||
|
Entry::Occupied(mut entry) => {
|
||||||
|
if let Property::Stored { value, .. } = entry.get_mut() {
|
||||||
|
match native_value {
|
||||||
|
None => {
|
||||||
|
entry.remove_entry();
|
||||||
|
}
|
||||||
|
Some(native_value) => {
|
||||||
|
*value = native_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
if let Some(native_value) = native_value {
|
||||||
|
entry.insert(Property::Stored {
|
||||||
|
value: native_value,
|
||||||
|
attributes: Attribute::DontEnum.into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||||
|
@ -215,7 +272,21 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if name == "__proto__" {
|
if name == "__proto__" {
|
||||||
self.0.write(context.gc_context).prototype = value.as_object().ok();
|
self.0.write(context.gc_context).prototype = value.as_object().ok();
|
||||||
|
} else if let Ok(index) = name.parse::<usize>() {
|
||||||
|
self.set_array_element(index, value.to_owned(), context.gc_context);
|
||||||
} else {
|
} else {
|
||||||
|
if name == "length" {
|
||||||
|
let length = value
|
||||||
|
.as_number(avm, context)
|
||||||
|
.map(|v| v.abs() as i32)
|
||||||
|
.unwrap_or(0);
|
||||||
|
if length > 0 {
|
||||||
|
self.set_length(context.gc_context, length as usize);
|
||||||
|
} else {
|
||||||
|
self.set_length(context.gc_context, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match self
|
match self
|
||||||
.0
|
.0
|
||||||
.write(context.gc_context)
|
.write(context.gc_context)
|
||||||
|
@ -264,8 +335,15 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Result<Object<'gc>, Error> {
|
) -> Result<Object<'gc>, Error> {
|
||||||
|
match self.0.read().array {
|
||||||
|
ArrayStorage::Vector(_) => {
|
||||||
|
Ok(ScriptObject::array(context.gc_context, Some(this)).into())
|
||||||
|
}
|
||||||
|
ArrayStorage::Properties { .. } => {
|
||||||
Ok(ScriptObject::object(context.gc_context, Some(this)).into())
|
Ok(ScriptObject::object(context.gc_context, Some(this)).into())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Delete a named property from the object.
|
/// Delete a named property from the object.
|
||||||
///
|
///
|
||||||
|
@ -375,16 +453,6 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the length of this object, as if it were an array.
|
|
||||||
fn get_length(&self) -> i32 {
|
|
||||||
self.0.read().length
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the length of this object, as if it were an array.
|
|
||||||
fn set_length(&self, gc_context: MutationContext<'gc, '_>, length: i32) {
|
|
||||||
self.0.write(gc_context).length = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_string(&self) -> String {
|
fn as_string(&self) -> String {
|
||||||
if self.0.read().function.is_some() {
|
if self.0.read().function.is_some() {
|
||||||
"[type Function]".to_string()
|
"[type Function]".to_string()
|
||||||
|
@ -417,6 +485,104 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||||
fn as_ptr(&self) -> *const ObjectPtr {
|
fn as_ptr(&self) -> *const ObjectPtr {
|
||||||
self.0.as_ptr() as *const ObjectPtr
|
self.0.as_ptr() as *const ObjectPtr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_length(&self) -> usize {
|
||||||
|
match &self.0.read().array {
|
||||||
|
ArrayStorage::Vector(vector) => vector.len(),
|
||||||
|
ArrayStorage::Properties { length } => *length,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_length(&self, gc_context: MutationContext<'gc, '_>, new_length: usize) {
|
||||||
|
let mut to_remove = None;
|
||||||
|
|
||||||
|
match &mut self.0.write(gc_context).array {
|
||||||
|
ArrayStorage::Vector(vector) => {
|
||||||
|
let old_length = vector.len();
|
||||||
|
vector.resize(new_length, Value::Undefined);
|
||||||
|
if new_length < old_length {
|
||||||
|
to_remove = Some(new_length..old_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ArrayStorage::Properties { length } => {
|
||||||
|
*length = new_length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(to_remove) = to_remove {
|
||||||
|
for i in to_remove {
|
||||||
|
self.sync_native_property(&i.to_string(), gc_context, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.sync_native_property("length", gc_context, Some(new_length.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_array(&self) -> Vec<Value<'gc>> {
|
||||||
|
match &self.0.read().array {
|
||||||
|
ArrayStorage::Vector(vector) => vector.to_owned(),
|
||||||
|
ArrayStorage::Properties { length } => {
|
||||||
|
let mut values = Vec::new();
|
||||||
|
for i in 0..*length {
|
||||||
|
values.push(self.get_array_element(i));
|
||||||
|
}
|
||||||
|
values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_array_element(&self, index: usize) -> Value<'gc> {
|
||||||
|
match &self.0.read().array {
|
||||||
|
ArrayStorage::Vector(vector) => {
|
||||||
|
if let Some(value) = vector.get(index) {
|
||||||
|
value.to_owned()
|
||||||
|
} else {
|
||||||
|
Value::Undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ArrayStorage::Properties { length } => {
|
||||||
|
if index < *length {
|
||||||
|
if let Some(Property::Stored { value, .. }) =
|
||||||
|
self.0.read().values.get(&index.to_string())
|
||||||
|
{
|
||||||
|
return value.to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_array_element(
|
||||||
|
&self,
|
||||||
|
index: usize,
|
||||||
|
value: Value<'gc>,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
) -> usize {
|
||||||
|
self.sync_native_property(&index.to_string(), gc_context, Some(value.clone()));
|
||||||
|
let mut adjust_length = false;
|
||||||
|
let length = match &mut self.0.write(gc_context).array {
|
||||||
|
ArrayStorage::Vector(vector) => {
|
||||||
|
if index >= vector.len() {
|
||||||
|
vector.resize(index + 1, Value::Undefined);
|
||||||
|
}
|
||||||
|
vector[index] = value.clone();
|
||||||
|
adjust_length = true;
|
||||||
|
vector.len()
|
||||||
|
}
|
||||||
|
ArrayStorage::Properties { length } => *length,
|
||||||
|
};
|
||||||
|
if adjust_length {
|
||||||
|
self.sync_native_property("length", gc_context, Some(length.into()));
|
||||||
|
}
|
||||||
|
length
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) {
|
||||||
|
if let ArrayStorage::Vector(vector) = &mut self.0.write(gc_context).array {
|
||||||
|
if index < vector.len() {
|
||||||
|
vector[index] = Value::Undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -199,12 +199,33 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
|
||||||
keys
|
keys
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_length(&self) -> i32 {
|
fn get_length(&self) -> usize {
|
||||||
self.base.get_length()
|
self.base.get_length()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_length(&self, gc_context: MutationContext<'gc, '_>, length: i32) {
|
fn set_length(&self, gc_context: MutationContext<'gc, '_>, new_length: usize) {
|
||||||
self.base.set_length(gc_context, length)
|
self.base.set_length(gc_context, new_length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_array(&self) -> Vec<Value<'gc>> {
|
||||||
|
self.base.get_array()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_array_element(&self, index: usize) -> Value<'gc> {
|
||||||
|
self.base.get_array_element(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_array_element(
|
||||||
|
&self,
|
||||||
|
index: usize,
|
||||||
|
value: Value<'gc>,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
) -> usize {
|
||||||
|
self.base.set_array_element(index, value, gc_context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) {
|
||||||
|
self.base.delete_array_element(index, gc_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_string(&self) -> String {
|
fn as_string(&self) -> String {
|
||||||
|
|
|
@ -63,6 +63,8 @@ swf_tests! {
|
||||||
(array_concat, "avm1/array_concat", 1),
|
(array_concat, "avm1/array_concat", 1),
|
||||||
(array_slice, "avm1/array_slice", 1),
|
(array_slice, "avm1/array_slice", 1),
|
||||||
(array_properties, "avm1/array_properties", 1),
|
(array_properties, "avm1/array_properties", 1),
|
||||||
|
(array_prototyping, "avm1/array_prototyping", 1),
|
||||||
|
(array_vs_object_length, "avm1/array_vs_object_length", 1),
|
||||||
(timeline_function_def, "avm1/timeline_function_def", 3),
|
(timeline_function_def, "avm1/timeline_function_def", 3),
|
||||||
(root_global_parent, "avm1/root_global_parent", 3),
|
(root_global_parent, "avm1/root_global_parent", 3),
|
||||||
(register_underflow, "avm1/register_underflow", 1),
|
(register_underflow, "avm1/register_underflow", 1),
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
// array
|
||||||
|
undefined,undefined,undefined
|
||||||
|
// array[0]
|
||||||
|
a
|
||||||
|
// array[1]
|
||||||
|
b
|
||||||
|
// array[2]
|
||||||
|
x
|
||||||
|
// array[3]
|
||||||
|
undefined
|
||||||
|
// array.length
|
||||||
|
3
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,100 @@
|
||||||
|
// array.length
|
||||||
|
1
|
||||||
|
|
||||||
|
// array
|
||||||
|
x
|
||||||
|
|
||||||
|
// array[0]
|
||||||
|
x
|
||||||
|
|
||||||
|
|
||||||
|
// array.length = 0
|
||||||
|
|
||||||
|
|
||||||
|
// array.length
|
||||||
|
0
|
||||||
|
|
||||||
|
// array
|
||||||
|
|
||||||
|
|
||||||
|
// array[0]
|
||||||
|
undefined
|
||||||
|
|
||||||
|
|
||||||
|
// array.length = 1
|
||||||
|
|
||||||
|
|
||||||
|
// array.length
|
||||||
|
1
|
||||||
|
|
||||||
|
// array
|
||||||
|
undefined
|
||||||
|
|
||||||
|
// array[0]
|
||||||
|
undefined
|
||||||
|
|
||||||
|
|
||||||
|
// array.length = 3
|
||||||
|
|
||||||
|
|
||||||
|
// array.length
|
||||||
|
3
|
||||||
|
|
||||||
|
// array
|
||||||
|
undefined,undefined,undefined
|
||||||
|
|
||||||
|
// array[0]
|
||||||
|
undefined
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// object.length
|
||||||
|
undefined
|
||||||
|
|
||||||
|
// object
|
||||||
|
|
||||||
|
|
||||||
|
// object[0]
|
||||||
|
x
|
||||||
|
|
||||||
|
|
||||||
|
// object.length = 0
|
||||||
|
|
||||||
|
|
||||||
|
// object.length
|
||||||
|
0
|
||||||
|
|
||||||
|
// object
|
||||||
|
|
||||||
|
|
||||||
|
// object[0]
|
||||||
|
x
|
||||||
|
|
||||||
|
|
||||||
|
// object.length = 1
|
||||||
|
|
||||||
|
|
||||||
|
// object.length
|
||||||
|
1
|
||||||
|
|
||||||
|
// object
|
||||||
|
x
|
||||||
|
|
||||||
|
// object[0]
|
||||||
|
x
|
||||||
|
|
||||||
|
|
||||||
|
// object.length = 3
|
||||||
|
|
||||||
|
|
||||||
|
// object.length
|
||||||
|
3
|
||||||
|
|
||||||
|
// object
|
||||||
|
x,undefined,undefined
|
||||||
|
|
||||||
|
// object[0]
|
||||||
|
x
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue