Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions core/src/avm2/activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
} => self.op_debug(method, is_local_register, register_name, register),
Op::DebugFile { file_name } => self.op_debug_file(method, file_name),
Op::DebugLine { line_num } => self.op_debug_line(line_num),
Op::TypeOf => self.op_type_of(),
_ => self.unknown_op(op),
};

Expand Down Expand Up @@ -2253,6 +2254,50 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
Ok(FrameControl::Continue)
}

fn op_type_of(&mut self) -> Result<FrameControl<'gc>, Error> {
let value = self.context.avm2.pop();

let type_name = match value {
Value::Undefined => "undefined",
Value::Null => "object",
Value::Bool(_) => "boolean",
Value::Number(_) | Value::Integer(_) | Value::Unsigned(_) => "number",
Value::Object(o) => {
// Subclasses always have a typeof = "object", must be a subclass if the prototype chain is > 2, or not a subclass if <=2
let is_not_subclass = matches!(
o.proto().and_then(|p| p.proto()).and_then(|p| p.proto()),
None
);

match o {
Object::FunctionObject(_) => {
if is_not_subclass {
"function"
} else {
"object"
}
}
Object::XmlObject(_) => {
if is_not_subclass {
"xml"
} else {
"object"
}
}
_ => "object",
}
}
Value::String(_) => "string",
};

self.context.avm2.push(Value::String(AvmString::new(
self.context.gc_context,
type_name,
)));

Ok(FrameControl::Continue)
}

#[allow(unused_variables)]
#[cfg(avm_debug)]
fn op_debug(
Expand Down
45 changes: 44 additions & 1 deletion core/src/avm2/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::avm2::method::NativeMethod;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{
implicit_deriver, ArrayObject, DomainObject, FunctionObject, NamespaceObject, Object,
PrimitiveObject, ScriptObject, StageObject, TObject,
PrimitiveObject, ScriptObject, StageObject, TObject, XmlObject,
};
use crate::avm2::scope::Scope;
use crate::avm2::script::Script;
Expand All @@ -29,6 +29,8 @@ mod number;
mod object;
mod string;
mod r#uint;
mod xml;
mod xml_list;

const NS_RUFFLE_INTERNAL: &str = "https://ruffle.rs/AS3/impl/";

Expand Down Expand Up @@ -96,6 +98,8 @@ pub struct SystemPrototypes<'gc> {
pub application_domain: Object<'gc>,
pub event: Object<'gc>,
pub video: Object<'gc>,
pub xml: Object<'gc>,
pub xml_list: Object<'gc>,
}

impl<'gc> SystemPrototypes<'gc> {
Expand Down Expand Up @@ -130,6 +134,8 @@ impl<'gc> SystemPrototypes<'gc> {
application_domain: empty,
event: empty,
video: empty,
xml: empty,
xml_list: empty,
}
}
}
Expand Down Expand Up @@ -274,6 +280,15 @@ fn array_deriver<'gc>(
ArrayObject::derive(base_proto, activation.context.gc_context, class, scope)
}

fn xml_deriver<'gc>(
base_proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
XmlObject::derive(base_proto, activation.context.gc_context, class, scope)
}

fn stage_deriver<'gc>(
base_proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
Expand Down Expand Up @@ -443,6 +458,34 @@ pub fn load_player_globals<'gc>(
script,
)?;

activation
.context
.avm2
.system_prototypes
.as_mut()
.unwrap()
.xml = class(
activation,
xml::create_class(mc),
xml_deriver,
domain,
script,
)?;

activation
.context
.avm2
.system_prototypes
.as_mut()
.unwrap()
.xml_list = class(
activation,
xml_list::create_class(mc),
xml_deriver,
domain,
script,
)?;

// package `flash.system`
activation
.context
Expand Down
38 changes: 38 additions & 0 deletions core/src/avm2/globals/xml.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! XML builtin and prototype

use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::method::Method;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::Object;
use crate::avm2::value::Value;
use crate::avm2::Error;
use gc_arena::{GcCell, MutationContext};

/// Implements `XML`'s instance initializer.
pub fn instance_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}

/// Implements `XML`'s class initializer
pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}

pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
Class::new(
QName::new(Namespace::public(), "XML"),
Some(QName::new(Namespace::public(), "Object").into()),
Method::from_builtin(instance_init),
Method::from_builtin(class_init),
mc,
)
}
38 changes: 38 additions & 0 deletions core/src/avm2/globals/xml_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! XMLList builtin and prototype

use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::method::Method;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::Object;
use crate::avm2::value::Value;
use crate::avm2::Error;
use gc_arena::{GcCell, MutationContext};

/// Implements `XMLList`'s instance initializer.
pub fn instance_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}

/// Implements `XMLList`'s class initializer
pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}

pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
Class::new(
QName::new(Namespace::public(), "XMLList"),
Some(QName::new(Namespace::public(), "Object").into()),
Method::from_builtin(instance_init),
Method::from_builtin(class_init),
mc,
)
}
5 changes: 4 additions & 1 deletion core/src/avm2/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ mod namespace_object;
mod primitive_object;
mod script_object;
mod stage_object;
mod xml_object;

pub use crate::avm2::object::array_object::ArrayObject;
pub use crate::avm2::object::dispatch_object::DispatchObject;
Expand All @@ -38,6 +39,7 @@ pub use crate::avm2::object::namespace_object::NamespaceObject;
pub use crate::avm2::object::primitive_object::PrimitiveObject;
pub use crate::avm2::object::script_object::ScriptObject;
pub use crate::avm2::object::stage_object::StageObject;
pub use crate::avm2::object::xml_object::XmlObject;

/// Represents an object that can be directly interacted with by the AVM2
/// runtime.
Expand All @@ -53,7 +55,8 @@ pub use crate::avm2::object::stage_object::StageObject;
StageObject(StageObject<'gc>),
DomainObject(DomainObject<'gc>),
EventObject(EventObject<'gc>),
DispatchObject(DispatchObject<'gc>)
DispatchObject(DispatchObject<'gc>),
XmlObject(XmlObject<'gc>),
}
)]
pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy {
Expand Down
82 changes: 82 additions & 0 deletions core/src/avm2/object/xml_object.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//! Object representation for XML objects

use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::script_object::{ScriptObjectClass, ScriptObjectData};
use crate::avm2::object::{Object, ObjectPtr, TObject};
use crate::avm2::scope::Scope;
use crate::avm2::string::AvmString;
use crate::avm2::traits::Trait;
use crate::avm2::value::Value;
use crate::avm2::Error;
use crate::{impl_avm2_custom_object, impl_avm2_custom_object_properties};
use gc_arena::{Collect, GcCell, MutationContext};

#[derive(Clone, Collect, Debug, Copy)]
#[collect(no_drop)]
pub struct XmlObject<'gc>(GcCell<'gc, XmlObjectData<'gc>>);

#[derive(Clone, Collect, Debug)]
#[collect(no_drop)]
pub struct XmlObjectData<'gc> {
/// Base script object
base: ScriptObjectData<'gc>,
}

impl<'gc> XmlObject<'gc> {
/// Instantiate an xml subclass.
pub fn derive(
base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let base = ScriptObjectData::base_new(
Some(base_proto),
ScriptObjectClass::InstancePrototype(class, scope),
);

Ok(XmlObject(GcCell::allocate(mc, XmlObjectData { base })).into())
}

pub fn empty_object(
mc: MutationContext<'gc, '_>,
base_proto: Option<Object<'gc>>,
) -> Object<'gc> {
let base = ScriptObjectData::base_new(base_proto, ScriptObjectClass::NoClass);

XmlObject(GcCell::allocate(mc, XmlObjectData { base })).into()
}
}

impl<'gc> TObject<'gc> for XmlObject<'gc> {
impl_avm2_custom_object!(base);
impl_avm2_custom_object_properties!(base);

fn construct(
&self,
activation: &mut Activation<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::XmlObject(*self);
Ok(Self::empty_object(
activation.context.gc_context,
Some(this),
))
}

fn derive(
&self,
activation: &mut Activation<'_, 'gc, '_>,
class: GcCell<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::XmlObject(*self);
Self::derive(this, activation.context.gc_context, class, scope)
}

fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {
Ok(Value::Object(Object::from(*self)))
}
}
1 change: 1 addition & 0 deletions core/tests/regression_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ swf_tests! {
(as3_string_length, "avm2/string_length", 1),
(as3_string_char_at, "avm2/string_char_at", 1),
(as3_string_char_code_at, "avm2/string_char_code_at", 1),
(as3_typeof, "avm2/typeof", 1),
}

// TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough.
Expand Down
Loading