commit
f55e8036bb
143
core/src/avm1.rs
143
core/src/avm1.rs
|
@ -27,6 +27,7 @@ mod return_value;
|
||||||
mod scope;
|
mod scope;
|
||||||
pub mod script_object;
|
pub mod script_object;
|
||||||
mod stage_object;
|
mod stage_object;
|
||||||
|
mod super_object;
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -51,8 +52,9 @@ pub struct Avm1<'gc> {
|
||||||
/// The Flash Player version we're emulating.
|
/// The Flash Player version we're emulating.
|
||||||
player_version: u8,
|
player_version: u8,
|
||||||
|
|
||||||
/// The currently installed constant pool.
|
/// The constant pool to use for new activations from code sources that
|
||||||
constant_pool: Vec<String>,
|
/// don't close over the constant pool they were defined with.
|
||||||
|
constant_pool: GcCell<'gc, Vec<String>>,
|
||||||
|
|
||||||
/// The global object.
|
/// The global object.
|
||||||
globals: Object<'gc>,
|
globals: Object<'gc>,
|
||||||
|
@ -78,6 +80,7 @@ unsafe impl<'gc> gc_arena::Collect for Avm1<'gc> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn trace(&self, cc: gc_arena::CollectionContext) {
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
||||||
self.globals.trace(cc);
|
self.globals.trace(cc);
|
||||||
|
self.constant_pool.trace(cc);
|
||||||
self.prototypes.trace(cc);
|
self.prototypes.trace(cc);
|
||||||
self.display_properties.trace(cc);
|
self.display_properties.trace(cc);
|
||||||
self.stack_frames.trace(cc);
|
self.stack_frames.trace(cc);
|
||||||
|
@ -97,7 +100,7 @@ impl<'gc> Avm1<'gc> {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
player_version,
|
player_version,
|
||||||
constant_pool: vec![],
|
constant_pool: GcCell::allocate(gc_context, vec![]),
|
||||||
globals,
|
globals,
|
||||||
prototypes,
|
prototypes,
|
||||||
display_properties: stage_object::DisplayPropertyMap::new(gc_context),
|
display_properties: stage_object::DisplayPropertyMap::new(gc_context),
|
||||||
|
@ -170,7 +173,14 @@ impl<'gc> Avm1<'gc> {
|
||||||
);
|
);
|
||||||
self.stack_frames.push(GcCell::allocate(
|
self.stack_frames.push(GcCell::allocate(
|
||||||
action_context.gc_context,
|
action_context.gc_context,
|
||||||
Activation::from_action(swf_version, code, child_scope, clip_obj, None),
|
Activation::from_action(
|
||||||
|
swf_version,
|
||||||
|
code,
|
||||||
|
child_scope,
|
||||||
|
self.constant_pool,
|
||||||
|
clip_obj,
|
||||||
|
None,
|
||||||
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +207,14 @@ impl<'gc> Avm1<'gc> {
|
||||||
self.push(Value::Undefined);
|
self.push(Value::Undefined);
|
||||||
self.stack_frames.push(GcCell::allocate(
|
self.stack_frames.push(GcCell::allocate(
|
||||||
action_context.gc_context,
|
action_context.gc_context,
|
||||||
Activation::from_action(swf_version, code, child_scope, clip_obj, None),
|
Activation::from_action(
|
||||||
|
swf_version,
|
||||||
|
code,
|
||||||
|
child_scope,
|
||||||
|
self.constant_pool,
|
||||||
|
clip_obj,
|
||||||
|
None,
|
||||||
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,6 +387,7 @@ impl<'gc> Avm1<'gc> {
|
||||||
Action::Call => self.action_call(context),
|
Action::Call => self.action_call(context),
|
||||||
Action::CallFunction => self.action_call_function(context),
|
Action::CallFunction => self.action_call_function(context),
|
||||||
Action::CallMethod => self.action_call_method(context),
|
Action::CallMethod => self.action_call_method(context),
|
||||||
|
Action::CastOp => self.action_cast_op(context),
|
||||||
Action::CharToAscii => self.action_char_to_ascii(context),
|
Action::CharToAscii => self.action_char_to_ascii(context),
|
||||||
Action::CloneSprite => self.action_clone_sprite(context),
|
Action::CloneSprite => self.action_clone_sprite(context),
|
||||||
Action::ConstantPool(constant_pool) => {
|
Action::ConstantPool(constant_pool) => {
|
||||||
|
@ -392,6 +410,7 @@ impl<'gc> Avm1<'gc> {
|
||||||
Action::Enumerate2 => self.action_enumerate_2(context),
|
Action::Enumerate2 => self.action_enumerate_2(context),
|
||||||
Action::Equals => self.action_equals(context),
|
Action::Equals => self.action_equals(context),
|
||||||
Action::Equals2 => self.action_equals_2(context),
|
Action::Equals2 => self.action_equals_2(context),
|
||||||
|
Action::Extends => self.action_extends(context),
|
||||||
Action::GetMember => self.action_get_member(context),
|
Action::GetMember => self.action_get_member(context),
|
||||||
Action::GetProperty => self.action_get_property(context),
|
Action::GetProperty => self.action_get_property(context),
|
||||||
Action::GetTime => self.action_get_time(context),
|
Action::GetTime => self.action_get_time(context),
|
||||||
|
@ -415,6 +434,7 @@ impl<'gc> Avm1<'gc> {
|
||||||
Action::Increment => self.action_increment(context),
|
Action::Increment => self.action_increment(context),
|
||||||
Action::InitArray => self.action_init_array(context),
|
Action::InitArray => self.action_init_array(context),
|
||||||
Action::InitObject => self.action_init_object(context),
|
Action::InitObject => self.action_init_object(context),
|
||||||
|
Action::ImplementsOp => self.action_implements_op(context),
|
||||||
Action::InstanceOf => self.action_instance_of(context),
|
Action::InstanceOf => self.action_instance_of(context),
|
||||||
Action::Jump { offset } => self.action_jump(context, offset, reader),
|
Action::Jump { offset } => self.action_jump(context, offset, reader),
|
||||||
Action::Less => self.action_less(context),
|
Action::Less => self.action_less(context),
|
||||||
|
@ -808,12 +828,38 @@ impl<'gc> Avm1<'gc> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn action_cast_op(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
||||||
|
let obj = self.pop()?.as_object()?;
|
||||||
|
let constr = self.pop()?.as_object()?;
|
||||||
|
|
||||||
|
let prototype = constr
|
||||||
|
.get("prototype", self, context)?
|
||||||
|
.resolve(self, context)?
|
||||||
|
.as_object()?;
|
||||||
|
|
||||||
|
if obj.is_instance_of(self, context, constr, prototype)? {
|
||||||
|
self.push(obj);
|
||||||
|
} else {
|
||||||
|
self.push(Value::Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn action_constant_pool(
|
fn action_constant_pool(
|
||||||
&mut self,
|
&mut self,
|
||||||
_context: &mut UpdateContext,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
constant_pool: &[&str],
|
constant_pool: &[&str],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.constant_pool = constant_pool.iter().map(|s| s.to_string()).collect();
|
self.constant_pool = GcCell::allocate(
|
||||||
|
context.gc_context,
|
||||||
|
constant_pool.iter().map(|s| s.to_string()).collect(),
|
||||||
|
);
|
||||||
|
self.current_stack_frame()
|
||||||
|
.unwrap()
|
||||||
|
.write(context.gc_context)
|
||||||
|
.set_constant_pool(self.constant_pool);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -842,7 +888,9 @@ impl<'gc> Avm1<'gc> {
|
||||||
self.current_stack_frame().unwrap().read().scope_cell(),
|
self.current_stack_frame().unwrap().read().scope_cell(),
|
||||||
context.gc_context,
|
context.gc_context,
|
||||||
);
|
);
|
||||||
let func = Avm1Function::from_df1(swf_version, func_data, name, params, scope);
|
let constant_pool = self.current_stack_frame().unwrap().read().constant_pool();
|
||||||
|
let func =
|
||||||
|
Avm1Function::from_df1(swf_version, func_data, name, params, scope, constant_pool);
|
||||||
let prototype =
|
let prototype =
|
||||||
ScriptObject::object(context.gc_context, Some(self.prototypes.object)).into();
|
ScriptObject::object(context.gc_context, Some(self.prototypes.object)).into();
|
||||||
let func_obj = ScriptObject::function(
|
let func_obj = ScriptObject::function(
|
||||||
|
@ -880,7 +928,9 @@ impl<'gc> Avm1<'gc> {
|
||||||
self.current_stack_frame().unwrap().read().scope_cell(),
|
self.current_stack_frame().unwrap().read().scope_cell(),
|
||||||
context.gc_context,
|
context.gc_context,
|
||||||
);
|
);
|
||||||
let func = Avm1Function::from_df2(swf_version, func_data, action_func, scope);
|
let constant_pool = self.current_stack_frame().unwrap().read().constant_pool();
|
||||||
|
let func =
|
||||||
|
Avm1Function::from_df2(swf_version, func_data, action_func, scope, constant_pool);
|
||||||
let prototype =
|
let prototype =
|
||||||
ScriptObject::object(context.gc_context, Some(self.prototypes.object)).into();
|
ScriptObject::object(context.gc_context, Some(self.prototypes.object)).into();
|
||||||
let func_obj = ScriptObject::function(
|
let func_obj = ScriptObject::function(
|
||||||
|
@ -1034,6 +1084,27 @@ impl<'gc> Avm1<'gc> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn action_extends(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
||||||
|
let superclass = self.pop()?.as_object()?;
|
||||||
|
let subclass = self.pop()?.as_object()?;
|
||||||
|
|
||||||
|
//TODO: What happens if we try to extend an object which has no `prototype`?
|
||||||
|
//e.g. `class Whatever extends Object.prototype` or `class Whatever extends 5`
|
||||||
|
let super_proto = superclass
|
||||||
|
.get("prototype", self, context)?
|
||||||
|
.resolve(self, context)
|
||||||
|
.and_then(|val| val.as_object())
|
||||||
|
.unwrap_or(self.prototypes.object);
|
||||||
|
|
||||||
|
let sub_prototype: Object<'gc> =
|
||||||
|
ScriptObject::object(context.gc_context, Some(super_proto)).into();
|
||||||
|
|
||||||
|
sub_prototype.set("constructor", superclass.into(), self, context)?;
|
||||||
|
subclass.set("prototype", sub_prototype.into(), self, context)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn action_get_member(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
fn action_get_member(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
||||||
let name_val = self.pop()?;
|
let name_val = self.pop()?;
|
||||||
let name = name_val.coerce_to_string(self, context)?;
|
let name = name_val.coerce_to_string(self, context)?;
|
||||||
|
@ -1324,6 +1395,30 @@ impl<'gc> Avm1<'gc> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn action_implements_op(
|
||||||
|
&mut self,
|
||||||
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let constr = self.pop()?.as_object()?;
|
||||||
|
let count = self.pop()?.as_i64()?; //TODO: Is this coercion actually performed by Flash?
|
||||||
|
let mut interfaces = vec![];
|
||||||
|
|
||||||
|
//TODO: If one of the interfaces is not an object, do we leave the
|
||||||
|
//whole stack dirty, or...?
|
||||||
|
for _ in 0..count {
|
||||||
|
interfaces.push(self.pop()?.as_object()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut prototype = constr
|
||||||
|
.get("prototype", self, context)?
|
||||||
|
.resolve(self, context)?
|
||||||
|
.as_object()?;
|
||||||
|
|
||||||
|
prototype.set_interfaces(context.gc_context, interfaces);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn action_instance_of(
|
fn action_instance_of(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
@ -1331,23 +1426,13 @@ impl<'gc> Avm1<'gc> {
|
||||||
let constr = self.pop()?.as_object()?;
|
let constr = self.pop()?.as_object()?;
|
||||||
let obj = self.pop()?.as_object()?;
|
let obj = self.pop()?.as_object()?;
|
||||||
|
|
||||||
//TODO: Interface detection on SWF7
|
|
||||||
let prototype = constr
|
let prototype = constr
|
||||||
.get("prototype", self, context)?
|
.get("prototype", self, context)?
|
||||||
.resolve(self, context)?
|
.resolve(self, context)?
|
||||||
.as_object()?;
|
.as_object()?;
|
||||||
let mut proto = obj.proto();
|
let is_instance_of = obj.is_instance_of(self, context, constr, prototype)?;
|
||||||
|
|
||||||
while let Some(this_proto) = proto {
|
self.push(is_instance_of);
|
||||||
if Object::ptr_eq(this_proto, prototype) {
|
|
||||||
self.push(true);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
proto = this_proto.proto();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.push(false);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1583,13 +1668,25 @@ impl<'gc> Avm1<'gc> {
|
||||||
SwfValue::Str(v) => v.to_string().into(),
|
SwfValue::Str(v) => v.to_string().into(),
|
||||||
SwfValue::Register(v) => self.current_register(*v),
|
SwfValue::Register(v) => self.current_register(*v),
|
||||||
SwfValue::ConstantPool(i) => {
|
SwfValue::ConstantPool(i) => {
|
||||||
if let Some(value) = self.constant_pool.get(*i as usize) {
|
if let Some(value) = self
|
||||||
|
.current_stack_frame()
|
||||||
|
.unwrap()
|
||||||
|
.read()
|
||||||
|
.constant_pool()
|
||||||
|
.read()
|
||||||
|
.get(*i as usize)
|
||||||
|
{
|
||||||
value.to_string().into()
|
value.to_string().into()
|
||||||
} else {
|
} else {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"ActionPush: Constant pool index {} out of range (len = {})",
|
"ActionPush: Constant pool index {} out of range (len = {})",
|
||||||
i,
|
i,
|
||||||
self.constant_pool.len()
|
self.current_stack_frame()
|
||||||
|
.unwrap()
|
||||||
|
.read()
|
||||||
|
.constant_pool()
|
||||||
|
.read()
|
||||||
|
.len()
|
||||||
);
|
);
|
||||||
Value::Undefined
|
Value::Undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,9 @@ pub struct Activation<'gc> {
|
||||||
/// All defined local variables in this stack frame.
|
/// All defined local variables in this stack frame.
|
||||||
scope: GcCell<'gc, Scope<'gc>>,
|
scope: GcCell<'gc, Scope<'gc>>,
|
||||||
|
|
||||||
|
/// The currently in use constant pool.
|
||||||
|
constant_pool: GcCell<'gc, Vec<String>>,
|
||||||
|
|
||||||
/// The immutable value of `this`.
|
/// The immutable value of `this`.
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
|
|
||||||
|
@ -114,6 +117,7 @@ impl<'gc> Activation<'gc> {
|
||||||
swf_version: u8,
|
swf_version: u8,
|
||||||
code: SwfSlice,
|
code: SwfSlice,
|
||||||
scope: GcCell<'gc, Scope<'gc>>,
|
scope: GcCell<'gc, Scope<'gc>>,
|
||||||
|
constant_pool: GcCell<'gc, Vec<String>>,
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
arguments: Option<Object<'gc>>,
|
arguments: Option<Object<'gc>>,
|
||||||
) -> Activation<'gc> {
|
) -> Activation<'gc> {
|
||||||
|
@ -122,6 +126,7 @@ impl<'gc> Activation<'gc> {
|
||||||
data: code,
|
data: code,
|
||||||
pc: 0,
|
pc: 0,
|
||||||
scope,
|
scope,
|
||||||
|
constant_pool,
|
||||||
this,
|
this,
|
||||||
arguments,
|
arguments,
|
||||||
return_value: None,
|
return_value: None,
|
||||||
|
@ -135,6 +140,7 @@ impl<'gc> Activation<'gc> {
|
||||||
swf_version: u8,
|
swf_version: u8,
|
||||||
code: SwfSlice,
|
code: SwfSlice,
|
||||||
scope: GcCell<'gc, Scope<'gc>>,
|
scope: GcCell<'gc, Scope<'gc>>,
|
||||||
|
constant_pool: GcCell<'gc, Vec<String>>,
|
||||||
this: Object<'gc>,
|
this: Object<'gc>,
|
||||||
arguments: Option<Object<'gc>>,
|
arguments: Option<Object<'gc>>,
|
||||||
) -> Activation<'gc> {
|
) -> Activation<'gc> {
|
||||||
|
@ -143,6 +149,7 @@ impl<'gc> Activation<'gc> {
|
||||||
data: code,
|
data: code,
|
||||||
pc: 0,
|
pc: 0,
|
||||||
scope,
|
scope,
|
||||||
|
constant_pool,
|
||||||
this,
|
this,
|
||||||
arguments,
|
arguments,
|
||||||
return_value: None,
|
return_value: None,
|
||||||
|
@ -166,6 +173,7 @@ impl<'gc> Activation<'gc> {
|
||||||
) -> Activation<'gc> {
|
) -> Activation<'gc> {
|
||||||
let global_scope = GcCell::allocate(mc, Scope::from_global_object(globals));
|
let global_scope = GcCell::allocate(mc, Scope::from_global_object(globals));
|
||||||
let child_scope = GcCell::allocate(mc, Scope::new_local_scope(global_scope, mc));
|
let child_scope = GcCell::allocate(mc, Scope::new_local_scope(global_scope, mc));
|
||||||
|
let empty_constant_pool = GcCell::allocate(mc, Vec::new());
|
||||||
|
|
||||||
Activation {
|
Activation {
|
||||||
swf_version,
|
swf_version,
|
||||||
|
@ -176,6 +184,7 @@ impl<'gc> Activation<'gc> {
|
||||||
},
|
},
|
||||||
pc: 0,
|
pc: 0,
|
||||||
scope: child_scope,
|
scope: child_scope,
|
||||||
|
constant_pool: empty_constant_pool,
|
||||||
this: globals,
|
this: globals,
|
||||||
arguments: None,
|
arguments: None,
|
||||||
return_value: None,
|
return_value: None,
|
||||||
|
@ -192,6 +201,7 @@ impl<'gc> Activation<'gc> {
|
||||||
data: code,
|
data: code,
|
||||||
pc: 0,
|
pc: 0,
|
||||||
scope,
|
scope,
|
||||||
|
constant_pool: self.constant_pool,
|
||||||
this: self.this,
|
this: self.this,
|
||||||
arguments: self.arguments,
|
arguments: self.arguments,
|
||||||
return_value: None,
|
return_value: None,
|
||||||
|
@ -342,6 +352,14 @@ impl<'gc> Activation<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn constant_pool(&self) -> GcCell<'gc, Vec<String>> {
|
||||||
|
self.constant_pool
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_constant_pool(&mut self, constant_pool: GcCell<'gc, Vec<String>>) {
|
||||||
|
self.constant_pool = constant_pool;
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to lock the activation frame for execution.
|
/// Attempts to lock the activation frame for execution.
|
||||||
///
|
///
|
||||||
/// If this frame is already executing, that is an error condition.
|
/// If this frame is already executing, that is an error condition.
|
||||||
|
|
|
@ -4,6 +4,7 @@ use crate::avm1::activation::Activation;
|
||||||
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::scope::Scope;
|
use crate::avm1::scope::Scope;
|
||||||
|
use crate::avm1::super_object::SuperObject;
|
||||||
use crate::avm1::value::Value;
|
use crate::avm1::value::Value;
|
||||||
use crate::avm1::{Avm1, Error, Object, ScriptObject, TObject, UpdateContext};
|
use crate::avm1::{Avm1, Error, Object, ScriptObject, TObject, UpdateContext};
|
||||||
use crate::display_object::TDisplayObject;
|
use crate::display_object::TDisplayObject;
|
||||||
|
@ -66,6 +67,9 @@ pub struct Avm1Function<'gc> {
|
||||||
|
|
||||||
/// The scope the function was born into.
|
/// The scope the function was born into.
|
||||||
scope: GcCell<'gc, Scope<'gc>>,
|
scope: GcCell<'gc, Scope<'gc>>,
|
||||||
|
|
||||||
|
/// The constant pool the function executes with.
|
||||||
|
constant_pool: GcCell<'gc, Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> Avm1Function<'gc> {
|
impl<'gc> Avm1Function<'gc> {
|
||||||
|
@ -79,6 +83,7 @@ impl<'gc> Avm1Function<'gc> {
|
||||||
name: &str,
|
name: &str,
|
||||||
params: &[&str],
|
params: &[&str],
|
||||||
scope: GcCell<'gc, Scope<'gc>>,
|
scope: GcCell<'gc, Scope<'gc>>,
|
||||||
|
constant_pool: GcCell<'gc, Vec<String>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let name = match name {
|
let name = match name {
|
||||||
"" => None,
|
"" => None,
|
||||||
|
@ -101,6 +106,7 @@ impl<'gc> Avm1Function<'gc> {
|
||||||
preload_global: false,
|
preload_global: false,
|
||||||
params: params.iter().map(|s| (None, s.to_string())).collect(),
|
params: params.iter().map(|s| (None, s.to_string())).collect(),
|
||||||
scope,
|
scope,
|
||||||
|
constant_pool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +116,7 @@ impl<'gc> Avm1Function<'gc> {
|
||||||
actions: SwfSlice,
|
actions: SwfSlice,
|
||||||
swf_function: &swf::avm1::types::Function,
|
swf_function: &swf::avm1::types::Function,
|
||||||
scope: GcCell<'gc, Scope<'gc>>,
|
scope: GcCell<'gc, Scope<'gc>>,
|
||||||
|
constant_pool: GcCell<'gc, Vec<String>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let name = match swf_function.name {
|
let name = match swf_function.name {
|
||||||
"" => None,
|
"" => None,
|
||||||
|
@ -141,6 +148,7 @@ impl<'gc> Avm1Function<'gc> {
|
||||||
preload_global: swf_function.preload_global,
|
preload_global: swf_function.preload_global,
|
||||||
params: owned_params,
|
params: owned_params,
|
||||||
scope,
|
scope,
|
||||||
|
constant_pool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,6 +231,19 @@ impl<'gc> Executable<'gc> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let argcell = arguments.into();
|
let argcell = arguments.into();
|
||||||
|
let super_object: Option<Object<'gc>> = if !af.suppress_super {
|
||||||
|
Some(SuperObject::from_child_object(this, avm, ac)?.into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(super_object) = super_object {
|
||||||
|
super_object
|
||||||
|
.as_super_object()
|
||||||
|
.unwrap()
|
||||||
|
.bind_this(ac.gc_context, super_object);
|
||||||
|
}
|
||||||
|
|
||||||
let effective_ver = if avm.current_swf_version() > 5 {
|
let effective_ver = if avm.current_swf_version() > 5 {
|
||||||
af.swf_version()
|
af.swf_version()
|
||||||
} else {
|
} else {
|
||||||
|
@ -237,6 +258,7 @@ impl<'gc> Executable<'gc> {
|
||||||
effective_ver,
|
effective_ver,
|
||||||
af.data(),
|
af.data(),
|
||||||
child_scope,
|
child_scope,
|
||||||
|
af.constant_pool,
|
||||||
this,
|
this,
|
||||||
Some(argcell),
|
Some(argcell),
|
||||||
),
|
),
|
||||||
|
@ -261,12 +283,15 @@ impl<'gc> Executable<'gc> {
|
||||||
preload_r += 1;
|
preload_r += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if af.preload_super {
|
if let Some(super_object) = super_object {
|
||||||
//TODO: super not implemented
|
if af.preload_super {
|
||||||
log::warn!("Cannot preload super into register because it's not implemented");
|
frame.set_local_register(preload_r, super_object, ac.gc_context);
|
||||||
//TODO: What happens if you specify both suppress and
|
//TODO: What happens if you specify both suppress and
|
||||||
//preload for super?
|
//preload for super?
|
||||||
preload_r += 1;
|
preload_r += 1;
|
||||||
|
} else {
|
||||||
|
frame.define("super", super_object, ac.gc_context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if af.preload_root {
|
if af.preload_root {
|
||||||
|
|
|
@ -232,6 +232,13 @@ pub fn create_globals<'gc>(
|
||||||
EnumSet::empty(),
|
EnumSet::empty(),
|
||||||
Some(function_proto),
|
Some(function_proto),
|
||||||
);
|
);
|
||||||
|
globals.force_set_function(
|
||||||
|
"ASSetPropFlags",
|
||||||
|
object::as_set_prop_flags,
|
||||||
|
gc_context,
|
||||||
|
EnumSet::empty(),
|
||||||
|
Some(function_proto),
|
||||||
|
);
|
||||||
globals.add_property(
|
globals.add_property(
|
||||||
gc_context,
|
gc_context,
|
||||||
"NaN",
|
"NaN",
|
||||||
|
|
|
@ -15,6 +15,7 @@ pub fn constructor<'gc>(
|
||||||
Ok(Value::Undefined.into())
|
Ok(Value::Undefined.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implements `Function.prototype.call`
|
||||||
pub fn call<'gc>(
|
pub fn call<'gc>(
|
||||||
_avm: &mut Avm1<'gc>,
|
_avm: &mut Avm1<'gc>,
|
||||||
_action_context: &mut UpdateContext<'_, 'gc, '_>,
|
_action_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
@ -24,6 +25,7 @@ pub fn call<'gc>(
|
||||||
Ok(Value::Undefined.into())
|
Ok(Value::Undefined.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implements `Function.prototype.apply`
|
||||||
pub fn apply<'gc>(
|
pub fn apply<'gc>(
|
||||||
_avm: &mut Avm1<'gc>,
|
_avm: &mut Avm1<'gc>,
|
||||||
_action_context: &mut UpdateContext<'_, 'gc, '_>,
|
_action_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
@ -33,6 +35,16 @@ pub fn apply<'gc>(
|
||||||
Ok(Value::Undefined.into())
|
Ok(Value::Undefined.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implements `Function.prototype.toString`
|
||||||
|
fn to_string<'gc>(
|
||||||
|
_: &mut Avm1<'gc>,
|
||||||
|
_: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
_: Object<'gc>,
|
||||||
|
_: &[Value<'gc>],
|
||||||
|
) -> Result<ReturnValue<'gc>, Error> {
|
||||||
|
Ok(ReturnValue::Immediate("[type Function]".into()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Partially construct `Function.prototype`.
|
/// Partially construct `Function.prototype`.
|
||||||
///
|
///
|
||||||
/// `__proto__` and other cross-linked properties of this object will *not*
|
/// `__proto__` and other cross-linked properties of this object will *not*
|
||||||
|
@ -51,6 +63,10 @@ pub fn create_proto<'gc>(gc_context: MutationContext<'gc, '_>, proto: Object<'gc
|
||||||
.as_script_object()
|
.as_script_object()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.force_set_function("apply", apply, gc_context, EnumSet::empty(), this);
|
.force_set_function("apply", apply, gc_context, EnumSet::empty(), this);
|
||||||
|
function_proto
|
||||||
|
.as_script_object()
|
||||||
|
.unwrap()
|
||||||
|
.force_set_function("toString", to_string, gc_context, EnumSet::empty(), this);
|
||||||
|
|
||||||
function_proto
|
function_proto
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//! Object prototype
|
//! Object prototype
|
||||||
use crate::avm1::property::Attribute::*;
|
use crate::avm1::property::Attribute::{self, *};
|
||||||
use crate::avm1::return_value::ReturnValue;
|
use crate::avm1::return_value::ReturnValue;
|
||||||
use crate::avm1::{Avm1, Error, Object, TObject, UpdateContext, Value};
|
use crate::avm1::{Avm1, Error, Object, TObject, UpdateContext, Value};
|
||||||
use enumset::EnumSet;
|
use enumset::EnumSet;
|
||||||
|
@ -180,3 +180,76 @@ pub fn fill_proto<'gc>(
|
||||||
Some(fn_proto),
|
Some(fn_proto),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implements `ASSetPropFlags`.
|
||||||
|
///
|
||||||
|
/// This is an undocumented function that allows ActionScript 2.0 classes to
|
||||||
|
/// declare the property flags of a given property. It's not part of
|
||||||
|
/// `Object.prototype`, and I suspect that's a deliberate omission.
|
||||||
|
pub fn as_set_prop_flags<'gc>(
|
||||||
|
avm: &mut Avm1<'gc>,
|
||||||
|
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
_: Object<'gc>,
|
||||||
|
args: &[Value<'gc>],
|
||||||
|
) -> Result<ReturnValue<'gc>, Error> {
|
||||||
|
//This exists because `.into` won't work inline
|
||||||
|
let my_error: Result<ReturnValue<'gc>, Error> =
|
||||||
|
Err("ASSetPropFlags called without object to apply to!".into());
|
||||||
|
let my_error_2: Result<ReturnValue<'gc>, Error> =
|
||||||
|
Err("ASSetPropFlags called without object list!".into());
|
||||||
|
|
||||||
|
let mut object = args
|
||||||
|
.get(0)
|
||||||
|
.ok_or_else(|| my_error.unwrap_err())?
|
||||||
|
.as_object()?;
|
||||||
|
let properties = match args.get(1).ok_or_else(|| my_error_2.unwrap_err())? {
|
||||||
|
Value::Object(ob) => {
|
||||||
|
//Convert to native array.
|
||||||
|
//TODO: Can we make this an iterator?
|
||||||
|
let mut array = vec![];
|
||||||
|
let length = ob
|
||||||
|
.get("length", avm, ac)?
|
||||||
|
.resolve(avm, ac)?
|
||||||
|
.as_number(avm, ac)? as usize;
|
||||||
|
for i in 0..length {
|
||||||
|
array.push(
|
||||||
|
ob.get(&format!("{}", i), avm, ac)?
|
||||||
|
.resolve(avm, ac)?
|
||||||
|
.coerce_to_string(avm, ac)?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(array)
|
||||||
|
}
|
||||||
|
Value::String(s) => Some(s.split(',').map(String::from).collect()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let set_attributes = EnumSet::<Attribute>::from_bits(
|
||||||
|
args.get(2)
|
||||||
|
.unwrap_or(&Value::Number(0.0))
|
||||||
|
.as_number(avm, ac)? as u128,
|
||||||
|
);
|
||||||
|
|
||||||
|
let clear_attributes = EnumSet::<Attribute>::from_bits(
|
||||||
|
args.get(3)
|
||||||
|
.unwrap_or(&Value::Number(0.0))
|
||||||
|
.as_number(avm, ac)? as u128,
|
||||||
|
);
|
||||||
|
|
||||||
|
match properties {
|
||||||
|
Some(properties) => {
|
||||||
|
for prop_name in properties {
|
||||||
|
object.set_attributes(
|
||||||
|
ac.gc_context,
|
||||||
|
Some(&prop_name),
|
||||||
|
set_attributes,
|
||||||
|
clear_attributes,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => object.set_attributes(ac.gc_context, None, set_attributes, clear_attributes),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::Undefined.into())
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +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::super_object::SuperObject;
|
||||||
use crate::avm1::{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;
|
||||||
|
@ -19,6 +20,7 @@ use std::fmt::Debug;
|
||||||
pub enum Object<'gc> {
|
pub enum Object<'gc> {
|
||||||
ScriptObject(ScriptObject<'gc>),
|
ScriptObject(ScriptObject<'gc>),
|
||||||
StageObject(StageObject<'gc>),
|
StageObject(StageObject<'gc>),
|
||||||
|
SuperObject(SuperObject<'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 {
|
||||||
|
@ -124,6 +126,21 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
||||||
attributes: EnumSet<Attribute>,
|
attributes: EnumSet<Attribute>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Set the attributes of a given property.
|
||||||
|
///
|
||||||
|
/// Leaving `name` unspecified allows setting all properties on a given
|
||||||
|
/// object to the same set of properties.
|
||||||
|
///
|
||||||
|
/// Attributes can be set, cleared, or left as-is using the pairs of `set_`
|
||||||
|
/// and `clear_attributes` parameters.
|
||||||
|
fn set_attributes(
|
||||||
|
&mut self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Option<&str>,
|
||||||
|
set_attributes: EnumSet<Attribute>,
|
||||||
|
clear_attributes: EnumSet<Attribute>,
|
||||||
|
);
|
||||||
|
|
||||||
/// Define a virtual property onto a given object.
|
/// Define a virtual property onto a given object.
|
||||||
///
|
///
|
||||||
/// A virtual property is a set of get/set functions that are called when a
|
/// A virtual property is a set of get/set functions that are called when a
|
||||||
|
@ -165,9 +182,75 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
||||||
/// Get the object's type string.
|
/// Get the object's type string.
|
||||||
fn type_of(&self) -> &'static str;
|
fn type_of(&self) -> &'static str;
|
||||||
|
|
||||||
|
/// Enumerate all interfaces implemented by this object.
|
||||||
|
fn interfaces(&self) -> Vec<Object<'gc>>;
|
||||||
|
|
||||||
|
/// Set the interface list for this object. (Only useful for prototypes.)
|
||||||
|
fn set_interfaces(
|
||||||
|
&mut self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
iface_list: Vec<Object<'gc>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Determine if this object is an instance of a class.
|
||||||
|
///
|
||||||
|
/// The class is provided in the form of it's constructor function and the
|
||||||
|
/// explicit prototype of that constructor function. It is assumed that
|
||||||
|
/// they are already linked.
|
||||||
|
///
|
||||||
|
/// Because ActionScript 2.0 added interfaces, this function cannot simply
|
||||||
|
/// check the prototype chain and call it a day. Each interface represents
|
||||||
|
/// a new, parallel prototype chain which also needs to be checked. You
|
||||||
|
/// can't implement interfaces within interfaces (fortunately), but if you
|
||||||
|
/// somehow could this would support that, too.
|
||||||
|
fn is_instance_of(
|
||||||
|
&self,
|
||||||
|
avm: &mut Avm1<'gc>,
|
||||||
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
constructor: Object<'gc>,
|
||||||
|
prototype: Object<'gc>,
|
||||||
|
) -> Result<bool, Error> {
|
||||||
|
let mut proto_stack = vec![];
|
||||||
|
if let Some(p) = self.proto() {
|
||||||
|
proto_stack.push(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(this_proto) = proto_stack.pop() {
|
||||||
|
if Object::ptr_eq(this_proto, prototype) {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(p) = this_proto.proto() {
|
||||||
|
proto_stack.push(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if avm.current_swf_version() >= 7 {
|
||||||
|
for interface in this_proto.interfaces() {
|
||||||
|
if Object::ptr_eq(interface, constructor) {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Value::Object(o) = interface
|
||||||
|
.get("prototype", avm, context)?
|
||||||
|
.resolve(avm, context)?
|
||||||
|
{
|
||||||
|
proto_stack.push(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the underlying script object, if it exists.
|
/// Get the underlying script object, if it exists.
|
||||||
fn as_script_object(&self) -> Option<ScriptObject<'gc>>;
|
fn as_script_object(&self) -> Option<ScriptObject<'gc>>;
|
||||||
|
|
||||||
|
/// Get the underlying super object, if it exists.
|
||||||
|
fn as_super_object(&self) -> Option<SuperObject<'gc>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the underlying display node for this object, if it exists.
|
/// Get the underlying display node for this object, if it exists.
|
||||||
fn as_display_object(&self) -> Option<DisplayObject<'gc>>;
|
fn as_display_object(&self) -> Option<DisplayObject<'gc>>;
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,12 @@ use core::fmt;
|
||||||
use enumset::{EnumSet, EnumSetType};
|
use enumset::{EnumSet, EnumSetType};
|
||||||
use std::mem::replace;
|
use std::mem::replace;
|
||||||
|
|
||||||
|
/// Attributes of properties in the AVM runtime.
|
||||||
|
/// The order is significant and should match the order used by `object::as_set_prop_flags`.
|
||||||
#[derive(EnumSetType, Debug)]
|
#[derive(EnumSetType, Debug)]
|
||||||
pub enum Attribute {
|
pub enum Attribute {
|
||||||
DontDelete,
|
|
||||||
DontEnum,
|
DontEnum,
|
||||||
|
DontDelete,
|
||||||
ReadOnly,
|
ReadOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +81,26 @@ impl<'gc> Property<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List this property's attributes.
|
||||||
|
pub fn attributes(&self) -> EnumSet<Attribute> {
|
||||||
|
match self {
|
||||||
|
Property::Virtual { attributes, .. } => *attributes,
|
||||||
|
Property::Stored { attributes, .. } => *attributes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Re-define this property's attributes.
|
||||||
|
pub fn set_attributes(&mut self, new_attributes: EnumSet<Attribute>) {
|
||||||
|
match self {
|
||||||
|
Property::Virtual {
|
||||||
|
ref mut attributes, ..
|
||||||
|
} => *attributes = new_attributes,
|
||||||
|
Property::Stored {
|
||||||
|
ref mut attributes, ..
|
||||||
|
} => *attributes = new_attributes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn can_delete(&self) -> bool {
|
pub fn can_delete(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Property::Virtual { attributes, .. } => !attributes.contains(DontDelete),
|
Property::Virtual { attributes, .. } => !attributes.contains(DontDelete),
|
||||||
|
|
|
@ -27,6 +27,7 @@ pub struct ScriptObjectData<'gc> {
|
||||||
prototype: Option<Object<'gc>>,
|
prototype: Option<Object<'gc>>,
|
||||||
values: HashMap<String, Property<'gc>>,
|
values: HashMap<String, Property<'gc>>,
|
||||||
function: Option<Executable<'gc>>,
|
function: Option<Executable<'gc>>,
|
||||||
|
interfaces: Vec<Object<'gc>>,
|
||||||
type_of: &'static str,
|
type_of: &'static str,
|
||||||
array: ArrayStorage<'gc>,
|
array: ArrayStorage<'gc>,
|
||||||
}
|
}
|
||||||
|
@ -37,6 +38,7 @@ unsafe impl<'gc> Collect for ScriptObjectData<'gc> {
|
||||||
self.values.trace(cc);
|
self.values.trace(cc);
|
||||||
self.function.trace(cc);
|
self.function.trace(cc);
|
||||||
self.array.trace(cc);
|
self.array.trace(cc);
|
||||||
|
self.interfaces.trace(cc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +66,7 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
values: HashMap::new(),
|
values: HashMap::new(),
|
||||||
function: None,
|
function: None,
|
||||||
array: ArrayStorage::Properties { length: 0 },
|
array: ArrayStorage::Properties { length: 0 },
|
||||||
|
interfaces: vec![],
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -80,6 +83,7 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
values: HashMap::new(),
|
values: HashMap::new(),
|
||||||
function: None,
|
function: None,
|
||||||
array: ArrayStorage::Vector(Vec::new()),
|
array: ArrayStorage::Vector(Vec::new()),
|
||||||
|
interfaces: vec![],
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
object.sync_native_property("length", gc_context, Some(0.into()));
|
object.sync_native_property("length", gc_context, Some(0.into()));
|
||||||
|
@ -99,6 +103,7 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
values: HashMap::new(),
|
values: HashMap::new(),
|
||||||
function: None,
|
function: None,
|
||||||
array: ArrayStorage::Properties { length: 0 },
|
array: ArrayStorage::Properties { length: 0 },
|
||||||
|
interfaces: vec![],
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.into()
|
.into()
|
||||||
|
@ -118,6 +123,7 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
values: HashMap::new(),
|
values: HashMap::new(),
|
||||||
function: None,
|
function: None,
|
||||||
array: ArrayStorage::Properties { length: 0 },
|
array: ArrayStorage::Properties { length: 0 },
|
||||||
|
interfaces: vec![],
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -136,6 +142,7 @@ impl<'gc> ScriptObject<'gc> {
|
||||||
function: Some(function.into()),
|
function: Some(function.into()),
|
||||||
values: HashMap::new(),
|
values: HashMap::new(),
|
||||||
array: ArrayStorage::Properties { length: 0 },
|
array: ArrayStorage::Properties { length: 0 },
|
||||||
|
interfaces: vec![],
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -391,6 +398,30 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||||
.insert(name.to_string(), Property::Stored { value, attributes });
|
.insert(name.to_string(), Property::Stored { value, attributes });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_attributes(
|
||||||
|
&mut self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Option<&str>,
|
||||||
|
set_attributes: EnumSet<Attribute>,
|
||||||
|
clear_attributes: EnumSet<Attribute>,
|
||||||
|
) {
|
||||||
|
match name {
|
||||||
|
None => {
|
||||||
|
// Change *all* attributes.
|
||||||
|
for (_name, prop) in self.0.write(gc_context).values.iter_mut() {
|
||||||
|
let new_atts = (prop.attributes() - clear_attributes) | set_attributes;
|
||||||
|
prop.set_attributes(new_atts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(name) => {
|
||||||
|
if let Some(prop) = self.0.write(gc_context).values.get_mut(name) {
|
||||||
|
let new_atts = (prop.attributes() - clear_attributes) | set_attributes;
|
||||||
|
prop.set_attributes(new_atts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn proto(&self) -> Option<Object<'gc>> {
|
fn proto(&self) -> Option<Object<'gc>> {
|
||||||
self.0.read().prototype
|
self.0.read().prototype
|
||||||
}
|
}
|
||||||
|
@ -465,6 +496,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
||||||
self.0.read().type_of
|
self.0.read().type_of
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn interfaces(&self) -> Vec<Object<'gc>> {
|
||||||
|
self.0.read().interfaces.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_interfaces(&mut self, context: MutationContext<'gc, '_>, iface_list: Vec<Object<'gc>>) {
|
||||||
|
self.0.write(context).interfaces = iface_list;
|
||||||
|
}
|
||||||
|
|
||||||
fn as_script_object(&self) -> Option<ScriptObject<'gc>> {
|
fn as_script_object(&self) -> Option<ScriptObject<'gc>> {
|
||||||
Some(*self)
|
Some(*self)
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,6 +150,17 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
|
||||||
self.base.define_value(gc_context, name, value, attributes)
|
self.base.define_value(gc_context, name, value, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_attributes(
|
||||||
|
&mut self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
name: Option<&str>,
|
||||||
|
set_attributes: EnumSet<Attribute>,
|
||||||
|
clear_attributes: EnumSet<Attribute>,
|
||||||
|
) {
|
||||||
|
self.base
|
||||||
|
.set_attributes(gc_context, name, set_attributes, clear_attributes)
|
||||||
|
}
|
||||||
|
|
||||||
fn add_property(
|
fn add_property(
|
||||||
&self,
|
&self,
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
@ -228,6 +239,14 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
|
||||||
self.base.delete_array_element(index, gc_context)
|
self.base.delete_array_element(index, gc_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn interfaces(&self) -> Vec<Object<'gc>> {
|
||||||
|
self.base.interfaces()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_interfaces(&mut self, context: MutationContext<'gc, '_>, iface_list: Vec<Object<'gc>>) {
|
||||||
|
self.base.set_interfaces(context, iface_list)
|
||||||
|
}
|
||||||
|
|
||||||
fn as_string(&self) -> String {
|
fn as_string(&self) -> String {
|
||||||
self.display_object.path()
|
self.display_object.path()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,252 @@
|
||||||
|
//! Special object that implements `super`
|
||||||
|
|
||||||
|
use crate::avm1::function::Executable;
|
||||||
|
use crate::avm1::property::Attribute;
|
||||||
|
use crate::avm1::return_value::ReturnValue;
|
||||||
|
use crate::avm1::script_object::TYPE_OF_OBJECT;
|
||||||
|
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::HashSet;
|
||||||
|
|
||||||
|
/// Implementation of the `super` object in AS2.
|
||||||
|
///
|
||||||
|
/// A `SuperObject` references all data from another object, but with one layer
|
||||||
|
/// of prototyping removed. It's as if the given object had been constructed
|
||||||
|
/// with it's parent class.
|
||||||
|
#[collect(no_drop)]
|
||||||
|
#[derive(Copy, Clone, Collect, Debug)]
|
||||||
|
pub struct SuperObject<'gc>(GcCell<'gc, SuperObjectData<'gc>>);
|
||||||
|
|
||||||
|
#[collect(no_drop)]
|
||||||
|
#[derive(Clone, Collect, Debug)]
|
||||||
|
pub struct SuperObjectData<'gc> {
|
||||||
|
child: Object<'gc>,
|
||||||
|
proto: Option<Object<'gc>>,
|
||||||
|
constr: Option<Object<'gc>>,
|
||||||
|
this: Option<Object<'gc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> SuperObject<'gc> {
|
||||||
|
pub fn from_child_object(
|
||||||
|
child: Object<'gc>,
|
||||||
|
avm: &mut Avm1<'gc>,
|
||||||
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let child_proto = child.proto();
|
||||||
|
let parent_proto = child_proto.and_then(|pr| pr.proto());
|
||||||
|
let parent_constr = if let Some(child_proto) = child_proto {
|
||||||
|
Some(
|
||||||
|
child_proto
|
||||||
|
.get("constructor", avm, context)?
|
||||||
|
.resolve(avm, context)?
|
||||||
|
.as_object()?,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self(GcCell::allocate(
|
||||||
|
context.gc_context,
|
||||||
|
SuperObjectData {
|
||||||
|
child,
|
||||||
|
proto: parent_proto,
|
||||||
|
constr: parent_constr,
|
||||||
|
this: None,
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set `this` to a particular value.
|
||||||
|
///
|
||||||
|
/// This is intended to be called with a self-reference, so that future
|
||||||
|
/// invocations of `super()` can get a `this` value one level up the chain.
|
||||||
|
pub fn bind_this(&mut self, context: MutationContext<'gc, '_>, this: Object<'gc>) {
|
||||||
|
self.0.write(context).this = Some(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> TObject<'gc> for SuperObject<'gc> {
|
||||||
|
fn get_local(
|
||||||
|
&self,
|
||||||
|
_name: &str,
|
||||||
|
_avm: &mut Avm1<'gc>,
|
||||||
|
_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
_this: Object<'gc>,
|
||||||
|
) -> Result<ReturnValue<'gc>, Error> {
|
||||||
|
Ok(Value::Undefined.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(
|
||||||
|
&self,
|
||||||
|
_name: &str,
|
||||||
|
_value: Value<'gc>,
|
||||||
|
_avm: &mut Avm1<'gc>,
|
||||||
|
_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
//TODO: What happens if you set `super.__proto__`?
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(
|
||||||
|
&self,
|
||||||
|
avm: &mut Avm1<'gc>,
|
||||||
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
this: Object<'gc>,
|
||||||
|
args: &[Value<'gc>],
|
||||||
|
) -> Result<ReturnValue<'gc>, Error> {
|
||||||
|
if let Some(constr) = self.0.read().constr {
|
||||||
|
constr.call(avm, context, self.0.read().this.unwrap_or(this), args)
|
||||||
|
} else {
|
||||||
|
Ok(Value::Undefined.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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> {
|
||||||
|
if let Some(proto) = self.proto() {
|
||||||
|
proto.new(avm, context, this, args)
|
||||||
|
} else {
|
||||||
|
// TODO: What happens when you `new super` but there's no
|
||||||
|
// super? Is this code even reachable?!
|
||||||
|
self.0.read().child.new(avm, context, this, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(&self, _gc_context: MutationContext<'gc, '_>, _name: &str) -> bool {
|
||||||
|
//`super` cannot have properties deleted from it
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proto(&self) -> Option<Object<'gc>> {
|
||||||
|
self.0.read().proto
|
||||||
|
}
|
||||||
|
|
||||||
|
fn define_value(
|
||||||
|
&self,
|
||||||
|
_gc_context: MutationContext<'gc, '_>,
|
||||||
|
_name: &str,
|
||||||
|
_value: Value<'gc>,
|
||||||
|
_attributes: EnumSet<Attribute>,
|
||||||
|
) {
|
||||||
|
//`super` cannot have values defined on it
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_attributes(
|
||||||
|
&mut self,
|
||||||
|
_gc_context: MutationContext<'gc, '_>,
|
||||||
|
_name: Option<&str>,
|
||||||
|
_set_attributes: EnumSet<Attribute>,
|
||||||
|
_clear_attributes: EnumSet<Attribute>,
|
||||||
|
) {
|
||||||
|
//TODO: Does ASSetPropFlags work on `super`? What would it even work on?
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_property(
|
||||||
|
&self,
|
||||||
|
_gc_context: MutationContext<'gc, '_>,
|
||||||
|
_name: &str,
|
||||||
|
_get: Executable<'gc>,
|
||||||
|
_set: Option<Executable<'gc>>,
|
||||||
|
_attributes: EnumSet<Attribute>,
|
||||||
|
) {
|
||||||
|
//`super` cannot have properties defined on it
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_property(&self, name: &str) -> bool {
|
||||||
|
self.0.read().child.has_property(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_own_property(&self, name: &str) -> bool {
|
||||||
|
self.0.read().child.has_own_property(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_property_enumerable(&self, name: &str) -> bool {
|
||||||
|
self.0.read().child.is_property_enumerable(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_property_overwritable(&self, name: &str) -> bool {
|
||||||
|
self.0.read().child.is_property_overwritable(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_keys(&self) -> HashSet<String> {
|
||||||
|
//`super` cannot be enumerated
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_string(&self) -> String {
|
||||||
|
self.0.read().child.as_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_of(&self) -> &'static str {
|
||||||
|
TYPE_OF_OBJECT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn length(&self) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_length(&self, _gc_context: MutationContext<'gc, '_>, _new_length: usize) {}
|
||||||
|
|
||||||
|
fn array(&self) -> Vec<Value<'gc>> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn array_element(&self, _index: usize) -> Value<'gc> {
|
||||||
|
Value::Undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_array_element(
|
||||||
|
&self,
|
||||||
|
_index: usize,
|
||||||
|
_value: Value<'gc>,
|
||||||
|
_gc_context: MutationContext<'gc, '_>,
|
||||||
|
) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_array_element(&self, _index: usize, _gc_context: MutationContext<'gc, '_>) {}
|
||||||
|
|
||||||
|
fn interfaces(&self) -> Vec<Object<'gc>> {
|
||||||
|
//`super` does not implement interfaces
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_interfaces(
|
||||||
|
&mut self,
|
||||||
|
_gc_context: MutationContext<'gc, '_>,
|
||||||
|
_iface_list: Vec<Object<'gc>>,
|
||||||
|
) {
|
||||||
|
//`super` probably cannot have interfaces set on it
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_script_object(&self) -> Option<ScriptObject<'gc>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_super_object(&self) -> Option<SuperObject<'gc>> {
|
||||||
|
Some(*self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_display_object(&self) -> Option<DisplayObject<'gc>> {
|
||||||
|
//`super` actually can be used to invoke MovieClip methods
|
||||||
|
self.0.read().child.as_display_object()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_executable(&self) -> Option<Executable<'gc>> {
|
||||||
|
//well, `super` *can* be called...
|
||||||
|
self.0.read().constr.and_then(|c| c.as_executable())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_ptr(&self) -> *const ObjectPtr {
|
||||||
|
self.0.as_ptr() as *const ObjectPtr
|
||||||
|
}
|
||||||
|
}
|
|
@ -74,7 +74,7 @@ swf_tests! {
|
||||||
(recursive_prototypes, "avm1/recursive_prototypes", 1),
|
(recursive_prototypes, "avm1/recursive_prototypes", 1),
|
||||||
(stage_object_children, "avm1/stage_object_children", 2),
|
(stage_object_children, "avm1/stage_object_children", 2),
|
||||||
(has_own_property, "avm1/has_own_property", 1),
|
(has_own_property, "avm1/has_own_property", 1),
|
||||||
#[ignore] (extends_chain, "avm1/extends_chain", 1),
|
(extends_chain, "avm1/extends_chain", 1),
|
||||||
(is_prototype_of, "avm1/is_prototype_of", 1),
|
(is_prototype_of, "avm1/is_prototype_of", 1),
|
||||||
#[ignore] (string_coercion, "avm1/string_coercion", 1),
|
#[ignore] (string_coercion, "avm1/string_coercion", 1),
|
||||||
(lessthan_swf4, "avm1/lessthan_swf4", 1),
|
(lessthan_swf4, "avm1/lessthan_swf4", 1),
|
||||||
|
@ -89,6 +89,7 @@ swf_tests! {
|
||||||
(equals2_swf7, "avm1/equals2_swf7", 1),
|
(equals2_swf7, "avm1/equals2_swf7", 1),
|
||||||
(strictequals_swf6, "avm1/strictequals_swf6", 1),
|
(strictequals_swf6, "avm1/strictequals_swf6", 1),
|
||||||
(global_is_bare, "avm1/global_is_bare", 1),
|
(global_is_bare, "avm1/global_is_bare", 1),
|
||||||
|
(as2_oop, "avm1/as2_oop", 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
interface MyInterface {
|
||||||
|
function a();
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import MyInterface;
|
||||||
|
|
||||||
|
class MyObject implements MyInterface, MyOtherInterface {
|
||||||
|
function a() {
|
||||||
|
trace("MyObject.a called");
|
||||||
|
}
|
||||||
|
|
||||||
|
function b() {
|
||||||
|
trace("clock crew's back baby");
|
||||||
|
}
|
||||||
|
|
||||||
|
function c() {
|
||||||
|
trace("MyObject.c called");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
interface MyOtherInterface {
|
||||||
|
function b();
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
interface NotMyInterface {
|
||||||
|
function c();
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
[type Function]
|
||||||
|
[object Object]
|
||||||
|
[type Function]
|
||||||
|
[object Object]
|
||||||
|
[object Object]
|
||||||
|
true
|
||||||
|
true
|
||||||
|
false
|
||||||
|
MyObject.a called
|
||||||
|
clock crew's back baby
|
||||||
|
MyObject.c called
|
||||||
|
[object Object]
|
||||||
|
null
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue