375 lines
13 KiB
Rust
375 lines
13 KiB
Rust
//! Object trait to expose objects to AVM
|
|
|
|
use crate::avm1::function::{Executable, FunctionObject};
|
|
use crate::avm1::property::Attribute;
|
|
use crate::avm1::return_value::ReturnValue;
|
|
use crate::avm1::super_object::SuperObject;
|
|
use crate::avm1::value_object::ValueObject;
|
|
use crate::avm1::xml_attributes_object::XMLAttributesObject;
|
|
use crate::avm1::xml_idmap_object::XMLIDMapObject;
|
|
use crate::avm1::xml_object::XMLObject;
|
|
use crate::avm1::{Avm1, Error, ScriptObject, SoundObject, StageObject, UpdateContext, Value};
|
|
use crate::display_object::DisplayObject;
|
|
use crate::xml::XMLNode;
|
|
use enumset::EnumSet;
|
|
use gc_arena::{Collect, MutationContext};
|
|
use ruffle_macros::enum_trait_object;
|
|
use std::collections::HashSet;
|
|
use std::fmt::Debug;
|
|
|
|
/// Represents an object that can be directly interacted with by the AVM
|
|
/// runtime.
|
|
#[enum_trait_object(
|
|
#[derive(Clone, Collect, Debug, Copy)]
|
|
#[collect(no_drop)]
|
|
pub enum Object<'gc> {
|
|
ScriptObject(ScriptObject<'gc>),
|
|
SoundObject(SoundObject<'gc>),
|
|
StageObject(StageObject<'gc>),
|
|
SuperObject(SuperObject<'gc>),
|
|
XMLObject(XMLObject<'gc>),
|
|
XMLAttributesObject(XMLAttributesObject<'gc>),
|
|
XMLIDMapObject(XMLIDMapObject<'gc>),
|
|
ValueObject(ValueObject<'gc>),
|
|
FunctionObject(FunctionObject<'gc>),
|
|
}
|
|
)]
|
|
pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy {
|
|
/// Retrieve a named property from this object exclusively.
|
|
///
|
|
/// This function takes a redundant `this` parameter which should be
|
|
/// the object's own `GcCell`, so that it can pass it to user-defined
|
|
/// overrides that may need to interact with the underlying object.
|
|
///
|
|
/// This function should not inspect prototype chains. Instead, use `get`
|
|
/// to do ordinary property look-up and resolution.
|
|
fn get_local(
|
|
&self,
|
|
name: &str,
|
|
avm: &mut Avm1<'gc>,
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
this: Object<'gc>,
|
|
) -> Result<ReturnValue<'gc>, Error>;
|
|
|
|
/// Retrieve a named property from the object, or it's prototype.
|
|
fn get(
|
|
&self,
|
|
name: &str,
|
|
avm: &mut Avm1<'gc>,
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
|
if self.has_own_property(name) {
|
|
self.get_local(name, avm, context, (*self).into())
|
|
} else {
|
|
search_prototype(self.proto(), name, avm, context, (*self).into())
|
|
}
|
|
}
|
|
|
|
/// Set a named property on this object, or it's prototype.
|
|
fn set(
|
|
&self,
|
|
name: &str,
|
|
value: Value<'gc>,
|
|
avm: &mut Avm1<'gc>,
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
) -> Result<(), Error>;
|
|
|
|
/// Call the underlying object.
|
|
///
|
|
/// This function takes a `this` parameter which generally
|
|
/// refers to the object which has this property, although
|
|
/// it can be changed by `Function.apply`/`Function.call`.
|
|
fn call(
|
|
&self,
|
|
avm: &mut Avm1<'gc>,
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
this: Object<'gc>,
|
|
args: &[Value<'gc>],
|
|
) -> Result<ReturnValue<'gc>, Error>;
|
|
|
|
/// Construct a host object of some kind and return it's cell.
|
|
///
|
|
/// As the first step in object construction, the `new` method is called on
|
|
/// the prototype to initialize an object. The prototype may construct any
|
|
/// object implementation it wants, with itself as the new object's proto.
|
|
/// Then, the constructor is `call`ed with the new object as `this` to
|
|
/// initialize the object.
|
|
///
|
|
/// The arguments passed to the constructor are provided here; however, all
|
|
/// object construction should happen in `call`, not `new`. `new` exists
|
|
/// purely so that host objects can be constructed by the VM.
|
|
fn new(
|
|
&self,
|
|
avm: &mut Avm1<'gc>,
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
this: Object<'gc>,
|
|
args: &[Value<'gc>],
|
|
) -> Result<Object<'gc>, Error>;
|
|
|
|
/// Delete a named property from the object.
|
|
///
|
|
/// Returns false if the property cannot be deleted.
|
|
fn delete(&self, gc_context: MutationContext<'gc, '_>, name: &str) -> bool;
|
|
|
|
/// Retrieve the `__proto__` of a given object.
|
|
///
|
|
/// The proto is another object used to resolve methods across a class of
|
|
/// multiple objects. It should also be accessible as `__proto__` from
|
|
/// `get`.
|
|
fn proto(&self) -> Option<Object<'gc>>;
|
|
|
|
/// Define a value on an object.
|
|
///
|
|
/// Unlike setting a value, this function is intended to replace any
|
|
/// existing virtual or built-in properties already installed on a given
|
|
/// object. As such, this should not run any setters; the resulting name
|
|
/// slot should either be completely replaced with the value or completely
|
|
/// untouched.
|
|
///
|
|
/// It is not guaranteed that all objects accept value definitions,
|
|
/// especially if a property name conflicts with a built-in property, such
|
|
/// as `__proto__`.
|
|
fn define_value(
|
|
&self,
|
|
gc_context: MutationContext<'gc, '_>,
|
|
name: &str,
|
|
value: Value<'gc>,
|
|
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.
|
|
///
|
|
/// A virtual property is a set of get/set functions that are called when a
|
|
/// given named property is retrieved or stored on an object. These
|
|
/// functions are then responsible for providing or accepting the value
|
|
/// that is given to or taken from the AVM.
|
|
///
|
|
/// It is not guaranteed that all objects accept virtual properties,
|
|
/// especially if a property name conflicts with a built-in property, such
|
|
/// as `__proto__`.
|
|
fn add_property(
|
|
&self,
|
|
gc_context: MutationContext<'gc, '_>,
|
|
name: &str,
|
|
get: Executable<'gc>,
|
|
set: Option<Executable<'gc>>,
|
|
attributes: EnumSet<Attribute>,
|
|
);
|
|
|
|
/// Checks if the object has a given named property.
|
|
fn has_property(&self, name: &str) -> bool;
|
|
|
|
/// Checks if the object has a given named property on itself (and not,
|
|
/// say, the object's prototype or superclass)
|
|
fn has_own_property(&self, name: &str) -> bool;
|
|
|
|
/// Checks if a named property can be overwritten.
|
|
fn is_property_overwritable(&self, name: &str) -> bool;
|
|
|
|
/// Checks if a named property appears when enumerating the object.
|
|
fn is_property_enumerable(&self, name: &str) -> bool;
|
|
|
|
/// Enumerate the object.
|
|
fn get_keys(&self) -> HashSet<String>;
|
|
|
|
/// Coerce the object into a string.
|
|
fn as_string(&self) -> String;
|
|
|
|
/// Get the object's type string.
|
|
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.
|
|
fn as_script_object(&self) -> Option<ScriptObject<'gc>>;
|
|
|
|
/// Get the underlying sound object, if it exists.
|
|
fn as_sound_object(&self) -> Option<SoundObject<'gc>> {
|
|
None
|
|
}
|
|
|
|
/// 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.
|
|
fn as_display_object(&self) -> Option<DisplayObject<'gc>> {
|
|
None
|
|
}
|
|
|
|
/// Get the underlying executable for this object, if it exists.
|
|
fn as_executable(&self) -> Option<Executable<'gc>> {
|
|
None
|
|
}
|
|
|
|
/// Get the underlying XML node for this object, if it exists.
|
|
fn as_xml_node(&self) -> Option<XMLNode<'gc>> {
|
|
None
|
|
}
|
|
|
|
/// Get the underlying `ValueObject`, if it exists.
|
|
fn as_value_object(&self) -> Option<ValueObject<'gc>> {
|
|
None
|
|
}
|
|
|
|
fn as_ptr(&self) -> *const ObjectPtr;
|
|
|
|
/// Check if this object is in the prototype chain of the specified test object.
|
|
fn is_prototype_of(&self, other: Object<'gc>) -> bool {
|
|
let mut proto = other.proto();
|
|
|
|
while let Some(proto_ob) = proto {
|
|
if self.as_ptr() == proto_ob.as_ptr() {
|
|
return true;
|
|
}
|
|
|
|
proto = proto_ob.proto();
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
/// Get the length of this object, as if it were an array.
|
|
fn length(&self) -> usize;
|
|
|
|
/// Gets a copy of the array storage behind this object.
|
|
fn array(&self) -> Vec<Value<'gc>>;
|
|
|
|
/// Sets the length of this object, as if it were an array.
|
|
///
|
|
/// Increasing this value will fill the gap with Value::Undefined.
|
|
/// Decreasing this value will remove affected items from both the array and properties storage.
|
|
fn set_length(&self, gc_context: MutationContext<'gc, '_>, length: usize);
|
|
|
|
/// Gets a property of this object as if it were an array.
|
|
///
|
|
/// Array element lookups do not respect the prototype chain, and will ignore virtual properties.
|
|
fn array_element(&self, index: usize) -> Value<'gc>;
|
|
|
|
/// Sets a property of this object as if it were an array.
|
|
///
|
|
/// This will increase the "length" of this object to encompass the index, and return the new length.
|
|
/// Any gap created by increasing the length will be filled with Value::Undefined, both in array
|
|
/// and property storage.
|
|
fn set_array_element(
|
|
&self,
|
|
index: usize,
|
|
value: Value<'gc>,
|
|
gc_context: MutationContext<'gc, '_>,
|
|
) -> usize;
|
|
|
|
/// Deletes a property of this object as if it were an array.
|
|
///
|
|
/// This will not rearrange the array or adjust the length, nor will it affect the properties
|
|
/// storage.
|
|
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>);
|
|
}
|
|
|
|
pub enum ObjectPtr {}
|
|
|
|
impl<'gc> Object<'gc> {
|
|
pub fn ptr_eq(a: Object<'gc>, b: Object<'gc>) -> bool {
|
|
a.as_ptr() == b.as_ptr()
|
|
}
|
|
}
|
|
|
|
pub fn search_prototype<'gc>(
|
|
mut proto: Option<Object<'gc>>,
|
|
name: &str,
|
|
avm: &mut Avm1<'gc>,
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
this: Object<'gc>,
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
|
let mut depth = 0;
|
|
|
|
while proto.is_some() {
|
|
if depth == 255 {
|
|
return Err("Encountered an excessively deep prototype chain.".into());
|
|
}
|
|
|
|
if proto.unwrap().has_own_property(name) {
|
|
return proto.unwrap().get_local(name, avm, context, this);
|
|
}
|
|
|
|
proto = proto.unwrap().proto();
|
|
depth += 1;
|
|
}
|
|
|
|
Ok(Value::Undefined.into())
|
|
}
|