From cf0ab2d82fc48f7c0f3cbe674201dafc0c32cbef Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 10 Nov 2020 22:58:59 -0500 Subject: [PATCH] avm2: Implement `removeChildren`. This also changes the underlying `DisplayObjectContainer` method to accept any type of range. Turns out enum trait objects aren't actually trait objects and don't need to worry about object safety! --- .../flash/display/displayobjectcontainer.rs | 56 ++++++++++++++++++ core/src/display_object/container.rs | 28 ++++----- core/src/prelude.rs | 2 +- core/tests/regression_tests.rs | 1 + .../output.txt | 40 +++++++++++++ .../test.fla | Bin 0 -> 6236 bytes .../test.swf | Bin 0 -> 1422 bytes 7 files changed, 110 insertions(+), 17 deletions(-) create mode 100644 core/tests/swfs/avm2/displayobjectcontainer_removechildren/output.txt create mode 100644 core/tests/swfs/avm2/displayobjectcontainer_removechildren/test.fla create mode 100644 core/tests/swfs/avm2/displayobjectcontainer_removechildren/test.swf diff --git a/core/src/avm2/globals/flash/display/displayobjectcontainer.rs b/core/src/avm2/globals/flash/display/displayobjectcontainer.rs index 8af35cc2d..e106c7738 100644 --- a/core/src/avm2/globals/flash/display/displayobjectcontainer.rs +++ b/core/src/avm2/globals/flash/display/displayobjectcontainer.rs @@ -11,6 +11,7 @@ use crate::avm2::Error; use crate::context::UpdateContext; use crate::display_object::{DisplayObject, TDisplayObject, TDisplayObjectContainer}; use gc_arena::{GcCell, MutationContext}; +use std::cmp::min; /// Implements `flash.display.DisplayObjectContainer`'s instance constructor. pub fn instance_init<'gc>( @@ -370,6 +371,57 @@ pub fn remove_child_at<'gc>( Ok(Value::Undefined) } +/// Implements `DisplayObjectContainer.removeChildren` +pub fn remove_children<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(parent) = this.and_then(|this| this.as_display_object()) { + if let Some(mut ctr) = parent.as_container() { + let from = args + .get(0) + .cloned() + .unwrap_or_else(|| 0.into()) + .coerce_to_i32(activation)?; + let to = args + .get(1) + .cloned() + .unwrap_or_else(|| i32::MAX.into()) + .coerce_to_i32(activation)?; + + if from >= ctr.num_children() as i32 || from < 0 { + return Err(format!( + "RangeError: Starting position {} does not exist in the child list (valid range is 0 to {})", + from, + ctr.num_children() + ) + .into()); + } + + if (to >= ctr.num_children() as i32 || to < 0) && to != i32::MAX { + return Err(format!( + "RangeError: Ending position {} does not exist in the child list (valid range is 0 to {})", + to, + ctr.num_children() + ) + .into()); + } + + if from > to { + return Err(format!("RangeError: Range {} to {} is invalid", from, to).into()); + } + + ctr.remove_range_of_ids( + &mut activation.context, + from as usize..min(ctr.num_children(), to as usize + 1), + ); + } + } + + Ok(Value::Undefined) +} + /// Construct `DisplayObjectContainer`'s class. pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { let class = Class::new( @@ -421,6 +473,10 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc> QName::new(Namespace::public_namespace(), "removeChildAt"), Method::from_builtin(remove_child_at), )); + write.define_instance_trait(Trait::from_method( + QName::new(Namespace::public_namespace(), "removeChildren"), + Method::from_builtin(remove_children), + )); class } diff --git a/core/src/display_object/container.rs b/core/src/display_object/container.rs index 142fe2382..4dfb4010e 100644 --- a/core/src/display_object/container.rs +++ b/core/src/display_object/container.rs @@ -10,7 +10,7 @@ use ruffle_macros::enum_trait_object; use std::cmp::Ordering; use std::collections::BTreeMap; use std::fmt::Debug; -use std::ops::Range; +use std::ops::RangeBounds; #[enum_trait_object( #[derive(Clone, Collect, Debug, Copy)] @@ -113,11 +113,9 @@ pub trait TDisplayObjectContainer<'gc>: /// Remove a set of children identified by their render list IDs from this /// container's render, depth, and execution lists. - fn remove_range_of_ids( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - range: Range, - ); + fn remove_range_of_ids(&mut self, context: &mut UpdateContext<'_, 'gc, '_>, range: R) + where + R: RangeBounds; /// Clear all three lists in the container. fn clear(&mut self, context: MutationContext<'gc, '_>); @@ -238,11 +236,10 @@ macro_rules! impl_display_object_container { .remove_child(context, child) } - fn remove_range_of_ids( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - range: Range, - ) { + fn remove_range_of_ids(&mut self, context: &mut UpdateContext<'_, 'gc, '_>, range: R) + where + R: RangeBounds, + { self.0 .write(context.gc_context) .$field @@ -625,11 +622,10 @@ impl<'gc> ChildContainer<'gc> { /// Remove a set of children identified by their render list IDs from this /// container's render, depth, and execution lists. - pub fn remove_range_of_ids( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - range: Range, - ) { + pub fn remove_range_of_ids(&mut self, context: &mut UpdateContext<'_, 'gc, '_>, range: R) + where + R: RangeBounds, + { let removed_list: Vec> = self.render_list.drain(range).collect(); for removed in removed_list { diff --git a/core/src/prelude.rs b/core/src/prelude.rs index bcb4c251f..5ece7730c 100644 --- a/core/src/prelude.rs +++ b/core/src/prelude.rs @@ -7,7 +7,7 @@ pub use crate::{ impl_display_object, impl_display_object_container, impl_display_object_sansbounds, }; pub use log::{error, info, trace, warn}; -pub use std::ops::Range; +pub use std::ops::RangeBounds; pub use swf::Matrix; pub use swf::{CharacterId, Color, Twips}; diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 398f01ae4..18a45a7a7 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -426,6 +426,7 @@ swf_tests! { (as3_displayobjectcontainer_contains, "avm2/displayobjectcontainer_contains", 5), (as3_displayobjectcontainer_getchildindex, "avm2/displayobjectcontainer_getchildindex", 5), (as3_displayobjectcontainer_removechildat, "avm2/displayobjectcontainer_removechildat", 1), + (as3_displayobjectcontainer_removechildren, "avm2/displayobjectcontainer_removechildren", 5), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/displayobjectcontainer_removechildren/output.txt b/core/tests/swfs/avm2/displayobjectcontainer_removechildren/output.txt new file mode 100644 index 000000000..f65d80d7b --- /dev/null +++ b/core/tests/swfs/avm2/displayobjectcontainer_removechildren/output.txt @@ -0,0 +1,40 @@ +//var chx = this.getChildAt(0) +//var chy = this.getChildAt(1) +//var chz = this.getChildAt(2) +//this.removeChildren(0, 1) +//this.getChildAt(0) === chz +true +//this.numChildren +1 +//this.removeChildren(0,0x7fffffff) +//this.numChildren +0 +//var chx = this.getChildAt(0) +//var chy = this.getChildAt(1) +//var chz = this.getChildAt(2) +//this.removeChildren(1) +//this.getChildAt(0) === chx +true +//this.numChildren +1 +//this.removeChildren() +//this.numChildren +0 +//var chx = this.getChildAt(0) +//var chy = this.getChildAt(1) +//var chz = this.getChildAt(2) +//this.removeChildren(0, 0) +//this.getChildAt(0) === chy +true +//this.getChildAt(1) === chz +true +//this.numChildren +2 +//this.removeChildren(0, 0) +//this.getChildAt(0) === chz +true +//this.numChildren +1 +//this.removeChildren(0, 0) +//this.numChildren +0 diff --git a/core/tests/swfs/avm2/displayobjectcontainer_removechildren/test.fla b/core/tests/swfs/avm2/displayobjectcontainer_removechildren/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..505f651d03666ebe1b89f36b04b6ea8e6934b385 GIT binary patch literal 6236 zcmbVRbySq=^Iz%iE-6WAX^>h_LAq0F=~!SvB&EAUL>iGs8tD${E|KmI>H00#uX^>| zd;j>&o_XJA-!n7soSpN`e4fuL$iTwk0RTt&*Y^3!weg026i;GGIub8 zfULn*2GB(QXAXme^jE6j7WZUscmN>&Apn30H3!Pe0&R^PtwA=>jIFJ9v~+D3`S9HD zo8S2GUJgeg7z$!gm8IiEmWGV>t7KeKg-PXOB}GrUrL~o_H+t;Q4g$OohMi1Cmys7; z2dWzyCLRY{C8QSh4yIf_($`;b6}vLPt|d3`p4|3%I5#)PyBB9d^tGEvoc7kik-s#1 zw2H(Ai;KB9H`b+4*!z>YYzv3=bUGUwnfzG=2zl~|VUditNM4vXjU3rdzVk9fs)Q7Q z9GTZKtg)2FqJ*6UL2fdt1_lWNJ5fbMpZJQvp`~l@sb_67ZC%6z@SB?KRhq4ExhCi8 zL%>_&rs7=hgPCJGQL#uYZj5Ze^**-=aB&a=2NHZg?W zaCL);Xzr*@Xf*_u75f2ck{ex0#Xx%VnKa%hSA(sq^SQM>w2F zJ5*xn*==6(IcoTqy-2|oWbXe=A&0+#ndDy=XXOL>S{mmuTbBPA=H5qiibMvH*Kj@2 zQgq2B8)SI_3#ryorRs7v3-pgHikj$nPo^UAo!{|lm~J)b7-RFt66ijGoaZ|m8L z!`${PM5L^%bmJ{;&r3D9?F|?R;9ZT~QGDr5tSzao6T;lHJrCE*)yRAIXJNDmCewu! zN}5>*t6&6^5Se)9TlsU0g)%&xpWxy)^qcMrcfm{xY5__)tOqwBK)7LvnaePq)%XN> z2haAZut-uSZv0P!N#FP%WhJTJGn)c8xbd%Dy8~B<4-dvH_GfYF&zD%@ju!gjcCA{s zpCYCRh8LLeFTb1Wgt$I?c@}LQ-@4ifUS_MW#tB^ z{-JpkksH5{Mj(bvLA&ERLZ=lOe_GqI@27lt{Vu`$%ZFJ*mo`~mc4~x3nIDSAvlM4< zLDHGG*U~=ar7S*XeKnjZjhtxJ-_B#ew28aU#{qIGp84|>@@8@C`|_ExslYdngXn~H zBPg&kv*Ok>LUDA3GI73&1Zdff2Oq_eQRKcXLlGVp zVHh41RBD#rY93jHH5|GY#vLq^e2v7MC4DLyeTf49hGN-U8#AbNQ@p(u6q?*a_;%l2 z;i$v0&EaV#HmDcf#sRkMe9~cQ<36j%TILGf7e@g)0T^Q^n$vfmuq7ku3iX1Vqb;eK zta59@Ct&Hw2W-oF@a;8?{kFyEm~sOPT7Q#u8M;=-T*Y>C_<1QYAZhRUk?fH+n{17rZKcq;|0dPpmgSURNGp z6cg2!BnNYXs747-;4~{x?GYODNt%C1l(uTVoH_oACS^XprG%JdvNv*v>XxRMBnPUo zN(m2Gn?*`$C-{`*ki5#=UEdTu&E@wyV^DEEqeH-R~!vNY;e+oWy^|NnVgCGXR z1`vb0KKsMvdsCKDw4c601ORf#003O5i^BIFt_*@e%xz2^?i|`RZJ~`B*V8ix(Qo)fal>Bb)P*gQiI-#EI?y4UamVAr^op$wAzrQ4MJ_J!ZE7c1%7JqS z^7Sk4!xf2+^(0z8Opgt1(G}vdq`_}SV}iNr%#2zK5o1&-nT{>_ab|r)a_5~)rj|PB z9=R$PF~>&Yhbw`l_=~twT)lkFui&%SIhyUB;rSY6trL{5dQFH_I6t6Pr|G~;7uA?i zC$sC`eMT~RnN6@47^-L-grpbr@SF)-;xIQ2{w*p{2ydUe71gUNdpt=R`X$o#J^XS*KX$p(bb< zMp9wY3M=bTuSx3H*Pr&)!ig=h?=)`<$L<+esyGa=-aJ?@dlfW6FyO z;8HIZi^Q4#KAM4VeG!#_zZq%E`az;UyNi&~=+VtONrq8iB~?G#5GQa1tAUEde;-eg zSHh9#P^*Dj3?Avkm*wLE7JAEw}H+EC}w0=Ps-k^m;2D>vrWgCUzL6L=B#(iq|O{R*uiz0D; zd}0mx$HwYKJ0o-z9g=Fh_Z4DvG&Z+Vcm2jfU2%>;gBD(fl1yhPG40WaeFF1|JbF=n zb?yw}AGse0y|Ik(g|TJm14gbWiz$Z_rKUNC8&T<+REYY<#sm*qriZ%uU}XZQb;NS~ zX5GPdO#DG)2f7N#x7AX}3m?VGJT0Dbv$sVchouB=!(s`Q6p;7AafEG{JU6O7!O7;1 z5|SC*BFeTbiRQF?E*ygt(L@Y-KR>!puGb7#;aKX4_PDoU8LVew*Ot=tu(yI?1t=yZ=Z4+X(UdjiiU;XhB*$wKb%3y^!54m6?*v%h_m~-N8kQ z5=au;vTB=D_k$_c+VK^Q8SZe^Wx+)B4LZLh?%0+mzG6T<0B>xI$WkaLF}ExuQEy-( z3s_;~5gTI-JJG4G3{5S$kT z#Dl6GU<_MyYZ_Hi>D@(5ybN*Ww=BpSE>^%N0KL~lK?ku#BO2$nG-{Wqi5`lLVQ&dF zZK9EB1y;M(q{9;be<7z;}vfGuq z8GkJ1qMIwkSd)z)k`T9-a)=KPDE}cOq-K#}fH$^XOmkBIgJ*6s`G_fXs}XD^mhZ_f zcZ_PUU+lc&#dCX@wG*jb(j13Kd$E}6(n7OfmNhQClKv4qv9adV^xd44V%xOXTG!R> zc3HyqssstX6qT)qeD!i9g_3MUq8`Dq#pj0k28uw{7#8JjSUeWVONMnr1|0{1HbrkD zf3<1h7*0@eBpJ^7%QXC_jK*i;O`q)$8QY%ok8r|U%4H>Rs@m>qcYM>cYx>|uE{x7W z*3)De^mzL<9eRc_`BGyiZX67Jwjht;0m2n(CK9~{H<^Jwn_dBVa2qvdqMmNMW(i%Y zacT`!1X{@iX>{-rQLzwqb%pKImjN4nz6-t?L}eVSG9-wk{`0Dkfu`NijcbOpV_vhO z&AJ;K{ET2vB@sXPYEF#*gKk#nwd+0l$F-MQ0M!&W!U{+Z#m;dE81WqcI z-fkBt`6Z>XwaSlUaY#vRnMyNAe@b-9{Yz)*=^NvlvgC=0r<(1k7lksjw>Xqt_sU^3 z<*Pz5rh}DO8{I6rCtZjKeM^PR%C%ae4SKvvX4pDG_O9n;ONVr8n0yX$XIQ39Jc$)g zT9`>&-o&wgj?eMb3axLo?Wr%FOC8)ay%biMYXD8$t37=His_ zD1Qkf67d%H2Uz%i*!&4DpbMNkd|_*7ZUy=~R`~rK3G5&Ck%HRY=^eN*G`C?>eq{{= zfk8ILtVRY#W*}4O<7Grt01N;HKmcF>K%oc#7OF@9sG*^)mH5w<#Ltx&prH_I@;CXB zCK4dvgNkA(GLa%jl zcany`+L-R`?vzd|OxO`!Yt&0U3zxLA)6CbPLt@D{>Y5H8u@7@nB7md@`SWIp8tdo- z3a4nWimvtI?}e6`^N^eN+9x*aUmF9BqfFPsNmCpyXyU!s`8$qv!^RSLhk zhzU7#4pFz4Mb-(ld=!J`$f<-l6xYI9U)!$8=zKAn^yDRz0pHtR6VZTg=BD6!J^`KN zh>g|7^_t}}x#(^AByK-k2_$`Ejsb_jw=}2Om{QlKAt!0g1r+7|id>l6BiRyJ7m$6M zM;jM$B;n=}BWf9h^~P$6QahyYQXrL@2RFA1XW!-oEqbaOrlOo2lN2HshIs;fDckGD}6$%2kGO3t4M#q_`yk z1cYTo-qmd>zB&$-X&Txi^xuXMf;(#PSQ zm(^o>GPfEKaUzt5q`G+Z^bPIqQuj6v6!p`;cpi;+yF9}qM@H$dH}_oj=}ZcownwMN z*0&QA(hBVX?Jopt-mVU=P2+a`fld`9L;bc!qHjcUTR$D}Q9N+rktrHR-}ETn7&5L6 zj%W?Q9rAfOTDf)$8}ak}@_plWGz5?Hqe|G0 zlS_BYIh(AhME)eyld^=%1%&7{Mn@YMau^mxeDSqHN@?{$lo4NYL)CegP#kjF`5`4M zU=Xl=YBDqV6X9u%+{|)2jvO!u#lGw*4Y5q15K~Rvj$3f}`pCj1DOFJvGDB{&me~?^ zyy-+{AWC>-n1Lk^E_s29m}x2bAQxUU56#EzXd6Ag0Uf7XW(H*CI1`HNdADHH9o={@ z9#bi5hx>TGtD3E)9mJ(8FB_CBwcr9rvB`wG8P9!aQpo)-U&uu~24HzpBX>Vm(p|rj zE%qZU$$-lpWllhs<5+)}nJ0}sFs)je%0wI%?4hE&;q=9N8zq0qG_GlDQN!Jl6}Xi> zuG)-@@XU>hP;#LSMrhhBSguh%aZwpO$G<>%Fl?q%m36vV-hgA8Jh8LkU$~mYn$J82 zs(9&>Hlc38;Uu*CBuKa~y06^Fh1|-CcZ$d8)g@TR=_tJWFwax}4DN=6A2ZTis>-U9g}xHmLp>EeikPb;qE7mpd<2sQx^c|CiexqxxO$+}xo05B%!) zT>p(>{hvSdmJiiG_|_lq{x|aAmoqxT-%|daO8EW#f07BmLWn{``~$V{Upd?Xj9+4n z@y~GM_hS7CIDYvjL;e2&e*8+{pP|FA0C-T!`bV|@1|_anyZ--)*N*e&H11E@#9k+)Ee4aePE*o0 z_z(^pTs0ee9B;ALW_RNzy&_ct^#%tjE=8P>N)A!av>=rmlJ7{+3s>Y`dO|WY`&TL$FasGD>CvcnXp_fP{Hkn8%Y|^&tpGzyA5=X1+<)`Pr zzuN!v;EVi|HIv6_@i=TA2L{IH61tF*|_At*^pBMl5b@|of$U!Ur zf*=h@fqD!8AAyq^QnlOFx|mxRCHZ5if$EZs zGB#RO#0JW~QPkoaMJ=8CpQwJ6wNP(A>cfCqOIIYdRTuZT^=4Z_g}T&=4&`u^YrFW~ z!}mRx@i@k?XbelVrif)krG)94rnPdJj96)Ip>(;~$Xwo7%q$4P`OMaiRM#ZgW4LxS zsjfy06=J!JR8`uN>XNo+DMRX~TC?RfETkw+MYW7Jk~P(D8oL@&WU+3hRq+}~QQd~M zjhYQ}jqQ4KORP)xL``bS9s_HjiX?KzTt=GDo~0{F!{l!%l8jYv+h!9lffO@V6&tNS z3(4whnd+XZp@ykM!#e%1r)gZ+P@jq9ZL(DcvLl)L^ctD^b{qBBz@FvoJsTOsH?U+4 zr3S$jV=QjnL1nGblr_A&NSPW8T9a|@#Ih!}kr6m{49R%^X5_Atf%rwOTtmuaCes#` zByO8zKB?6t6>o)Bs7dw8W$lbGJ7INMy1&bE=9}Vv9c%OK4dKbo+U|Qp+zYc~nT!r9 zsL^aA9aoTiMwm-xXZ?fVxEVSiGnvol33=Fb$~z4!-qSaqIcgbU_d?aUj(_6C_;7~o z>~XSoW5ctKUu)^;q6o(>w>QL@wM*nw@yqHLE0tBU$VOR_TH4GJI{Gb_(M*9u{o@;G zwa|YWlONvCE~EFaujNXGW(Ad$oocm?O68`4N<@rWQtwvPlGtu6l+31X>I`Lz;kHqh zvRRhWY2L-NaW@ZRF4|3p*$6wrM_Hba@o{#PPw->xI6uKp@>6V*on}w4Gkl6a$)93R z^Jn;3_H4qPIG319%qP-`OhTYexfmy;S`5<=zayiJYK<+B!{E!PkE(jUt zHOBWLL#;8?b%wgZP&Zx1sYU6hDLE_2X{cimQY=0vT?E!~-2upX%;0mF)z4E%L+FI$ zXoQ^G(Gjj;$N}A+A7Dqg#m!}3E_7j$gI%iANr6>2ta-W)jK4!LC&nPbyj>b&O9XRa zj3St?%VO+2!8n|nf<-^o^ZDfL1D433Ei!BoM=au~wQKz0U5i=iaXUR>rH@(Z6IRfq9hB@8 zgP=Q&%i&~jIg%Zrvj%R3Uaodw3UsRB6xFFlQqE3wq!@B_DI7;rxIxc#1Ozst!X__l z#)QqdusJGhCWK82|2_c|##Mta*Sm3?vK&bmF9I0)SA{