avm1: Extract ArrayObject
This commit is contained in:
parent
b7de03b5c8
commit
95c30b85e1
|
@ -40,6 +40,7 @@ pub use crate::avm1::error::Error;
|
|||
use crate::avm1::globals::as_broadcaster;
|
||||
use crate::avm1::globals::as_broadcaster::BroadcasterFunctions;
|
||||
pub use globals::SystemPrototypes;
|
||||
pub use object::array_object::ArrayObject;
|
||||
pub use object::script_object::ScriptObject;
|
||||
pub use object::sound_object::SoundObject;
|
||||
pub use object::stage_object::StageObject;
|
||||
|
|
|
@ -5,7 +5,8 @@ use crate::avm1::object::{Object, TObject};
|
|||
use crate::avm1::property::Attribute;
|
||||
use crate::avm1::scope::Scope;
|
||||
use crate::avm1::{
|
||||
fscommand, globals, scope, skip_actions, start_drag, AvmString, ScriptObject, Value,
|
||||
fscommand, globals, scope, skip_actions, start_drag, ArrayObject, AvmString, ScriptObject,
|
||||
Value,
|
||||
};
|
||||
use crate::backend::navigator::{NavigationMethod, RequestOptions};
|
||||
use crate::context::UpdateContext;
|
||||
|
@ -1469,15 +1470,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
// InitArray pops no args and pushes undefined if num_elements is out of range.
|
||||
Value::Undefined
|
||||
} else {
|
||||
let array = ScriptObject::array(
|
||||
ArrayObject::new(
|
||||
self.context.gc_context,
|
||||
Some(self.context.avm1.prototypes.array),
|
||||
);
|
||||
for i in 0..num_elements as i32 {
|
||||
let element = self.context.avm1.pop();
|
||||
array.set_element(self, i, element).unwrap();
|
||||
}
|
||||
Value::Object(array.into())
|
||||
self.context.avm1.prototypes().array,
|
||||
(0..num_elements as i32).map(|_| self.context.avm1.pop()),
|
||||
)
|
||||
.into()
|
||||
};
|
||||
|
||||
self.context.avm1.push(result);
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::avm1::object::super_object::SuperObject;
|
|||
use crate::avm1::property::Attribute;
|
||||
use crate::avm1::scope::Scope;
|
||||
use crate::avm1::value::Value;
|
||||
use crate::avm1::{Object, ObjectPtr, ScriptObject, TObject};
|
||||
use crate::avm1::{ArrayObject, Object, ObjectPtr, ScriptObject, TObject};
|
||||
use crate::display_object::{DisplayObject, TDisplayObject};
|
||||
use crate::tag_utils::SwfSlice;
|
||||
use gc_arena::{Collect, CollectionContext, Gc, GcCell, MutationContext};
|
||||
|
@ -244,10 +244,16 @@ impl<'gc> Executable<'gc> {
|
|||
activation.context.gc_context,
|
||||
Scope::new_local_scope(af.scope(), activation.context.gc_context),
|
||||
);
|
||||
let arguments = ScriptObject::array(
|
||||
|
||||
let arguments = if af.flags.contains(FunctionFlags::SUPPRESS_ARGUMENTS) {
|
||||
ArrayObject::empty(activation)
|
||||
} else {
|
||||
ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes().array),
|
||||
);
|
||||
activation.context.avm1.prototypes().array,
|
||||
args.iter().cloned(),
|
||||
)
|
||||
};
|
||||
arguments.define_value(
|
||||
activation.context.gc_context,
|
||||
"callee",
|
||||
|
@ -262,14 +268,6 @@ impl<'gc> Executable<'gc> {
|
|||
Attribute::DONT_ENUM,
|
||||
);
|
||||
|
||||
if !af.flags.contains(FunctionFlags::SUPPRESS_ARGUMENTS) {
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
arguments
|
||||
.set_element(activation, i as i32, arg.to_owned())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let argcell = arguments.into();
|
||||
let super_object: Option<Object<'gc>> =
|
||||
if !af.flags.contains(FunctionFlags::SUPPRESS_SUPER) {
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::avm1::activation::Activation;
|
|||
use crate::avm1::error::Error;
|
||||
use crate::avm1::function::{Executable, FunctionObject};
|
||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||
use crate::avm1::{AvmString, Object, ScriptObject, TObject, Value};
|
||||
use crate::avm1::{ArrayObject, AvmString, Object, TObject, Value};
|
||||
use bitflags::bitflags;
|
||||
use gc_arena::MutationContext;
|
||||
use std::cmp::Ordering;
|
||||
|
@ -101,11 +101,24 @@ pub fn array_function<'gc>(
|
|||
_this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let array = ScriptObject::array(
|
||||
if let [Value::Number(length)] = *args {
|
||||
let length = if length.is_finite() && length >= i32::MIN.into() && length <= i32::MAX.into()
|
||||
{
|
||||
length as i32
|
||||
} else {
|
||||
i32::MIN
|
||||
};
|
||||
let array = ArrayObject::empty(activation);
|
||||
array.set_length(activation, length)?;
|
||||
Ok(array.into())
|
||||
} else {
|
||||
Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
constructor(activation, array.into(), args)
|
||||
activation.context.avm1.prototypes().array,
|
||||
args.iter().cloned(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push<'gc>(
|
||||
|
@ -297,18 +310,12 @@ pub fn slice<'gc>(
|
|||
make_index_absolute(end.coerce_to_i32(activation)?, length)
|
||||
};
|
||||
|
||||
let array = ScriptObject::array(
|
||||
Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
for i in start..end {
|
||||
if this.has_element(activation, i) {
|
||||
let element = this.get_element(activation, i);
|
||||
array.set_element(activation, i - start, element).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(array.into())
|
||||
activation.context.avm1.prototypes().array,
|
||||
(start..end).map(|i| this.get_element(activation, i)),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
pub fn splice<'gc>(
|
||||
|
@ -332,17 +339,9 @@ pub fn splice<'gc>(
|
|||
length - start
|
||||
};
|
||||
|
||||
let result_array = ScriptObject::array(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
for i in 0..delete_count {
|
||||
if this.has_element(activation, start + i) {
|
||||
let element = this.get_element(activation, start + i);
|
||||
result_array.set_element(activation, i, element).unwrap();
|
||||
}
|
||||
}
|
||||
result_array.set_length(activation, delete_count).unwrap();
|
||||
let result_elements: Vec<_> = (0..delete_count)
|
||||
.map(|i| this.get_element(activation, start + i))
|
||||
.collect();
|
||||
|
||||
let items = if args.len() > 2 { &args[2..] } else { &[] };
|
||||
// TODO: Avoid code duplication.
|
||||
|
@ -371,7 +370,12 @@ pub fn splice<'gc>(
|
|||
}
|
||||
this.set_length(activation, length - delete_count + items.len() as i32)?;
|
||||
|
||||
Ok(result_array.into())
|
||||
Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
activation.context.avm1.prototypes().array,
|
||||
result_elements,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
pub fn concat<'gc>(
|
||||
|
@ -379,21 +383,10 @@ pub fn concat<'gc>(
|
|||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let result_array = ScriptObject::array(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
|
||||
let mut index = 0;
|
||||
let mut elements = vec![];
|
||||
for &value in [this.into()].iter().chain(args) {
|
||||
let array_object = if let Value::Object(object) = value {
|
||||
if activation
|
||||
.context
|
||||
.avm1
|
||||
.prototypes
|
||||
.array
|
||||
.is_prototype_of(object)
|
||||
{
|
||||
if object.as_array_object().is_some() {
|
||||
Some(object)
|
||||
} else {
|
||||
None
|
||||
|
@ -405,21 +398,19 @@ pub fn concat<'gc>(
|
|||
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;
|
||||
}
|
||||
elements.push(element);
|
||||
}
|
||||
} else {
|
||||
result_array.set_element(activation, index, value).unwrap();
|
||||
index += 1;
|
||||
elements.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result_array.into())
|
||||
Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
activation.context.avm1.prototypes().array,
|
||||
elements,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
pub fn to_string<'gc>(
|
||||
|
@ -597,17 +588,12 @@ fn sort_with_function<'gc>(
|
|||
if flags.contains(SortFlags::RETURN_INDEXED_ARRAY) {
|
||||
// Array.RETURNINDEXEDARRAY returns an array containing the sorted indices, and does not modify
|
||||
// the original array.
|
||||
let array = ScriptObject::array(
|
||||
Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
for (i, (index, _)) in values.into_iter().enumerate() {
|
||||
array
|
||||
.set_element(activation, i as i32, index.into())
|
||||
.unwrap();
|
||||
}
|
||||
array.set_length(activation, length).unwrap();
|
||||
Ok(array.into())
|
||||
activation.context.avm1.prototypes().array,
|
||||
values.into_iter().map(|(index, _)| index.into()),
|
||||
)
|
||||
.into())
|
||||
} else {
|
||||
// Standard sort modifies the original array, and returns it.
|
||||
// AS2 reference incorrectly states this returns nothing, but it returns the original array, sorted.
|
||||
|
@ -624,10 +610,10 @@ pub fn create_proto<'gc>(
|
|||
proto: Object<'gc>,
|
||||
fn_proto: Object<'gc>,
|
||||
) -> Object<'gc> {
|
||||
let array = ScriptObject::array(gc_context, Some(proto));
|
||||
let array = ArrayObject::empty_with_proto(gc_context, Some(proto));
|
||||
let object = array.as_script_object().unwrap();
|
||||
define_properties_on(PROTO_DECLS, gc_context, object, fn_proto);
|
||||
array.into()
|
||||
object.into()
|
||||
}
|
||||
|
||||
fn sort_compare_string<'gc>(
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::avm1::error::Error;
|
|||
use crate::avm1::object::TObject;
|
||||
use crate::avm1::property::Attribute;
|
||||
use crate::avm1::property_decl::Declaration;
|
||||
use crate::avm1::{Object, ScriptObject, Value};
|
||||
use crate::avm1::{ArrayObject, Object, ScriptObject, Value};
|
||||
use gc_arena::{Collect, MutationContext};
|
||||
|
||||
const OBJECT_DECLS: &[Declaration] = declare_properties! {
|
||||
|
@ -183,35 +183,30 @@ pub fn initialize<'gc>(
|
|||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
pub fn initialize_internal<'gc>(
|
||||
fn initialize_internal<'gc>(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
broadcaster: Object<'gc>,
|
||||
functions: BroadcasterFunctions<'gc>,
|
||||
array_proto: Object<'gc>,
|
||||
) {
|
||||
let listeners = ScriptObject::array(gc_context, Some(array_proto));
|
||||
|
||||
broadcaster.define_value(
|
||||
gc_context,
|
||||
"_listeners",
|
||||
listeners.into(),
|
||||
ArrayObject::empty_with_proto(gc_context, Some(array_proto)).into(),
|
||||
Attribute::DONT_ENUM,
|
||||
);
|
||||
|
||||
broadcaster.define_value(
|
||||
gc_context,
|
||||
"addListener",
|
||||
functions.add_listener.into(),
|
||||
Attribute::DONT_DELETE | Attribute::DONT_ENUM,
|
||||
);
|
||||
|
||||
broadcaster.define_value(
|
||||
gc_context,
|
||||
"removeListener",
|
||||
functions.remove_listener.into(),
|
||||
Attribute::DONT_DELETE | Attribute::DONT_ENUM,
|
||||
);
|
||||
|
||||
broadcaster.define_value(
|
||||
gc_context,
|
||||
"broadcastMessage",
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::avm1::activation::Activation;
|
|||
use crate::avm1::error::Error;
|
||||
use crate::avm1::object::color_matrix_filter::ColorMatrixFilterObject;
|
||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||
use crate::avm1::{Object, ScriptObject, TObject, Value};
|
||||
use crate::avm1::{ArrayObject, Object, TObject, Value};
|
||||
use gc_arena::MutationContext;
|
||||
|
||||
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
||||
|
@ -27,16 +27,12 @@ pub fn matrix<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(filter) = this.as_color_matrix_filter_object() {
|
||||
let array = ScriptObject::array(
|
||||
return Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
for (i, item) in filter.matrix().iter().copied().enumerate() {
|
||||
array
|
||||
.set_element(activation, i as i32, item.into())
|
||||
.unwrap();
|
||||
}
|
||||
return Ok(array.into());
|
||||
activation.context.avm1.prototypes().array,
|
||||
filter.matrix().iter().map(|&x| x.into()),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::avm1::activation::Activation;
|
|||
use crate::avm1::error::Error;
|
||||
use crate::avm1::object::convolution_filter::ConvolutionFilterObject;
|
||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||
use crate::avm1::{Object, ScriptObject, TObject, Value};
|
||||
use crate::avm1::{ArrayObject, Object, TObject, Value};
|
||||
use gc_arena::MutationContext;
|
||||
|
||||
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
||||
|
@ -189,16 +189,12 @@ pub fn matrix<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(filter) = this.as_convolution_filter_object() {
|
||||
let array = ScriptObject::array(
|
||||
return Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
for (i, item) in filter.matrix().iter().copied().enumerate() {
|
||||
array
|
||||
.set_element(activation, i as i32, item.into())
|
||||
.unwrap();
|
||||
}
|
||||
return Ok(array.into());
|
||||
activation.context.avm1.prototypes().array,
|
||||
filter.matrix().iter().map(|&x| x.into()),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::avm1::error::Error;
|
|||
use crate::avm1::object::bevel_filter::BevelFilterType;
|
||||
use crate::avm1::object::gradient_bevel_filter::GradientBevelFilterObject;
|
||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||
use crate::avm1::{AvmString, Object, ScriptObject, TObject, Value};
|
||||
use crate::avm1::{ArrayObject, AvmString, Object, TObject, Value};
|
||||
use gc_arena::MutationContext;
|
||||
|
||||
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
||||
|
@ -112,16 +112,12 @@ pub fn colors<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(filter) = this.as_gradient_bevel_filter_object() {
|
||||
let array = ScriptObject::array(
|
||||
return Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
for (i, item) in filter.colors().iter().copied().enumerate() {
|
||||
array
|
||||
.set_element(activation, i as i32, item.into())
|
||||
.unwrap();
|
||||
}
|
||||
return Ok(array.into());
|
||||
activation.context.avm1.prototypes().array,
|
||||
filter.colors().iter().map(|&x| x.into()),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
|
@ -176,16 +172,12 @@ pub fn alphas<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(filter) = this.as_gradient_bevel_filter_object() {
|
||||
let array = ScriptObject::array(
|
||||
return Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
for (i, item) in filter.alphas().iter().copied().enumerate() {
|
||||
array
|
||||
.set_element(activation, i as i32, item.into())
|
||||
.unwrap();
|
||||
}
|
||||
return Ok(array.into());
|
||||
activation.context.avm1.prototypes().array,
|
||||
filter.alphas().iter().map(|&x| x.into()),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
|
@ -231,16 +223,12 @@ pub fn ratios<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(filter) = this.as_gradient_bevel_filter_object() {
|
||||
let array = ScriptObject::array(
|
||||
return Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
for (i, item) in filter.ratios().iter().copied().enumerate() {
|
||||
array
|
||||
.set_element(activation, i as i32, item.into())
|
||||
.unwrap();
|
||||
}
|
||||
return Ok(array.into());
|
||||
activation.context.avm1.prototypes().array,
|
||||
filter.ratios().iter().map(|&x| x.into()),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::avm1::error::Error;
|
|||
use crate::avm1::object::bevel_filter::BevelFilterType;
|
||||
use crate::avm1::object::gradient_glow_filter::GradientGlowFilterObject;
|
||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||
use crate::avm1::{AvmString, Object, ScriptObject, TObject, Value};
|
||||
use crate::avm1::{ArrayObject, AvmString, Object, TObject, Value};
|
||||
use gc_arena::MutationContext;
|
||||
|
||||
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
||||
|
@ -112,16 +112,12 @@ pub fn colors<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(filter) = this.as_gradient_glow_filter_object() {
|
||||
let array = ScriptObject::array(
|
||||
return Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
for (i, item) in filter.colors().iter().copied().enumerate() {
|
||||
array
|
||||
.set_element(activation, i as i32, item.into())
|
||||
.unwrap();
|
||||
}
|
||||
return Ok(array.into());
|
||||
activation.context.avm1.prototypes().array,
|
||||
filter.colors().iter().map(|&x| x.into()),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
|
@ -176,16 +172,12 @@ pub fn alphas<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(filter) = this.as_gradient_glow_filter_object() {
|
||||
let array = ScriptObject::array(
|
||||
return Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
for (i, item) in filter.alphas().iter().copied().enumerate() {
|
||||
array
|
||||
.set_element(activation, i as i32, item.into())
|
||||
.unwrap();
|
||||
}
|
||||
return Ok(array.into());
|
||||
activation.context.avm1.prototypes().array,
|
||||
filter.alphas().iter().map(|&x| x.into()),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
|
@ -231,16 +223,12 @@ pub fn ratios<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(filter) = this.as_gradient_glow_filter_object() {
|
||||
let array = ScriptObject::array(
|
||||
return Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
for (i, item) in filter.ratios().iter().copied().enumerate() {
|
||||
array
|
||||
.set_element(activation, i as i32, item.into())
|
||||
.unwrap();
|
||||
}
|
||||
return Ok(array.into());
|
||||
activation.context.avm1.prototypes().array,
|
||||
filter.ratios().iter().map(|&x| x.into()),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::avm1::object::script_object::ScriptObject;
|
|||
use crate::avm1::object::TObject;
|
||||
use crate::avm1::property::Attribute;
|
||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||
use crate::avm1::{Object, Value};
|
||||
use crate::avm1::{ArrayObject, Object, Value};
|
||||
use crate::backend::navigator::RequestOptions;
|
||||
use crate::display_object::{DisplayObject, TDisplayObject};
|
||||
use gc_arena::MutationContext;
|
||||
|
@ -23,9 +23,10 @@ pub fn constructor<'gc>(
|
|||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let listeners = ScriptObject::array(
|
||||
let listeners = ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes().array),
|
||||
activation.context.avm1.prototypes().array,
|
||||
[this.into()],
|
||||
);
|
||||
this.define_value(
|
||||
activation.context.gc_context,
|
||||
|
@ -33,8 +34,6 @@ pub fn constructor<'gc>(
|
|||
Value::Object(listeners.into()),
|
||||
Attribute::DONT_ENUM,
|
||||
);
|
||||
listeners.set_element(activation, 0, this.into()).unwrap();
|
||||
|
||||
Ok(this.into())
|
||||
}
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ fn deserialize_value<'gc>(activation: &mut Activation<'_, 'gc, '_>, val: &AmfVal
|
|||
let value = deserialize_value(activation, entry.value());
|
||||
|
||||
if let Ok(i) = entry.name().parse::<i32>() {
|
||||
let _ = obj.set_element(activation, i, value);
|
||||
obj.set_element(activation, i, value).unwrap();
|
||||
} else {
|
||||
obj.define_value(
|
||||
activation.context.gc_context,
|
||||
|
@ -291,7 +291,7 @@ fn deserialize_array_json<'gc>(
|
|||
for entry in json_obj.iter() {
|
||||
let value = recursive_deserialize_json(entry.1.clone(), activation);
|
||||
if let Ok(i) = entry.0.parse::<i32>() {
|
||||
let _ = obj.set_element(activation, i, value);
|
||||
obj.set_element(activation, i, value).unwrap();
|
||||
} else {
|
||||
obj.define_value(
|
||||
activation.context.gc_context,
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::avm1::function::{Executable, FunctionObject};
|
|||
use crate::avm1::object::value_object::ValueObject;
|
||||
use crate::avm1::property::Attribute;
|
||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||
use crate::avm1::{AvmString, Object, ScriptObject, TObject, Value};
|
||||
use crate::avm1::{ArrayObject, AvmString, Object, TObject, Value};
|
||||
use crate::string_utils;
|
||||
use gc_arena::MutationContext;
|
||||
|
||||
|
@ -304,43 +304,37 @@ fn split<'gc>(
|
|||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let this_val = Value::from(this);
|
||||
let this = this_val.coerce_to_string(activation)?;
|
||||
let delimiter_val = args.get(0).unwrap_or(&Value::Undefined);
|
||||
let delimiter = delimiter_val.coerce_to_string(activation)?;
|
||||
let limit = match args.get(1) {
|
||||
None | Some(Value::Undefined) => usize::MAX,
|
||||
Some(n) => std::cmp::max(0, n.coerce_to_i32(activation)?) as usize,
|
||||
let this = Value::from(this).coerce_to_string(activation)?;
|
||||
let delimiter = args
|
||||
.get(0)
|
||||
.unwrap_or(&Value::Undefined)
|
||||
.coerce_to_string(activation)?;
|
||||
let limit = match args.get(1).unwrap_or(&Value::Undefined) {
|
||||
Value::Undefined => usize::MAX,
|
||||
limit => limit.coerce_to_i32(activation)?.max(0) as usize,
|
||||
};
|
||||
let array = ScriptObject::array(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
if !delimiter.is_empty() {
|
||||
for (i, token) in this.split(delimiter.as_ref()).take(limit).enumerate() {
|
||||
array
|
||||
.set_element(
|
||||
activation,
|
||||
i as i32,
|
||||
AvmString::new(activation.context.gc_context, token.to_string()).into(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
} else {
|
||||
// When using an empty "" delimiter, Rust's str::split adds an extra beginning and trailing item, but Flash does not.
|
||||
if delimiter.is_empty() {
|
||||
// When using an empty delimiter, Rust's str::split adds an extra beginning and trailing item, but Flash does not.
|
||||
// e.g., split("foo", "") returns ["", "f", "o", "o", ""] in Rust but ["f, "o", "o"] in Flash.
|
||||
// Special case this to match Flash's behavior.
|
||||
for (i, token) in this.chars().take(limit).enumerate() {
|
||||
array
|
||||
.set_element(
|
||||
activation,
|
||||
i as i32,
|
||||
AvmString::new(activation.context.gc_context, token.to_string()).into(),
|
||||
Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
activation.context.avm1.prototypes().array,
|
||||
this.chars()
|
||||
.take(limit)
|
||||
.map(|c| AvmString::new(activation.context.gc_context, c.to_string()).into()),
|
||||
)
|
||||
.unwrap();
|
||||
.into())
|
||||
} else {
|
||||
Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
activation.context.avm1.prototypes().array,
|
||||
this.split(delimiter.as_ref())
|
||||
.take(limit)
|
||||
.map(|c| AvmString::new(activation.context.gc_context, c.to_string()).into()),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
Ok(array.into())
|
||||
}
|
||||
|
||||
fn substr<'gc>(
|
||||
|
|
|
@ -2,10 +2,9 @@
|
|||
|
||||
use crate::avm1::activation::Activation;
|
||||
use crate::avm1::error::Error;
|
||||
use crate::avm1::object::script_object::ScriptObject;
|
||||
use crate::avm1::object::xml_object::XmlObject;
|
||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||
use crate::avm1::{AvmString, Object, TObject, Value};
|
||||
use crate::avm1::{ArrayObject, AvmString, Object, TObject, Value};
|
||||
use crate::avm_warn;
|
||||
use crate::backend::navigator::RequestOptions;
|
||||
use crate::xml;
|
||||
|
@ -73,7 +72,7 @@ const XML_PROTO_DECLS: &[Declaration] = declare_properties! {
|
|||
/// not. Those nodes are filtered from all attributes that return XML nodes to
|
||||
/// act as if those nodes did not exist. For example, `prevSibling` skips
|
||||
/// past incompatible nodes, etc.
|
||||
fn is_as2_compatible(node: XmlNode<'_>) -> bool {
|
||||
fn is_as2_compatible(node: &XmlNode<'_>) -> bool {
|
||||
node.is_document_root() || node.is_element() || node.is_text()
|
||||
}
|
||||
|
||||
|
@ -357,34 +356,19 @@ pub fn xmlnode_child_nodes<'gc>(
|
|||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(node) = this.as_xml_node() {
|
||||
let array = ScriptObject::array(
|
||||
return Ok(ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.array),
|
||||
);
|
||||
|
||||
let mut compatible_nodes = 0;
|
||||
for mut child in node.children() {
|
||||
if !is_as2_compatible(child) {
|
||||
continue;
|
||||
}
|
||||
|
||||
array
|
||||
.set_element(
|
||||
activation,
|
||||
compatible_nodes,
|
||||
activation.context.avm1.prototypes().array,
|
||||
node.children().filter(is_as2_compatible).map(|mut child| {
|
||||
child
|
||||
.script_object(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes.xml_node),
|
||||
)
|
||||
.into(),
|
||||
.into()
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
compatible_nodes += 1;
|
||||
}
|
||||
|
||||
return Ok(array.into());
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
|
@ -399,7 +383,7 @@ pub fn xmlnode_first_child<'gc>(
|
|||
let mut children = node.children();
|
||||
let mut next = children.next();
|
||||
while let Some(my_next) = next {
|
||||
if is_as2_compatible(my_next) {
|
||||
if is_as2_compatible(&my_next) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -430,7 +414,7 @@ pub fn xmlnode_last_child<'gc>(
|
|||
let mut children = node.children();
|
||||
let mut prev = children.next_back();
|
||||
while let Some(my_prev) = prev {
|
||||
if is_as2_compatible(my_prev) {
|
||||
if is_as2_compatible(&my_prev) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -481,7 +465,7 @@ pub fn xmlnode_previous_sibling<'gc>(
|
|||
if let Some(node) = this.as_xml_node() {
|
||||
let mut prev = node.prev_sibling();
|
||||
while let Some(my_prev) = prev {
|
||||
if is_as2_compatible(my_prev) {
|
||||
if is_as2_compatible(&my_prev) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -510,7 +494,7 @@ pub fn xmlnode_next_sibling<'gc>(
|
|||
if let Some(node) = this.as_xml_node() {
|
||||
let mut next = node.next_sibling();
|
||||
while let Some(my_next) = next {
|
||||
if is_as2_compatible(my_next) {
|
||||
if is_as2_compatible(&my_next) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::avm1::object::value_object::ValueObject;
|
|||
use crate::avm1::property::Attribute;
|
||||
|
||||
use crate::avm1::activation::Activation;
|
||||
use crate::avm1::object::array_object::ArrayObject;
|
||||
use crate::avm1::object::bevel_filter::BevelFilterObject;
|
||||
use crate::avm1::object::bitmap_data::BitmapDataObject;
|
||||
use crate::avm1::object::blur_filter::BlurFilterObject;
|
||||
|
@ -33,6 +34,7 @@ use ruffle_macros::enum_trait_object;
|
|||
use std::borrow::Cow;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub mod array_object;
|
||||
pub mod bevel_filter;
|
||||
pub mod bitmap_data;
|
||||
pub mod blur_filter;
|
||||
|
@ -65,6 +67,7 @@ pub mod xml_object;
|
|||
#[collect(no_drop)]
|
||||
pub enum Object<'gc> {
|
||||
ScriptObject(ScriptObject<'gc>),
|
||||
ArrayObject(ArrayObject<'gc>),
|
||||
SoundObject(SoundObject<'gc>),
|
||||
StageObject(StageObject<'gc>),
|
||||
SuperObject(SuperObject<'gc>),
|
||||
|
@ -144,22 +147,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
if let Ok(index) = name.parse::<i32>() {
|
||||
return self.set_element(activation, index, value.to_owned());
|
||||
}
|
||||
|
||||
if name == "length" {
|
||||
let length = value
|
||||
.coerce_to_f64(activation)
|
||||
.map(|v| v.abs() as i32)
|
||||
.unwrap_or(0);
|
||||
if length > 0 {
|
||||
self.set_length(activation, length)?;
|
||||
} else {
|
||||
self.set_length(activation, 0)?;
|
||||
}
|
||||
}
|
||||
|
||||
let this = (*self).into();
|
||||
if !self.has_own_property(activation, name) {
|
||||
// Before actually inserting a new property, we need to crawl the
|
||||
|
@ -460,6 +447,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
|||
/// Get the underlying script object, if it exists.
|
||||
fn as_script_object(&self) -> Option<ScriptObject<'gc>>;
|
||||
|
||||
/// Get the underlying array object, if it exists.
|
||||
fn as_array_object(&self) -> Option<ArrayObject<'gc>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the underlying sound object, if it exists.
|
||||
fn as_sound_object(&self) -> Option<SoundObject<'gc>> {
|
||||
None
|
||||
|
|
|
@ -0,0 +1,281 @@
|
|||
use crate::avm1::property::Attribute;
|
||||
use crate::avm1::{Activation, Error, Object, ObjectPtr, ScriptObject, TObject, Value};
|
||||
use gc_arena::{Collect, GcCell, MutationContext};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Copy, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct ArrayObject<'gc>(GcCell<'gc, ScriptObject<'gc>>);
|
||||
|
||||
impl fmt::Debug for ArrayObject<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("ArrayObject").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> ArrayObject<'gc> {
|
||||
pub fn empty(activation: &Activation<'_, 'gc, '_>) -> Self {
|
||||
Self::new(
|
||||
activation.context.gc_context,
|
||||
activation.context.avm1.prototypes().array,
|
||||
[],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn empty_with_proto(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
proto: Option<Object<'gc>>,
|
||||
) -> Self {
|
||||
Self::new_internal(gc_context, proto, [])
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
array_proto: Object<'gc>,
|
||||
elements: impl IntoIterator<Item = Value<'gc>>,
|
||||
) -> Self {
|
||||
Self::new_internal(gc_context, Some(array_proto), elements)
|
||||
}
|
||||
|
||||
fn new_internal(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
proto: Option<Object<'gc>>,
|
||||
elements: impl IntoIterator<Item = Value<'gc>>,
|
||||
) -> Self {
|
||||
let base = ScriptObject::object(gc_context, proto);
|
||||
let mut length: i32 = 0;
|
||||
for value in elements.into_iter() {
|
||||
base.define_value(gc_context, &length.to_string(), value, Attribute::empty());
|
||||
length += 1;
|
||||
}
|
||||
base.define_value(
|
||||
gc_context,
|
||||
"length",
|
||||
length.into(),
|
||||
Attribute::DONT_ENUM | Attribute::DONT_DELETE,
|
||||
);
|
||||
Self(GcCell::allocate(gc_context, base))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> TObject<'gc> for ArrayObject<'gc> {
|
||||
fn get_local(
|
||||
&self,
|
||||
name: &str,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
) -> Option<Result<Value<'gc>, Error<'gc>>> {
|
||||
self.0.read().get_local(name, activation, this)
|
||||
}
|
||||
|
||||
fn set_local(
|
||||
&self,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
base_proto: Option<Object<'gc>>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
if name == "length" {
|
||||
let new_length = value.coerce_to_i32(activation)?;
|
||||
self.set_length(activation, new_length)?;
|
||||
} else if let Ok(index) = name.parse::<i32>() {
|
||||
let length = self.length(activation)?;
|
||||
if index >= length {
|
||||
self.set_length(activation, index.wrapping_add(1))?;
|
||||
}
|
||||
}
|
||||
|
||||
self.0
|
||||
.read()
|
||||
.set_local(name, value, activation, this, base_proto)
|
||||
}
|
||||
|
||||
fn call(
|
||||
&self,
|
||||
name: &str,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
base_proto: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
self.0.read().call(name, activation, this, base_proto, args)
|
||||
}
|
||||
|
||||
fn call_setter(
|
||||
&self,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
) -> Option<Object<'gc>> {
|
||||
self.0.read().call_setter(name, value, activation)
|
||||
}
|
||||
|
||||
fn create_bare_object(
|
||||
&self,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
) -> Result<Object<'gc>, Error<'gc>> {
|
||||
Ok(Self::empty_with_proto(activation.context.gc_context, Some(this)).into())
|
||||
}
|
||||
|
||||
fn delete(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.0.read().delete(activation, name)
|
||||
}
|
||||
|
||||
fn add_property(
|
||||
&self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
name: &str,
|
||||
get: Object<'gc>,
|
||||
set: Option<Object<'gc>>,
|
||||
attributes: Attribute,
|
||||
) {
|
||||
self.0
|
||||
.read()
|
||||
.add_property(gc_context, name, get, set, attributes)
|
||||
}
|
||||
|
||||
fn add_property_with_case(
|
||||
&self,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
name: &str,
|
||||
get: Object<'gc>,
|
||||
set: Option<Object<'gc>>,
|
||||
attributes: Attribute,
|
||||
) {
|
||||
self.0
|
||||
.read()
|
||||
.add_property_with_case(activation, name, get, set, attributes)
|
||||
}
|
||||
|
||||
fn set_watcher(
|
||||
&self,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
name: Cow<str>,
|
||||
callback: Object<'gc>,
|
||||
user_data: Value<'gc>,
|
||||
) {
|
||||
self.0
|
||||
.read()
|
||||
.set_watcher(activation, name, callback, user_data);
|
||||
}
|
||||
|
||||
fn remove_watcher(&self, activation: &mut Activation<'_, 'gc, '_>, name: Cow<str>) -> bool {
|
||||
self.0.read().remove_watcher(activation, name)
|
||||
}
|
||||
|
||||
fn define_value(
|
||||
&self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
attributes: Attribute,
|
||||
) {
|
||||
self.0
|
||||
.read()
|
||||
.define_value(gc_context, name, value, attributes)
|
||||
}
|
||||
|
||||
fn set_attributes(
|
||||
&self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
name: Option<&str>,
|
||||
set_attributes: Attribute,
|
||||
clear_attributes: Attribute,
|
||||
) {
|
||||
self.0
|
||||
.read()
|
||||
.set_attributes(gc_context, name, set_attributes, clear_attributes)
|
||||
}
|
||||
|
||||
fn proto(&self) -> Value<'gc> {
|
||||
self.0.read().proto()
|
||||
}
|
||||
|
||||
fn set_proto(&self, gc_context: MutationContext<'gc, '_>, prototype: Value<'gc>) {
|
||||
self.0.read().set_proto(gc_context, prototype);
|
||||
}
|
||||
|
||||
fn has_property(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.0.read().has_property(activation, name)
|
||||
}
|
||||
|
||||
fn has_own_property(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.0.read().has_own_property(activation, name)
|
||||
}
|
||||
|
||||
fn has_own_virtual(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.0.read().has_own_virtual(activation, name)
|
||||
}
|
||||
|
||||
fn is_property_enumerable(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
|
||||
self.0.read().is_property_enumerable(activation, name)
|
||||
}
|
||||
|
||||
fn get_keys(&self, activation: &mut Activation<'_, 'gc, '_>) -> Vec<String> {
|
||||
self.0.read().get_keys(activation)
|
||||
}
|
||||
|
||||
fn type_of(&self) -> &'static str {
|
||||
self.0.read().type_of()
|
||||
}
|
||||
|
||||
fn interfaces(&self) -> Vec<Object<'gc>> {
|
||||
self.0.read().interfaces()
|
||||
}
|
||||
|
||||
fn set_interfaces(&self, gc_context: MutationContext<'gc, '_>, iface_list: Vec<Object<'gc>>) {
|
||||
self.0.read().set_interfaces(gc_context, iface_list)
|
||||
}
|
||||
|
||||
fn as_script_object(&self) -> Option<ScriptObject<'gc>> {
|
||||
Some(*self.0.read())
|
||||
}
|
||||
|
||||
fn as_array_object(&self) -> Option<ArrayObject<'gc>> {
|
||||
Some(*self)
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *const ObjectPtr {
|
||||
self.0.read().as_ptr() as *const ObjectPtr
|
||||
}
|
||||
|
||||
fn length(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
|
||||
self.0.read().length(activation)
|
||||
}
|
||||
|
||||
fn set_length(
|
||||
&self,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
length: i32,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
self.0.read().set_length(activation, length)
|
||||
}
|
||||
|
||||
fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
|
||||
self.0.read().has_element(activation, index)
|
||||
}
|
||||
|
||||
fn get_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> Value<'gc> {
|
||||
self.0.read().get_element(activation, index)
|
||||
}
|
||||
|
||||
fn set_element(
|
||||
&self,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
index: i32,
|
||||
value: Value<'gc>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
let length = self.length(activation)?;
|
||||
if index >= length {
|
||||
self.set_length(activation, index.wrapping_add(1))?;
|
||||
}
|
||||
self.0.read().set_element(activation, index, value)
|
||||
}
|
||||
|
||||
fn delete_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
|
||||
self.0.read().delete_element(activation, index)
|
||||
}
|
||||
}
|
|
@ -4,19 +4,13 @@ use crate::avm1::function::ExecutionReason;
|
|||
use crate::avm1::property::{Attribute, Property};
|
||||
use crate::avm1::property_map::{Entry, PropertyMap};
|
||||
use crate::avm1::{AvmString, Object, ObjectPtr, TObject, Value};
|
||||
use crate::ecma_conversions::f64_to_wrapping_i32;
|
||||
use core::fmt;
|
||||
use gc_arena::{Collect, GcCell, MutationContext};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub const TYPE_OF_OBJECT: &str = "object";
|
||||
|
||||
#[derive(Debug, Clone, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub enum ArrayStorage<'gc> {
|
||||
Vector(Vec<Value<'gc>>),
|
||||
Properties { length: i32 },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct Watcher<'gc> {
|
||||
|
@ -32,7 +26,6 @@ impl<'gc> Watcher<'gc> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn call(
|
||||
&self,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
|
@ -78,7 +71,6 @@ pub struct ScriptObjectData<'gc> {
|
|||
values: PropertyMap<Property<'gc>>,
|
||||
interfaces: Vec<Object<'gc>>,
|
||||
type_of: &'static str,
|
||||
array: ArrayStorage<'gc>,
|
||||
watchers: PropertyMap<Watcher<'gc>>,
|
||||
}
|
||||
|
||||
|
@ -87,7 +79,6 @@ impl fmt::Debug for ScriptObjectData<'_> {
|
|||
f.debug_struct("Object")
|
||||
.field("prototype", &self.prototype)
|
||||
.field("values", &self.values)
|
||||
.field("array", &self.array)
|
||||
.field("watchers", &self.watchers)
|
||||
.finish()
|
||||
}
|
||||
|
@ -104,32 +95,12 @@ impl<'gc> ScriptObject<'gc> {
|
|||
prototype: proto.map_or(Value::Undefined, Value::Object),
|
||||
type_of: TYPE_OF_OBJECT,
|
||||
values: PropertyMap::new(),
|
||||
array: ArrayStorage::Properties { length: 0 },
|
||||
interfaces: vec![],
|
||||
watchers: PropertyMap::new(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn array(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
proto: Option<Object<'gc>>,
|
||||
) -> ScriptObject<'gc> {
|
||||
let object = ScriptObject(GcCell::allocate(
|
||||
gc_context,
|
||||
ScriptObjectData {
|
||||
prototype: proto.map_or(Value::Undefined, Value::Object),
|
||||
type_of: TYPE_OF_OBJECT,
|
||||
values: PropertyMap::new(),
|
||||
array: ArrayStorage::Vector(Vec::new()),
|
||||
interfaces: vec![],
|
||||
watchers: PropertyMap::new(),
|
||||
},
|
||||
));
|
||||
object.sync_native_property("length", gc_context, Some(0.into()), false);
|
||||
object
|
||||
}
|
||||
|
||||
/// Constructs and allocates an empty but normal object in one go.
|
||||
pub fn object_cell(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
|
@ -141,7 +112,6 @@ impl<'gc> ScriptObject<'gc> {
|
|||
prototype: proto.map_or(Value::Undefined, Value::Object),
|
||||
type_of: TYPE_OF_OBJECT,
|
||||
values: PropertyMap::new(),
|
||||
array: ArrayStorage::Properties { length: 0 },
|
||||
interfaces: vec![],
|
||||
watchers: PropertyMap::new(),
|
||||
},
|
||||
|
@ -161,7 +131,6 @@ impl<'gc> ScriptObject<'gc> {
|
|||
prototype: Value::Undefined,
|
||||
type_of: TYPE_OF_OBJECT,
|
||||
values: PropertyMap::new(),
|
||||
array: ArrayStorage::Properties { length: 0 },
|
||||
interfaces: vec![],
|
||||
watchers: PropertyMap::new(),
|
||||
},
|
||||
|
@ -172,52 +141,56 @@ impl<'gc> ScriptObject<'gc> {
|
|||
self.0.write(gc_context).type_of = type_of;
|
||||
}
|
||||
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
pub fn sync_native_property(
|
||||
/// Gets the value of a data property on this object.
|
||||
///
|
||||
/// Doesn't look up the prototype chain and ignores virtual properties, thus cannot cause
|
||||
/// any side-effects.
|
||||
fn get_data(&self, name: &str, activation: &mut Activation<'_, 'gc, '_>) -> Value<'gc> {
|
||||
if let Some(Property::Stored { value, .. }) = self
|
||||
.0
|
||||
.read()
|
||||
.values
|
||||
.get(name, activation.is_case_sensitive())
|
||||
{
|
||||
value.to_owned()
|
||||
} else {
|
||||
Value::Undefined
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a data property on this object.
|
||||
///
|
||||
/// Doesn't look up the prototype chain and ignores virtual properties, but still might
|
||||
/// call to watchers.
|
||||
fn set_data(
|
||||
&self,
|
||||
name: &str,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
native_value: Option<Value<'gc>>,
|
||||
is_enumerable: bool,
|
||||
) {
|
||||
match self.0.write(gc_context).values.entry(name, false) {
|
||||
value: Value<'gc>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
// TODO: Call watchers.
|
||||
match self
|
||||
.0
|
||||
.write(activation.context.gc_context)
|
||||
.values
|
||||
.entry(name, activation.is_case_sensitive())
|
||||
{
|
||||
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.get_mut().set(value);
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
if let Some(native_value) = native_value {
|
||||
entry.insert(Property::Stored {
|
||||
value: native_value,
|
||||
attributes: if is_enumerable {
|
||||
Attribute::empty()
|
||||
} else {
|
||||
Attribute::DONT_ENUM
|
||||
},
|
||||
value,
|
||||
attributes: Attribute::empty(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||
/// Get the value of a particular property on this object.
|
||||
///
|
||||
/// The `avm`, `context`, and `this` parameters exist so that this object
|
||||
/// can call virtual properties. Furthermore, since some virtual properties
|
||||
/// may resolve on the AVM stack, this function may return `None` instead
|
||||
/// of a `Value`. *This is not equivalent to `undefined`.* Instead, it is a
|
||||
/// signal that your value will be returned on the ActionScript stack, and
|
||||
/// that you should register a stack continuation in order to get it.
|
||||
fn get_local(
|
||||
&self,
|
||||
name: &str,
|
||||
|
@ -360,28 +333,24 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
|||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
) -> Result<Object<'gc>, Error<'gc>> {
|
||||
match self.0.read().array {
|
||||
ArrayStorage::Vector(_) => {
|
||||
Ok(ScriptObject::array(activation.context.gc_context, Some(this)).into())
|
||||
}
|
||||
ArrayStorage::Properties { .. } => {
|
||||
Ok(ScriptObject::object(activation.context.gc_context, Some(this)).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete a named property from the object.
|
||||
///
|
||||
/// Returns false if the property cannot be deleted.
|
||||
fn delete(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
|
||||
let mut object = self.0.write(activation.context.gc_context);
|
||||
if let Some(prop) = object.values.get(name, activation.is_case_sensitive()) {
|
||||
if prop.can_delete() {
|
||||
object.values.remove(name, activation.is_case_sensitive());
|
||||
if let Entry::Occupied(mut entry) = self
|
||||
.0
|
||||
.write(activation.context.gc_context)
|
||||
.values
|
||||
.entry(name, activation.is_case_sensitive())
|
||||
{
|
||||
if entry.get().can_delete() {
|
||||
entry.remove_entry();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
|
@ -589,11 +558,9 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
|||
self.0.as_ptr() as *const ObjectPtr
|
||||
}
|
||||
|
||||
fn length(&self, _activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
|
||||
match &self.0.read().array {
|
||||
ArrayStorage::Vector(vector) => Ok(vector.len() as i32),
|
||||
ArrayStorage::Properties { length } => Ok(*length),
|
||||
}
|
||||
fn length(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result<i32, Error<'gc>> {
|
||||
self.get_data("length", activation)
|
||||
.coerce_to_i32(activation)
|
||||
}
|
||||
|
||||
fn set_length(
|
||||
|
@ -601,66 +568,20 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
|||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
new_length: i32,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
let to_remove = match &mut self.0.write(activation.context.gc_context).array {
|
||||
ArrayStorage::Vector(vector) => {
|
||||
if new_length >= 0 {
|
||||
let old_length = vector.len();
|
||||
let new_length = new_length as usize;
|
||||
vector.resize(new_length, Value::Undefined);
|
||||
Some(new_length..old_length)
|
||||
} else {
|
||||
None
|
||||
if let Value::Number(old_length) = self.get_data("length", activation) {
|
||||
for i in new_length.max(0)..f64_to_wrapping_i32(old_length) {
|
||||
self.delete_element(activation, i);
|
||||
}
|
||||
}
|
||||
ArrayStorage::Properties { length } => {
|
||||
*length = new_length;
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(to_remove) = to_remove {
|
||||
for i in to_remove {
|
||||
self.sync_native_property(
|
||||
&i.to_string(),
|
||||
activation.context.gc_context,
|
||||
None,
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
self.sync_native_property(
|
||||
"length",
|
||||
activation.context.gc_context,
|
||||
Some(new_length.into()),
|
||||
false,
|
||||
);
|
||||
Ok(())
|
||||
self.set_data("length", new_length.into(), activation)
|
||||
}
|
||||
|
||||
fn has_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
|
||||
self.has_own_property(activation, &index.to_string())
|
||||
}
|
||||
|
||||
fn get_element(&self, _activation: &mut Activation<'_, 'gc, '_>, index: i32) -> Value<'gc> {
|
||||
match &self.0.read().array {
|
||||
ArrayStorage::Vector(vector) => {
|
||||
let index = index as usize;
|
||||
if let Some(value) = vector.get(index) {
|
||||
value.to_owned()
|
||||
} else {
|
||||
Value::Undefined
|
||||
}
|
||||
}
|
||||
ArrayStorage::Properties { length } => {
|
||||
if index < *length {
|
||||
if let Some(Property::Stored { value, .. }) =
|
||||
self.0.read().values.get(&index.to_string(), false)
|
||||
{
|
||||
return value.to_owned();
|
||||
}
|
||||
}
|
||||
Value::Undefined
|
||||
}
|
||||
}
|
||||
fn get_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> Value<'gc> {
|
||||
self.get_data(&index.to_string(), activation)
|
||||
}
|
||||
|
||||
fn set_element(
|
||||
|
@ -669,54 +590,11 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
|||
index: i32,
|
||||
value: Value<'gc>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
self.sync_native_property(
|
||||
&index.to_string(),
|
||||
activation.context.gc_context,
|
||||
Some(value),
|
||||
true,
|
||||
);
|
||||
let length = match &mut self.0.write(activation.context.gc_context).array {
|
||||
ArrayStorage::Vector(vector) => {
|
||||
if index >= 0 {
|
||||
let index = index as usize;
|
||||
if index >= vector.len() {
|
||||
vector.resize(index + 1, Value::Undefined);
|
||||
}
|
||||
vector[index] = value;
|
||||
Some(vector.len())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
ArrayStorage::Properties { length: _ } => {
|
||||
// TODO: Support Array-like case?
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(length) = length {
|
||||
self.sync_native_property(
|
||||
"length",
|
||||
activation.context.gc_context,
|
||||
Some(length.into()),
|
||||
false,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
self.set_data(&index.to_string(), value, activation)
|
||||
}
|
||||
|
||||
fn delete_element(&self, activation: &mut Activation<'_, 'gc, '_>, index: i32) -> bool {
|
||||
match &mut self.0.write(activation.context.gc_context).array {
|
||||
ArrayStorage::Vector(vector) => {
|
||||
let index = index as usize;
|
||||
if index < vector.len() {
|
||||
vector[index] = Value::Undefined;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
ArrayStorage::Properties { length: _ } => self.delete(activation, &index.to_string()),
|
||||
}
|
||||
self.delete(activation, &index.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,8 +31,8 @@ impl<V> PropertyMap<V> {
|
|||
|
||||
pub fn entry<'a>(&'a mut self, key: &'a str, case_sensitive: bool) -> Entry<'a, V> {
|
||||
if case_sensitive {
|
||||
match self.0.get_full_mut(&CaseSensitiveStr(key)) {
|
||||
Some((index, _, _)) => Entry::Occupied(OccupiedEntry {
|
||||
match self.0.get_index_of(&CaseSensitiveStr(key)) {
|
||||
Some(index) => Entry::Occupied(OccupiedEntry {
|
||||
map: &mut self.0,
|
||||
index,
|
||||
}),
|
||||
|
@ -42,8 +42,8 @@ impl<V> PropertyMap<V> {
|
|||
}),
|
||||
}
|
||||
} else {
|
||||
match self.0.get_full_mut(&CaseInsensitiveStr(key)) {
|
||||
Some((index, _, _)) => Entry::Occupied(OccupiedEntry {
|
||||
match self.0.get_index_of(&CaseInsensitiveStr(key)) {
|
||||
Some(index) => Entry::Occupied(OccupiedEntry {
|
||||
map: &mut self.0,
|
||||
index,
|
||||
}),
|
||||
|
@ -65,7 +65,6 @@ impl<V> PropertyMap<V> {
|
|||
}
|
||||
|
||||
/// Gets a mutable reference to the value for the specified property.
|
||||
#[allow(dead_code)]
|
||||
pub fn get_mut(&mut self, key: &str, case_sensitive: bool) -> Option<&mut V> {
|
||||
if case_sensitive {
|
||||
self.0.get_mut(&CaseSensitiveStr(key))
|
||||
|
@ -133,6 +132,10 @@ impl<'a, V> OccupiedEntry<'a, V> {
|
|||
(k.0, v)
|
||||
}
|
||||
|
||||
pub fn get(&self) -> &V {
|
||||
self.map.get_index(self.index).unwrap().1
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self) -> &mut V {
|
||||
self.map.get_index_mut(self.index).unwrap().1
|
||||
}
|
||||
|
|
|
@ -1321,7 +1321,7 @@ impl<'gc> EditText<'gc> {
|
|||
if matches!(length, Ok(0)) {
|
||||
// Add the TextField as its own listener to match Flash's behavior
|
||||
// This makes it so that the TextField's handlers are called before other listeners'.
|
||||
let _ = listeners.set_element(activation, 0, object.into());
|
||||
listeners.set_element(activation, 0, object.into()).unwrap();
|
||||
} else {
|
||||
log::warn!("_listeners should be empty");
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@ use crate::avm1::activation::{
|
|||
use crate::avm1::object::TObject;
|
||||
use crate::avm1::Value as Avm1Value;
|
||||
use crate::avm1::{
|
||||
AvmString as Avm1String, Object as Avm1Object, ScriptObject as Avm1ScriptObject,
|
||||
ArrayObject as Avm1ArrayObject, AvmString as Avm1String, Error as Avm1Error,
|
||||
Object as Avm1Object, ScriptObject as Avm1ScriptObject,
|
||||
};
|
||||
use crate::context::UpdateContext;
|
||||
use gc_arena::Collect;
|
||||
|
@ -117,22 +118,16 @@ impl Value {
|
|||
pub fn from_avm1<'gc>(
|
||||
activation: &mut Avm1Activation<'_, 'gc, '_>,
|
||||
value: Avm1Value<'gc>,
|
||||
) -> Result<Value, crate::avm1::error::Error<'gc>> {
|
||||
) -> Result<Value, Avm1Error<'gc>> {
|
||||
Ok(match value {
|
||||
Avm1Value::Undefined | Avm1Value::Null => Value::Null,
|
||||
Avm1Value::Bool(value) => Value::Bool(value),
|
||||
Avm1Value::Number(value) => Value::Number(value),
|
||||
Avm1Value::String(value) => Value::String(value.to_string()),
|
||||
Avm1Value::Object(object) => {
|
||||
if activation
|
||||
.context
|
||||
.avm1
|
||||
.prototypes()
|
||||
.array
|
||||
.is_prototype_of(object)
|
||||
{
|
||||
if object.as_array_object().is_some() {
|
||||
let length = object.length(activation)?;
|
||||
let values: Result<Vec<_>, crate::avm1::error::Error<'gc>> = (0..length)
|
||||
let values: Result<Vec<_>, Avm1Error<'gc>> = (0..length)
|
||||
.map(|i| {
|
||||
let element = object.get_element(activation, i);
|
||||
Value::from_avm1(activation, element)
|
||||
|
@ -170,17 +165,14 @@ impl Value {
|
|||
}
|
||||
object.into()
|
||||
}
|
||||
Value::List(values) => {
|
||||
let array = Avm1ScriptObject::array(
|
||||
Value::List(values) => Avm1ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes().array),
|
||||
);
|
||||
for (i, value) in values.iter().enumerate() {
|
||||
let element = value.to_owned().into_avm1(activation);
|
||||
array.set_element(activation, i as i32, element).unwrap();
|
||||
}
|
||||
array.into()
|
||||
}
|
||||
activation.context.avm1.prototypes().array,
|
||||
values
|
||||
.iter()
|
||||
.map(|value| value.to_owned().into_avm1(activation)),
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
use crate::avm1::activation::Activation as Avm1Activation;
|
||||
use crate::avm1::{
|
||||
AvmString, Object as Avm1Object, ScriptObject as Avm1ScriptObject, TObject as Avm1TObject,
|
||||
Value as Avm1Value,
|
||||
ArrayObject as Avm1ArrayObject, AvmString, Object as Avm1Object,
|
||||
ScriptObject as Avm1ScriptObject, TObject as Avm1TObject, Value as Avm1Value,
|
||||
};
|
||||
use crate::avm2::{
|
||||
Activation as Avm2Activation, ArrayObject as Avm2ArrayObject, Error as Avm2Error,
|
||||
|
@ -677,20 +677,17 @@ impl TextFormat {
|
|||
activation,
|
||||
)?;
|
||||
|
||||
let tab_stops = if let Some(ts) = &self.tab_stops {
|
||||
let tab_stops = Avm1ScriptObject::array(
|
||||
let tab_stops = self
|
||||
.tab_stops
|
||||
.as_ref()
|
||||
.map_or(Avm1Value::Null, |tab_stops| {
|
||||
Avm1ArrayObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes().array),
|
||||
);
|
||||
for (i, &tab) in ts.iter().enumerate() {
|
||||
tab_stops
|
||||
.set_element(activation, i as i32, tab.into())
|
||||
.unwrap();
|
||||
}
|
||||
tab_stops.into()
|
||||
} else {
|
||||
Avm1Value::Null
|
||||
};
|
||||
activation.context.avm1.prototypes().array,
|
||||
tab_stops.iter().map(|&x| x.into()),
|
||||
)
|
||||
.into()
|
||||
});
|
||||
object.set("tabStops", tab_stops, activation)?;
|
||||
Ok(object.into())
|
||||
}
|
||||
|
|
|
@ -1151,7 +1151,7 @@ impl<'gc> XmlNode<'gc> {
|
|||
/// that yield `true` shall be printed.
|
||||
pub fn into_string<F>(self, filter: &mut F) -> Result<String, Error>
|
||||
where
|
||||
F: FnMut(XmlNode<'gc>) -> bool,
|
||||
F: FnMut(&XmlNode<'gc>) -> bool,
|
||||
{
|
||||
let mut buf = Vec::new();
|
||||
let mut writer = Writer::new(Cursor::new(&mut buf));
|
||||
|
@ -1173,9 +1173,9 @@ impl<'gc> XmlNode<'gc> {
|
|||
) -> Result<(), Error>
|
||||
where
|
||||
W: Write,
|
||||
F: FnMut(XmlNode<'gc>) -> bool,
|
||||
F: FnMut(&XmlNode<'gc>) -> bool,
|
||||
{
|
||||
let children: Vec<_> = self.children().filter(|child| filter(*child)).collect();
|
||||
let children: Vec<_> = self.children().filter(|child| filter(child)).collect();
|
||||
let children_len = children.len();
|
||||
|
||||
match &*self.0.read() {
|
||||
|
|
Loading…
Reference in New Issue