Skip to content

Commit 51b5090

Browse files
CUB3DHerschel
authored andcommitted
avm2: Implement Op::Typeof and dummy XML/XMLList
1 parent df5e974 commit 51b5090

File tree

11 files changed

+359
-2
lines changed

11 files changed

+359
-2
lines changed

core/src/avm2/activation.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
673673
} => self.op_debug(method, is_local_register, register_name, register),
674674
Op::DebugFile { file_name } => self.op_debug_file(method, file_name),
675675
Op::DebugLine { line_num } => self.op_debug_line(line_num),
676+
Op::TypeOf => self.op_type_of(),
676677
_ => self.unknown_op(op),
677678
};
678679

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

2257+
fn op_type_of(&mut self) -> Result<FrameControl<'gc>, Error> {
2258+
let value = self.context.avm2.pop();
2259+
2260+
let type_name = match value {
2261+
Value::Undefined => "undefined",
2262+
Value::Null => "object",
2263+
Value::Bool(_) => "boolean",
2264+
Value::Number(_) | Value::Integer(_) | Value::Unsigned(_) => "number",
2265+
Value::Object(o) => {
2266+
// Subclasses always have a typeof = "object", must be a subclass if the prototype chain is > 2, or not a subclass if <=2
2267+
let is_not_subclass = matches!(
2268+
o.proto().and_then(|p| p.proto()).and_then(|p| p.proto()),
2269+
None
2270+
);
2271+
2272+
match o {
2273+
Object::FunctionObject(_) => {
2274+
if is_not_subclass {
2275+
"function"
2276+
} else {
2277+
"object"
2278+
}
2279+
}
2280+
Object::XmlObject(_) => {
2281+
if is_not_subclass {
2282+
"xml"
2283+
} else {
2284+
"object"
2285+
}
2286+
}
2287+
_ => "object",
2288+
}
2289+
}
2290+
Value::String(_) => "string",
2291+
};
2292+
2293+
self.context.avm2.push(Value::String(AvmString::new(
2294+
self.context.gc_context,
2295+
type_name,
2296+
)));
2297+
2298+
Ok(FrameControl::Continue)
2299+
}
2300+
22562301
#[allow(unused_variables)]
22572302
#[cfg(avm_debug)]
22582303
fn op_debug(

core/src/avm2/globals.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::avm2::method::NativeMethod;
77
use crate::avm2::names::{Namespace, QName};
88
use crate::avm2::object::{
99
implicit_deriver, ArrayObject, DomainObject, FunctionObject, NamespaceObject, Object,
10-
PrimitiveObject, ScriptObject, StageObject, TObject,
10+
PrimitiveObject, ScriptObject, StageObject, TObject, XmlObject,
1111
};
1212
use crate::avm2::scope::Scope;
1313
use crate::avm2::script::Script;
@@ -29,6 +29,8 @@ mod number;
2929
mod object;
3030
mod string;
3131
mod r#uint;
32+
mod xml;
33+
mod xml_list;
3234

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

@@ -96,6 +98,8 @@ pub struct SystemPrototypes<'gc> {
9698
pub application_domain: Object<'gc>,
9799
pub event: Object<'gc>,
98100
pub video: Object<'gc>,
101+
pub xml: Object<'gc>,
102+
pub xml_list: Object<'gc>,
99103
}
100104

101105
impl<'gc> SystemPrototypes<'gc> {
@@ -130,6 +134,8 @@ impl<'gc> SystemPrototypes<'gc> {
130134
application_domain: empty,
131135
event: empty,
132136
video: empty,
137+
xml: empty,
138+
xml_list: empty,
133139
}
134140
}
135141
}
@@ -274,6 +280,15 @@ fn array_deriver<'gc>(
274280
ArrayObject::derive(base_proto, activation.context.gc_context, class, scope)
275281
}
276282

283+
fn xml_deriver<'gc>(
284+
base_proto: Object<'gc>,
285+
activation: &mut Activation<'_, 'gc, '_>,
286+
class: GcCell<'gc, Class<'gc>>,
287+
scope: Option<GcCell<'gc, Scope<'gc>>>,
288+
) -> Result<Object<'gc>, Error> {
289+
XmlObject::derive(base_proto, activation.context.gc_context, class, scope)
290+
}
291+
277292
fn stage_deriver<'gc>(
278293
base_proto: Object<'gc>,
279294
activation: &mut Activation<'_, 'gc, '_>,
@@ -443,6 +458,34 @@ pub fn load_player_globals<'gc>(
443458
script,
444459
)?;
445460

461+
activation
462+
.context
463+
.avm2
464+
.system_prototypes
465+
.as_mut()
466+
.unwrap()
467+
.xml = class(
468+
activation,
469+
xml::create_class(mc),
470+
xml_deriver,
471+
domain,
472+
script,
473+
)?;
474+
475+
activation
476+
.context
477+
.avm2
478+
.system_prototypes
479+
.as_mut()
480+
.unwrap()
481+
.xml_list = class(
482+
activation,
483+
xml_list::create_class(mc),
484+
xml_deriver,
485+
domain,
486+
script,
487+
)?;
488+
446489
// package `flash.system`
447490
activation
448491
.context

core/src/avm2/globals/xml.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//! XML builtin and prototype
2+
3+
use crate::avm2::activation::Activation;
4+
use crate::avm2::class::Class;
5+
use crate::avm2::method::Method;
6+
use crate::avm2::names::{Namespace, QName};
7+
use crate::avm2::object::Object;
8+
use crate::avm2::value::Value;
9+
use crate::avm2::Error;
10+
use gc_arena::{GcCell, MutationContext};
11+
12+
/// Implements `XML`'s instance initializer.
13+
pub fn instance_init<'gc>(
14+
_activation: &mut Activation<'_, 'gc, '_>,
15+
_this: Option<Object<'gc>>,
16+
_args: &[Value<'gc>],
17+
) -> Result<Value<'gc>, Error> {
18+
Ok(Value::Undefined)
19+
}
20+
21+
/// Implements `XML`'s class initializer
22+
pub fn class_init<'gc>(
23+
_activation: &mut Activation<'_, 'gc, '_>,
24+
_this: Option<Object<'gc>>,
25+
_args: &[Value<'gc>],
26+
) -> Result<Value<'gc>, Error> {
27+
Ok(Value::Undefined)
28+
}
29+
30+
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
31+
Class::new(
32+
QName::new(Namespace::public(), "XML"),
33+
Some(QName::new(Namespace::public(), "Object").into()),
34+
Method::from_builtin(instance_init),
35+
Method::from_builtin(class_init),
36+
mc,
37+
)
38+
}

core/src/avm2/globals/xml_list.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//! XMLList builtin and prototype
2+
3+
use crate::avm2::activation::Activation;
4+
use crate::avm2::class::Class;
5+
use crate::avm2::method::Method;
6+
use crate::avm2::names::{Namespace, QName};
7+
use crate::avm2::object::Object;
8+
use crate::avm2::value::Value;
9+
use crate::avm2::Error;
10+
use gc_arena::{GcCell, MutationContext};
11+
12+
/// Implements `XMLList`'s instance initializer.
13+
pub fn instance_init<'gc>(
14+
_activation: &mut Activation<'_, 'gc, '_>,
15+
_this: Option<Object<'gc>>,
16+
_args: &[Value<'gc>],
17+
) -> Result<Value<'gc>, Error> {
18+
Ok(Value::Undefined)
19+
}
20+
21+
/// Implements `XMLList`'s class initializer
22+
pub fn class_init<'gc>(
23+
_activation: &mut Activation<'_, 'gc, '_>,
24+
_this: Option<Object<'gc>>,
25+
_args: &[Value<'gc>],
26+
) -> Result<Value<'gc>, Error> {
27+
Ok(Value::Undefined)
28+
}
29+
30+
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
31+
Class::new(
32+
QName::new(Namespace::public(), "XMLList"),
33+
Some(QName::new(Namespace::public(), "Object").into()),
34+
Method::from_builtin(instance_init),
35+
Method::from_builtin(class_init),
36+
mc,
37+
)
38+
}

core/src/avm2/object.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ mod namespace_object;
2828
mod primitive_object;
2929
mod script_object;
3030
mod stage_object;
31+
mod xml_object;
3132

3233
pub use crate::avm2::object::array_object::ArrayObject;
3334
pub use crate::avm2::object::dispatch_object::DispatchObject;
@@ -38,6 +39,7 @@ pub use crate::avm2::object::namespace_object::NamespaceObject;
3839
pub use crate::avm2::object::primitive_object::PrimitiveObject;
3940
pub use crate::avm2::object::script_object::ScriptObject;
4041
pub use crate::avm2::object::stage_object::StageObject;
42+
pub use crate::avm2::object::xml_object::XmlObject;
4143

4244
/// Represents an object that can be directly interacted with by the AVM2
4345
/// runtime.
@@ -53,7 +55,8 @@ pub use crate::avm2::object::stage_object::StageObject;
5355
StageObject(StageObject<'gc>),
5456
DomainObject(DomainObject<'gc>),
5557
EventObject(EventObject<'gc>),
56-
DispatchObject(DispatchObject<'gc>)
58+
DispatchObject(DispatchObject<'gc>),
59+
XmlObject(XmlObject<'gc>),
5760
}
5861
)]
5962
pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy {

core/src/avm2/object/xml_object.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//! Object representation for XML objects
2+
3+
use crate::avm2::activation::Activation;
4+
use crate::avm2::class::Class;
5+
use crate::avm2::names::{Namespace, QName};
6+
use crate::avm2::object::script_object::{ScriptObjectClass, ScriptObjectData};
7+
use crate::avm2::object::{Object, ObjectPtr, TObject};
8+
use crate::avm2::scope::Scope;
9+
use crate::avm2::string::AvmString;
10+
use crate::avm2::traits::Trait;
11+
use crate::avm2::value::Value;
12+
use crate::avm2::Error;
13+
use crate::{impl_avm2_custom_object, impl_avm2_custom_object_properties};
14+
use gc_arena::{Collect, GcCell, MutationContext};
15+
16+
#[derive(Clone, Collect, Debug, Copy)]
17+
#[collect(no_drop)]
18+
pub struct XmlObject<'gc>(GcCell<'gc, XmlObjectData<'gc>>);
19+
20+
#[derive(Clone, Collect, Debug)]
21+
#[collect(no_drop)]
22+
pub struct XmlObjectData<'gc> {
23+
/// Base script object
24+
base: ScriptObjectData<'gc>,
25+
}
26+
27+
impl<'gc> XmlObject<'gc> {
28+
/// Instantiate an xml subclass.
29+
pub fn derive(
30+
base_proto: Object<'gc>,
31+
mc: MutationContext<'gc, '_>,
32+
class: GcCell<'gc, Class<'gc>>,
33+
scope: Option<GcCell<'gc, Scope<'gc>>>,
34+
) -> Result<Object<'gc>, Error> {
35+
let base = ScriptObjectData::base_new(
36+
Some(base_proto),
37+
ScriptObjectClass::InstancePrototype(class, scope),
38+
);
39+
40+
Ok(XmlObject(GcCell::allocate(mc, XmlObjectData { base })).into())
41+
}
42+
43+
pub fn empty_object(
44+
mc: MutationContext<'gc, '_>,
45+
base_proto: Option<Object<'gc>>,
46+
) -> Object<'gc> {
47+
let base = ScriptObjectData::base_new(base_proto, ScriptObjectClass::NoClass);
48+
49+
XmlObject(GcCell::allocate(mc, XmlObjectData { base })).into()
50+
}
51+
}
52+
53+
impl<'gc> TObject<'gc> for XmlObject<'gc> {
54+
impl_avm2_custom_object!(base);
55+
impl_avm2_custom_object_properties!(base);
56+
57+
fn construct(
58+
&self,
59+
activation: &mut Activation<'_, 'gc, '_>,
60+
_args: &[Value<'gc>],
61+
) -> Result<Object<'gc>, Error> {
62+
let this: Object<'gc> = Object::XmlObject(*self);
63+
Ok(Self::empty_object(
64+
activation.context.gc_context,
65+
Some(this),
66+
))
67+
}
68+
69+
fn derive(
70+
&self,
71+
activation: &mut Activation<'_, 'gc, '_>,
72+
class: GcCell<'gc, Class<'gc>>,
73+
scope: Option<GcCell<'gc, Scope<'gc>>>,
74+
) -> Result<Object<'gc>, Error> {
75+
let this: Object<'gc> = Object::XmlObject(*self);
76+
Self::derive(this, activation.context.gc_context, class, scope)
77+
}
78+
79+
fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error> {
80+
Ok(Value::Object(Object::from(*self)))
81+
}
82+
}

core/tests/regression_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ swf_tests! {
519519
(as3_string_length, "avm2/string_length", 1),
520520
(as3_string_char_at, "avm2/string_char_at", 1),
521521
(as3_string_char_code_at, "avm2/string_char_code_at", 1),
522+
(as3_typeof, "avm2/typeof", 1),
522523
}
523524

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

0 commit comments

Comments
 (0)