Skip to content

Commit cc20bc3

Browse files
committed
avm1: Migrate StageObject to a native object
1 parent 20e9282 commit cc20bc3

13 files changed

+206
-259
lines changed

core/src/avm1.rs

-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ pub use globals::array::ArrayBuilder;
3232
pub use globals::context_menu::make_context_menu_state;
3333
pub use globals::sound::start as start_sound;
3434
pub use object::script_object::ScriptObject;
35-
pub use object::stage_object::StageObject;
3635
pub use object::{NativeObject, Object, ObjectPtr, TObject};
3736
pub use property::Attribute;
3837
pub use property_map::PropertyMap;

core/src/avm1/activation.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
582582
}
583583

584584
fn stack_push(&mut self, mut value: Value<'gc>) {
585-
if let Value::Object(Object::StageObject(s)) = value {
585+
if let Value::Object(obj) = value {
586586
// Note that there currently exists a subtle issue with this logic:
587587
// If the cached `Object` in a `MovieClipReference` becomes invalidated, causing it to switch back to path-based object resolution,
588588
// it should *never* switch back to cache-based resolution
@@ -591,7 +591,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
591591
// Fixing this will require a thorough refactor of AVM1 to store `Either<MovieClipReference, Object>
592592
// can refer to a MovieClip
593593
// There is a ignored test for this issue of "reference laundering" at "avm1/string_paths_reference_launder"
594-
if let Some(mcr) = MovieClipReference::try_from_stage_object(self, s) {
594+
if let Some(mcr) = MovieClipReference::try_from_stage_object(self, obj) {
595595
value = Value::MovieClip(mcr);
596596
}
597597
}

core/src/avm1/globals/transform.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@ impl<'gc> TransformObject<'gc> {
2424
let clip = match args {
2525
// `Transform` constructor accepts exactly 1 argument.
2626
[Value::MovieClip(clip)] => Some(*clip),
27-
[Value::Object(clip)] => {
28-
let stage_object = clip.as_stage_object()?;
29-
MovieClipReference::try_from_stage_object(activation, stage_object)
30-
}
27+
[Value::Object(clip)] => MovieClipReference::try_from_stage_object(activation, *clip),
3128
_ => return None,
3229
};
3330
Some(Self { clip })

core/src/avm1/object.rs

+28-13
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ use crate::avm1::globals::transform::TransformObject;
2121
use crate::avm1::globals::xml::Xml;
2222
use crate::avm1::globals::xml_socket::XmlSocket;
2323
use crate::avm1::object::super_object::SuperObject;
24-
use crate::avm1::{Activation, Attribute, Error, ScriptObject, StageObject, Value};
24+
use crate::avm1::{Activation, Attribute, Error, ScriptObject, Value};
2525
use crate::bitmap::bitmap_data::BitmapDataWrapper;
26-
use crate::display_object::DisplayObject;
27-
use crate::display_object::TDisplayObject;
26+
use crate::display_object::{
27+
Avm1Button, DisplayObject, EditText, MovieClip, TDisplayObject as _, Video,
28+
};
2829
use crate::html::TextFormat;
2930
use crate::streams::NetStream;
3031
use crate::string::AvmString;
@@ -59,6 +60,11 @@ pub enum NativeObject<'gc> {
5960
Array(()),
6061
Function(Gc<'gc, FunctionObject<'gc>>),
6162

63+
MovieClip(MovieClip<'gc>),
64+
Button(Avm1Button<'gc>),
65+
EditText(EditText<'gc>),
66+
Video(Video<'gc>),
67+
6268
Date(Gc<'gc, Cell<Date>>),
6369
BlurFilter(BlurFilter<'gc>),
6470
BevelFilter(BevelFilter<'gc>),
@@ -119,6 +125,18 @@ impl<'gc> BoxedF64<'gc> {
119125
}
120126
}
121127

128+
impl<'gc> NativeObject<'gc> {
129+
pub fn as_display_object(self) -> Option<DisplayObject<'gc>> {
130+
match self {
131+
Self::MovieClip(dobj) => Some(DisplayObject::MovieClip(dobj)),
132+
Self::Button(dobj) => Some(DisplayObject::Avm1Button(dobj)),
133+
Self::EditText(dobj) => Some(DisplayObject::EditText(dobj)),
134+
Self::Video(dobj) => Some(DisplayObject::Video(dobj)),
135+
_ => None,
136+
}
137+
}
138+
}
139+
122140
/// Represents an object that can be directly interacted with by the AVM
123141
/// runtime.
124142
#[enum_trait_object(
@@ -127,7 +145,6 @@ impl<'gc> BoxedF64<'gc> {
127145
#[collect(no_drop)]
128146
pub enum Object<'gc> {
129147
ScriptObject(ScriptObject<'gc>),
130-
StageObject(StageObject<'gc>),
131148
SuperObject(SuperObject<'gc>),
132149
}
133150
)]
@@ -323,10 +340,8 @@ pub trait TObject<'gc>: 'gc + Collect<'gc> + Into<Object<'gc>> + Clone + Copy {
323340
) -> Result<Value<'gc>, Error<'gc>> {
324341
let this = (*self).into();
325342

326-
if let Some(s) = this.as_stage_object() {
327-
let d_o = s.as_display_object().unwrap();
328-
329-
if d_o.avm1_removed() {
343+
if let Some(dobj) = this.as_display_object() {
344+
if dobj.avm1_removed() {
330345
return Ok(Value::Undefined);
331346
}
332347
}
@@ -608,11 +623,6 @@ pub trait TObject<'gc>: 'gc + Collect<'gc> + Into<Object<'gc>> + Clone + Copy {
608623

609624
fn set_native(&self, _gc_context: &Mutation<'gc>, _native: NativeObject<'gc>) {}
610625

611-
/// Get the underlying stage object, if it exists.
612-
fn as_stage_object(&self) -> Option<StageObject<'gc>> {
613-
None
614-
}
615-
616626
/// Get the underlying super object, if it exists.
617627
fn as_super_object(&self) -> Option<SuperObject<'gc>> {
618628
None
@@ -623,6 +633,11 @@ pub trait TObject<'gc>: 'gc + Collect<'gc> + Into<Object<'gc>> + Clone + Copy {
623633
None
624634
}
625635

636+
/// Get the underlying stage object, if it exists, but doesn't follow `super` objects.
637+
fn as_display_object_no_super(&self) -> Option<DisplayObject<'gc>> {
638+
None
639+
}
640+
626641
/// Get the underlying executable for this object, if it exists.
627642
fn as_executable(&self) -> Option<Executable<'gc>> {
628643
None

core/src/avm1/object/script_object.rs

+93-15
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
use crate::avm1::activation::Activation;
22
use crate::avm1::error::Error;
33
use crate::avm1::function::{Executable, ExecutionName, ExecutionReason};
4-
use crate::avm1::object::NativeObject;
4+
use crate::avm1::object::{stage_object, NativeObject};
55
use crate::avm1::property::{Attribute, Property};
66
use crate::avm1::property_map::{Entry, PropertyMap};
77
use crate::avm1::{Object, ObjectPtr, TObject, Value};
8+
use crate::display_object::{DisplayObject, TDisplayObject as _};
89
use crate::ecma_conversions::f64_to_wrapping_i32;
910
use crate::string::{AvmString, StringContext};
1011
use core::fmt;
11-
use gc_arena::{Collect, GcCell, Mutation};
12+
use gc_arena::{Collect, GcCell, GcWeakCell, Mutation};
1213
use ruffle_macros::istr;
1314

1415
#[derive(Clone, Collect)]
@@ -52,9 +53,27 @@ impl<'gc> Watcher<'gc> {
5253
#[collect(no_drop)]
5354
pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
5455

56+
#[derive(Copy, Clone, Collect)]
57+
#[collect(no_drop)]
58+
pub struct ScriptObjectWeak<'gc>(GcWeakCell<'gc, ScriptObjectData<'gc>>);
59+
60+
impl<'gc> ScriptObjectWeak<'gc> {
61+
pub fn upgrade(self, mc: &Mutation<'gc>) -> Option<ScriptObject<'gc>> {
62+
self.0.upgrade(mc).map(ScriptObject)
63+
}
64+
}
65+
66+
impl fmt::Debug for ScriptObjectWeak<'_> {
67+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68+
f.debug_struct("ScriptObjectWeak")
69+
.field("ptr", &self.0.as_ptr())
70+
.finish()
71+
}
72+
}
73+
5574
#[derive(Collect)]
5675
#[collect(no_drop)]
57-
pub struct ScriptObjectData<'gc> {
76+
struct ScriptObjectData<'gc> {
5877
native: NativeObject<'gc>,
5978
properties: PropertyMap<'gc, Property<'gc>>,
6079
interfaces: Vec<Object<'gc>>,
@@ -70,6 +89,10 @@ impl fmt::Debug for ScriptObject<'_> {
7089
}
7190

7291
impl<'gc> ScriptObject<'gc> {
92+
pub fn as_weak(self) -> ScriptObjectWeak<'gc> {
93+
ScriptObjectWeak(GcCell::downgrade(self.0))
94+
}
95+
7396
pub fn new(context: &StringContext<'gc>, proto: Option<Object<'gc>>) -> Self {
7497
let object = Self(GcCell::new(
7598
context.gc(),
@@ -91,6 +114,16 @@ impl<'gc> ScriptObject<'gc> {
91114
object
92115
}
93116

117+
pub fn new_with_native(
118+
context: &StringContext<'gc>,
119+
proto: Option<Object<'gc>>,
120+
native: NativeObject<'gc>,
121+
) -> Self {
122+
let obj = Self::new(context, proto);
123+
obj.set_native(context.gc(), native);
124+
obj
125+
}
126+
94127
// Creates a ScriptObject, without assigning any __proto__ property.
95128
pub fn new_without_proto(gc_context: &Mutation<'gc>) -> Self {
96129
Self(GcCell::new(
@@ -170,14 +203,20 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
170203
&self,
171204
name: impl Into<AvmString<'gc>>,
172205
activation: &mut Activation<'_, 'gc>,
173-
_is_slash_path: bool,
206+
is_slash_path: bool,
174207
) -> Option<Value<'gc>> {
175-
self.0
176-
.read()
177-
.properties
178-
.get(name.into(), activation.is_case_sensitive())
208+
let name = name.into();
209+
let read = self.0.read();
210+
211+
read.properties
212+
.get(name, activation.is_case_sensitive())
179213
.filter(|property| property.allow_swf_version(activation.swf_version()))
180214
.map(|property| property.data())
215+
.or_else(|| {
216+
read.native.as_display_object().and_then(|dobj| {
217+
stage_object::get_property(dobj, name, activation, is_slash_path)
218+
})
219+
})
181220
}
182221

183222
/// Set a named property on the object.
@@ -188,7 +227,8 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
188227
activation: &mut Activation<'_, 'gc>,
189228
this: Object<'gc>,
190229
) -> Result<(), Error<'gc>> {
191-
if let NativeObject::Array(_) = self.native() {
230+
let native = self.native();
231+
if let NativeObject::Array(_) = native {
192232
if name == istr!("length") {
193233
let new_length = value.coerce_to_i32(activation)?;
194234
let old_length = self.get_data(istr!("length"), activation);
@@ -203,6 +243,20 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
203243
self.set_length(activation, index.wrapping_add(1))?;
204244
}
205245
}
246+
} else if let Some(dobj) = native.as_display_object() {
247+
stage_object::notify_property_change(dobj, name, value, activation)?;
248+
// 'magic' display object properties (such as _x, _y, etc) take
249+
// priority over properties in prototypes.
250+
if !self.has_own_property(activation, name) {
251+
if let Some(property) = activation
252+
.context
253+
.avm1
254+
.display_properties()
255+
.get_by_name(name)
256+
{
257+
return property.set(activation, dobj, value);
258+
}
259+
}
206260
}
207261

208262
let setter = match self
@@ -249,6 +303,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
249303
}
250304
}
251305

306+
fn as_display_object_no_super(&self) -> Option<DisplayObject<'gc>> {
307+
self.0.read().native.as_display_object()
308+
}
309+
310+
fn as_display_object(&self) -> Option<DisplayObject<'gc>> {
311+
self.0.read().native.as_display_object()
312+
}
313+
252314
/// Call the underlying object.
253315
///
254316
/// This function takes a redundant `this` parameter which should be
@@ -465,17 +527,27 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
465527

466528
/// Checks if the object has a given named property.
467529
fn has_property(&self, activation: &mut Activation<'_, 'gc>, name: AvmString<'gc>) -> bool {
468-
self.has_own_property(activation, name)
469-
|| if let Value::Object(proto) = self.proto(activation) {
470-
proto.has_property(activation, name)
471-
} else {
472-
false
530+
let dobj = self.as_display_object_no_super();
531+
532+
// Normal property checks
533+
if dobj.is_none_or(|o| !o.avm1_removed()) {
534+
if self.has_own_property(activation, name) {
535+
return true;
536+
} else if let Value::Object(proto) = self.proto(activation) {
537+
if proto.has_property(activation, name) {
538+
return true;
539+
}
473540
}
541+
}
542+
543+
// Fallback: display object properties
544+
dobj.is_some_and(|o| stage_object::has_display_object_property(o, activation, name))
474545
}
475546

476547
/// Checks if the object has a given named property on itself (and not,
477548
/// say, the object's prototype or superclass)
478549
fn has_own_property(&self, activation: &mut Activation<'_, 'gc>, name: AvmString<'gc>) -> bool {
550+
// Note that `hasOwnProperty` does NOT return true for display object properties.
479551
self.0
480552
.read()
481553
.properties
@@ -526,14 +598,20 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
526598
);
527599

528600
// Then our own keys.
529-
out_keys.extend(self.0.read().properties.iter().filter_map(move |(k, p)| {
601+
let read = self.0.read();
602+
out_keys.extend(read.properties.iter().filter_map(move |(k, p)| {
530603
if include_hidden || p.is_enumerable() {
531604
Some(k)
532605
} else {
533606
None
534607
}
535608
}));
536609

610+
// Then display object keys.
611+
if let Some(dobj) = read.native.as_display_object() {
612+
stage_object::enumerate_keys(dobj, &mut out_keys);
613+
}
614+
537615
out_keys
538616
}
539617

0 commit comments

Comments
 (0)