1
1
use crate :: avm1:: activation:: Activation ;
2
2
use crate :: avm1:: error:: Error ;
3
3
use crate :: avm1:: function:: { Executable , ExecutionName , ExecutionReason } ;
4
- use crate :: avm1:: object:: NativeObject ;
4
+ use crate :: avm1:: object:: { stage_object , NativeObject } ;
5
5
use crate :: avm1:: property:: { Attribute , Property } ;
6
6
use crate :: avm1:: property_map:: { Entry , PropertyMap } ;
7
7
use crate :: avm1:: { Object , ObjectPtr , TObject , Value } ;
8
+ use crate :: display_object:: { DisplayObject , TDisplayObject as _} ;
8
9
use crate :: ecma_conversions:: f64_to_wrapping_i32;
9
10
use crate :: string:: { AvmString , StringContext } ;
10
11
use core:: fmt;
11
- use gc_arena:: { Collect , GcCell , Mutation } ;
12
+ use gc_arena:: { Collect , GcCell , GcWeakCell , Mutation } ;
12
13
use ruffle_macros:: istr;
13
14
14
15
#[ derive( Clone , Collect ) ]
@@ -52,9 +53,27 @@ impl<'gc> Watcher<'gc> {
52
53
#[ collect( no_drop) ]
53
54
pub struct ScriptObject < ' gc > ( GcCell < ' gc , ScriptObjectData < ' gc > > ) ;
54
55
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
+
55
74
#[ derive( Collect ) ]
56
75
#[ collect( no_drop) ]
57
- pub struct ScriptObjectData < ' gc > {
76
+ struct ScriptObjectData < ' gc > {
58
77
native : NativeObject < ' gc > ,
59
78
properties : PropertyMap < ' gc , Property < ' gc > > ,
60
79
interfaces : Vec < Object < ' gc > > ,
@@ -70,6 +89,10 @@ impl fmt::Debug for ScriptObject<'_> {
70
89
}
71
90
72
91
impl < ' gc > ScriptObject < ' gc > {
92
+ pub fn as_weak ( self ) -> ScriptObjectWeak < ' gc > {
93
+ ScriptObjectWeak ( GcCell :: downgrade ( self . 0 ) )
94
+ }
95
+
73
96
pub fn new ( context : & StringContext < ' gc > , proto : Option < Object < ' gc > > ) -> Self {
74
97
let object = Self ( GcCell :: new (
75
98
context. gc ( ) ,
@@ -91,6 +114,16 @@ impl<'gc> ScriptObject<'gc> {
91
114
object
92
115
}
93
116
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
+
94
127
// Creates a ScriptObject, without assigning any __proto__ property.
95
128
pub fn new_without_proto ( gc_context : & Mutation < ' gc > ) -> Self {
96
129
Self ( GcCell :: new (
@@ -170,14 +203,20 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
170
203
& self ,
171
204
name : impl Into < AvmString < ' gc > > ,
172
205
activation : & mut Activation < ' _ , ' gc > ,
173
- _is_slash_path : bool ,
206
+ is_slash_path : bool ,
174
207
) -> 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 ( ) )
179
213
. filter ( |property| property. allow_swf_version ( activation. swf_version ( ) ) )
180
214
. 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
+ } )
181
220
}
182
221
183
222
/// Set a named property on the object.
@@ -188,7 +227,8 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
188
227
activation : & mut Activation < ' _ , ' gc > ,
189
228
this : Object < ' gc > ,
190
229
) -> Result < ( ) , Error < ' gc > > {
191
- if let NativeObject :: Array ( _) = self . native ( ) {
230
+ let native = self . native ( ) ;
231
+ if let NativeObject :: Array ( _) = native {
192
232
if name == istr ! ( "length" ) {
193
233
let new_length = value. coerce_to_i32 ( activation) ?;
194
234
let old_length = self . get_data ( istr ! ( "length" ) , activation) ;
@@ -203,6 +243,20 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
203
243
self . set_length ( activation, index. wrapping_add ( 1 ) ) ?;
204
244
}
205
245
}
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
+ }
206
260
}
207
261
208
262
let setter = match self
@@ -249,6 +303,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
249
303
}
250
304
}
251
305
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
+
252
314
/// Call the underlying object.
253
315
///
254
316
/// This function takes a redundant `this` parameter which should be
@@ -465,17 +527,27 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
465
527
466
528
/// Checks if the object has a given named property.
467
529
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
+ }
473
540
}
541
+ }
542
+
543
+ // Fallback: display object properties
544
+ dobj. is_some_and ( |o| stage_object:: has_display_object_property ( o, activation, name) )
474
545
}
475
546
476
547
/// Checks if the object has a given named property on itself (and not,
477
548
/// say, the object's prototype or superclass)
478
549
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.
479
551
self . 0
480
552
. read ( )
481
553
. properties
@@ -526,14 +598,20 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
526
598
) ;
527
599
528
600
// 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) | {
530
603
if include_hidden || p. is_enumerable ( ) {
531
604
Some ( k)
532
605
} else {
533
606
None
534
607
}
535
608
} ) ) ;
536
609
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
+
537
615
out_keys
538
616
}
539
617
0 commit comments