avm1: Implement `Object.registerClass`

This commit is contained in:
Nathan Adams 2020-01-20 22:46:46 +01:00
parent d850443c84
commit 041bb6b44c
7 changed files with 106 additions and 8 deletions

View File

@ -179,12 +179,7 @@ pub fn create_globals<'gc>(
boolean::create_proto(gc_context, object_proto, function_proto);
//TODO: These need to be constructors and should also set `.prototype` on each one
let object = FunctionObject::function(
gc_context,
Executable::Native(object::constructor),
Some(function_proto),
Some(object_proto),
);
let object = object::create_object_object(gc_context, object_proto, function_proto);
let color = FunctionObject::function(
gc_context,

View File

@ -1,7 +1,9 @@
//! Object prototype
use crate::avm1::function::{Executable, FunctionObject};
use crate::avm1::property::Attribute::{self, *};
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, Object, TObject, UpdateContext, Value};
use crate::character::Character;
use enumset::EnumSet;
use gc_arena::MutationContext;
@ -129,6 +131,32 @@ fn value_of<'gc>(
Ok(ReturnValue::Immediate(this.into()))
}
/// Implements `Object.registerClass`
pub fn register_class<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Some(class_name) = args
.get(0)
.and_then(|v| v.clone().coerce_to_string(avm, context).ok())
{
if let Some(Character::MovieClip(movie_clip)) = context
.library
.library_for_movie_mut(context.swf.clone())
.get_character_by_export_name(&class_name)
{
if let Some(constructor) = args.get(1).and_then(|v| v.as_object().ok()) {
movie_clip.set_avm1_constructor(context.gc_context, Some(constructor));
} else {
movie_clip.set_avm1_constructor(context.gc_context, None);
}
}
}
Ok(Value::Undefined.into())
}
/// Partially construct `Object.prototype`.
///
/// `__proto__` and other cross-linked properties of this object will *not*
@ -259,3 +287,27 @@ pub fn as_set_prop_flags<'gc>(
Ok(Value::Undefined.into())
}
pub fn create_object_object<'gc>(
gc_context: MutationContext<'gc, '_>,
proto: Object<'gc>,
fn_proto: Object<'gc>,
) -> Object<'gc> {
let object_function = FunctionObject::function(
gc_context,
Executable::Native(constructor),
Some(fn_proto),
Some(proto),
);
let mut object = object_function.as_script_object().unwrap();
object.force_set_function(
"registerClass",
register_class,
gc_context,
Attribute::DontEnum | Attribute::DontDelete | Attribute::ReadOnly,
Some(fn_proto),
);
object_function
}

View File

@ -1,5 +1,5 @@
//! `MovieClip` display object and support code.
use crate::avm1::{Avm1, Object, StageObject, Value};
use crate::avm1::{Avm1, Object, StageObject, TObject, Value};
use crate::backend::audio::AudioStreamHandle;
use crate::character::Character;
use crate::context::{ActionType, RenderContext, UpdateContext};
@ -41,6 +41,7 @@ pub struct MovieClipData<'gc> {
object: Option<Object<'gc>>,
clip_actions: SmallVec<[ClipAction; 2]>,
flags: EnumSet<MovieClipFlags>,
avm1_constructor: Option<Object<'gc>>,
}
impl<'gc> MovieClip<'gc> {
@ -58,6 +59,7 @@ impl<'gc> MovieClip<'gc> {
object: None,
clip_actions: SmallVec::new(),
flags: EnumSet::empty(),
avm1_constructor: None,
},
))
}
@ -89,6 +91,7 @@ impl<'gc> MovieClip<'gc> {
object: None,
clip_actions: SmallVec::new(),
flags: MovieClipFlags::Playing.into(),
avm1_constructor: None,
},
))
}
@ -180,6 +183,14 @@ impl<'gc> MovieClip<'gc> {
self.0.read().static_data.total_frames
}
pub fn set_avm1_constructor(
self,
gc_context: MutationContext<'gc, '_>,
prototype: Option<Object<'gc>>,
) {
self.0.write(gc_context).avm1_constructor = prototype;
}
pub fn frame_label_to_number(self, frame_label: &str) -> Option<FrameNumber> {
// Frame labels are case insensitive.
let label = frame_label.to_ascii_lowercase();
@ -386,12 +397,32 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
fn post_instantiation(
&mut self,
_avm: &mut Avm1<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
display_object: DisplayObject<'gc>,
) {
let mut mc = self.0.write(context.gc_context);
if mc.object.is_none() {
if let Some(constructor) = mc.avm1_constructor {
if let Ok(prototype) = constructor
.get("prototype", avm, context)
.and_then(|v| v.resolve(avm, context))
.and_then(|v| v.as_object())
{
let object: Object<'gc> = StageObject::for_display_object(
context.gc_context,
display_object,
Some(prototype),
)
.into();
mc.object = Some(object);
if let Ok(result) = constructor.call(avm, context, object, &[]) {
let _ = result.resolve(avm, context);
}
return;
}
};
let object = StageObject::for_display_object(
context.gc_context,
display_object,
@ -432,6 +463,7 @@ unsafe impl<'gc> Collect for MovieClipData<'gc> {
self.base.trace(cc);
self.static_data.trace(cc);
self.object.trace(cc);
self.avm1_constructor.trace(cc);
}
}

View File

@ -126,6 +126,7 @@ swf_tests! {
(equals2_swf5, "avm1/equals2_swf5", 1),
(equals2_swf6, "avm1/equals2_swf6", 1),
(equals2_swf7, "avm1/equals2_swf7", 1),
(register_class, "avm1/register_class", 1),
(set_variable_scope, "avm1/set_variable_scope", 1),
(slash_syntax, "avm1/slash_syntax", 2),
(strictequals_swf6, "avm1/strictequals_swf6", 1),

View File

@ -0,0 +1,18 @@
Custom movieclip!
// typeof custom_movieclip
movieclip
// custom_movieclip.__proto__ == custom_mc.prototype
true
// custom_movieclip.__proto__ == MovieClip.prototype
false
// custom_movieclip.isCustom()
true
// typeof normal_movieclip
movieclip
// normal_movieclip.__proto__ == custom_mc.prototype
false
// normal_movieclip.__proto__ == MovieClip.prototype
true
// normal_movieclip.isCustom()
undefined

Binary file not shown.

Binary file not shown.