avm1: Remove delay to enter the unloaded state for non-root MovieClips

avm1_unload_movie has been adapted to only enter the unloaded state with
one frame delay if the MovieClip is a root MovieClip. The unloaded state
is now immediately entered for non-root MovieClips.
This fixes the regressions #12254 and #12265 which got introduced
because of this delay.

However, in Flash Player, even non-root MovieClips enter the unloaded
state one frame after the unloadMovie command has been read. Ruffle is
probably replacing a MovieClip differently to Flash, therefore
introducing these regressions when trying to emulate that delay.
Documentation explaining this all has been added to avm1_unload_movie.

Therefore, the test movieclip_library_state_values has been added. It
tests the default state and the unloaded state of a (non-root) child
MovieClip that's loaded from the library. It is marked as known_failure
because Ruffle currently doesn't implement the delay before entering the
unloaded state for non-root MovieClips.
This commit is contained in:
Kornelius Rohrschneider 2023-08-08 19:42:16 +02:00 committed by Nathan Adams
parent f582ea572c
commit a50f5cd072
6 changed files with 267 additions and 30 deletions

View File

@ -2420,40 +2420,53 @@ impl<'gc> MovieClip<'gc> {
// after one frame if the target is a MovieClip? Test the behaviour and adapt the code // after one frame if the target is a MovieClip? Test the behaviour and adapt the code
// if necessary. // if necessary.
pub fn avm1_unload_movie(&self, context: &mut UpdateContext<'_, 'gc>) { pub fn avm1_unload_movie(&self, context: &mut UpdateContext<'_, 'gc>) {
let unloader = Loader::MovieUnloader { // TODO: In Flash player, the MovieClip properties change to the unloaded state
self_handle: None, // one frame after the unloadMovie command has been read, even if the MovieClip
target_clip: DisplayObject::MovieClip(*self), // is not a root MovieClip (see the movieclip_library_state_values test).
}; // However, if avm1_unload and transform_to_unloaded_state are called with a one
let handle = context.load_manager.add_loader(unloader); // frame delay when the MovieClip is not a root MovieClip, regressions appear.
// Ruffle is probably replacing a MovieClip differently to Flash, therefore
// introducing these regressions when trying to emulate that delay.
let player = context if self.is_root() {
.player let unloader = Loader::MovieUnloader {
.clone() self_handle: None,
.upgrade() target_clip: DisplayObject::MovieClip(*self),
.expect("Could not upgrade weak reference to player"); };
let future = Box::pin(async move { let handle = context.load_manager.add_loader(unloader);
player
.lock()
.unwrap()
.update(|uc| -> Result<(), loader::Error> {
let clip = match uc.load_manager.get_loader(handle) {
Some(Loader::MovieUnloader { target_clip, .. }) => *target_clip,
None => return Err(loader::Error::Cancelled),
_ => unreachable!(),
};
if let Some(mc) = clip.as_movie_clip() {
mc.avm1_unload(uc);
mc.transform_to_unloaded_state(uc);
}
uc.load_manager.remove_loader(handle); let player = context
.player
.clone()
.upgrade()
.expect("Could not upgrade weak reference to player");
let future = Box::pin(async move {
player
.lock()
.unwrap()
.update(|uc| -> Result<(), loader::Error> {
let clip = match uc.load_manager.get_loader(handle) {
Some(Loader::MovieUnloader { target_clip, .. }) => *target_clip,
None => return Err(loader::Error::Cancelled),
_ => unreachable!(),
};
if let Some(mc) = clip.as_movie_clip() {
mc.avm1_unload(uc);
mc.transform_to_unloaded_state(uc);
}
Ok(()) uc.load_manager.remove_loader(handle);
})?;
Ok(())
});
context.navigator.spawn_future(future); Ok(())
})?;
Ok(())
});
context.navigator.spawn_future(future);
} else {
self.avm1_unload(context);
self.transform_to_unloaded_state(context);
}
} }
/// This makes the MovieClip enter the unloaded state in which some attributes have /// This makes the MovieClip enter the unloaded state in which some attributes have

View File

@ -0,0 +1,78 @@
_accProps = undefined
_alpha = 100
_currentframe = 1
_droptarget =
_focusrect = null
_framesloaded = 1
_height = 0
_highquality = 1
_lockroot = false
_name = childClip
_parent = _level0
_quality = HIGH
_rotation = 0
_soundbuftime = 5
_target = /childClip
_totalframes = 1
_visible = true
_width = 0
_x = 0
_xmouse = 21
_xscale = 100
_y = 0
_ymouse = 0
_yscale = 100
blendMode = normal
cacheAsBitmap = false
enabled = true
filters =
filters.length = 0
focusEnabled = undefined
forceSmoothing = undefined
hitArea = undefined
menu = undefined
opaqueBackground = undefined
scale9Grid = undefined
scrollRect = undefined
tabChildren = undefined
tabEnabled = undefined
tabIndex = undefined
trackAsMenu = undefined
transform.colorTransform = (redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=0, greenOffset=0, blueOffset=0, alphaOffset=0)
transform.concatenatedColorTransform = (redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=0, greenOffset=0, blueOffset=0, alphaOffset=0)
transform.matrix = (a=1, b=0, c=0, d=1, tx=0, ty=0)
transform.concatenatedMatrix = (a=1, b=0, c=0, d=1, tx=0, ty=0)
transform.pixelBounds = (x=0, y=0, w=0, h=0)
useHandCursor = true
_url = movieclip_library_state_values/test.swf
getBounds(this).xMin = 6710886.4
getBounds(this).xMax = 6710886.4
getBounds(this).yMin = 6710886.4
getBounds(this).yMax = 6710886.4
getBounds(mc).xMin = 6710886.35
getBounds(mc).xMax = 6710886.35
getBounds(mc).yMin = 6710886.35
getBounds(mc).yMax = 6710886.35
getBytesLoaded() = 4
getBytesTotal() = 4
getDepth() = 0
getInstanceAtDepth(0) = undefined
getNextHighestDepth() = 0
getRect(this).xMin = 6710886.4
getRect(this).xMax = 6710886.4
getRect(this).yMin = 6710886.4
getRect(this).yMax = 6710886.4
getRect(mc).xMin = 6710886.35
getRect(mc).xMax = 6710886.35
getRect(mc).yMin = 6710886.35
getRect(mc).yMax = 6710886.35
getSWFVersion() = 10
getTextSnapshot().getCount() = 0
Both targets have the same props.
Change: Prop _currentframe is "1" on the first, but "0" on the second target.
Change: Prop _framesloaded is "1" on the first, but "0" on the second target.
Change: Prop _totalframes is "1" on the first, but "0" on the second target.
Change: Prop getBytesLoaded() is "4" on the first, but "0" on the second target.
Change: Prop getBytesTotal() is "4" on the first, but "0" on the second target.

View File

@ -0,0 +1,144 @@
// SWF Version 10
/*
* This test tests the default state and the unloaded state of a child MovieClip that's loaded
* from the library.
* A state of a MovieClip consists of the values of all properties and the results of some getter
* functions of the MovieClip.
*
* The default state is the state the MovieClip is in after it's loaded with attachMovie.
* The unloaded state is the state the MovieClip enters on the next frame after the MovieClip
* has been unloaded.
*/
var childClip = this.attachMovie("childClip", "childClip", this.getNextHighestDepth());
var mcPropsArray1: Array;
var mcPropsArray2: Array;
var mcPropsArray3: Array;
var frameCount = 0;
this.onEnterFrame = function() {
frameCount++;
if (frameCount == 1) {
mcPropsArray1 = getMcPropsArray(childClip);
printMcProps(mcPropsArray1);
unloadMovie(childClip);
mcPropsArray2 = getMcPropsArray(childClip);
trace("");
compareMcProps(mcPropsArray1, mcPropsArray2, true);
}
if (frameCount == 2) {
mcPropsArray3 = getMcPropsArray(childClip);
trace("");
compareMcProps(mcPropsArray2, mcPropsArray3, true);
this.onEnterFrame = null;
}
};
/*
* This traces all elements of an McProps-Array returned by getMcPropsArray with their respective names.
*/
function printMcProps(mcPropsArray:Array) {
for (var propIterator = 0; propIterator < mcPropsArray.length; propIterator++) {
var propName = mcPropsArray[propIterator][0];
var propValue = mcPropsArray[propIterator][1];
trace(propName + " = " + propValue);
}
}
/*
* This compares two McProps-Arrays returned by getMcPropsArray.
* If traceDifferences is true, all differences will be traced.
* It returns the diverging values with their respective names in a two-dimensional array.
*/
function compareMcProps(mcPropsArray1:Array, mcPropsArray2:Array, traceDifferences:Boolean) {
if (mcPropsArray1 == undefined || mcPropsArray2 == undefined) {
trace("Error: An mcPropsArray is undefined.");
return;
}
var differenceArray = [];
for (var propIterator = 0; propIterator < mcPropsArray1.length; propIterator++) {
if (mcPropsArray1[propIterator][1].toString() != mcPropsArray2[propIterator][1].toString()) {
var value1 = mcPropsArray1[propIterator][1];
var value2 = mcPropsArray2[propIterator][1];
var propName = mcPropsArray1[propIterator][0];
differenceArray.push([propName, value1, value2])
if (traceDifferences) {
trace("Change: Prop " + propName + " is \"" + value1 + "\" on the first, but \"" + value2 +
"\" on the second target.");
}
}
}
if (traceDifferences && differenceArray.length == 0) {
trace("Both targets have the same props.");
}
return differenceArray;
}
/*
* This returns all properties and the results of some getter functions of a MovieClip with their respective
* names in a two-dimensional array.
*/
function getMcPropsArray(mc:MovieClip) {
var mcProps = [];
var simplePropNames = ["_accProps", "_alpha", "_currentframe", "_droptarget", "_focusrect", "_framesloaded",
"_height", "_highquality", "_lockroot", "_name", "_parent", "_quality", "_rotation", "_soundbuftime",
"_target", "_totalframes", "_visible", "_width", "_x", "_xmouse", "_xscale", "_y", "_ymouse", "_yscale",
"blendMode", "cacheAsBitmap", "enabled", "filters", "filters.length", "focusEnabled", "forceSmoothing",
"hitArea", "menu", "opaqueBackground", "scale9Grid", "scrollRect", "tabChildren", "tabEnabled", "tabIndex",
"trackAsMenu", "transform.colorTransform", "transform.concatenatedColorTransform", "transform.matrix",
"transform.concatenatedMatrix", "transform.pixelBounds", "useHandCursor"];
for (var simplePropIterator = 0; simplePropIterator < simplePropNames.length; simplePropIterator++) {
var simplePropName = simplePropNames[simplePropIterator];
var simplePropValue = eval("mc." + simplePropName);
mcProps.push([simplePropName, simplePropValue]);
}
var url = unescape(mc._url);
if (url.indexOf("file:///") == 0) {
var urlSplit = url.split("/");
mcProps.push(["_url", urlSplit[urlSplit.length - 2] + "/" + urlSplit[urlSplit.length - 1]]);
} else {
mcProps.push(["_url", url]);
}
var getBoundsThis = mc.getBounds(this);
mcProps.push(["getBounds(this).xMin", getBoundsThis.xMin]);
mcProps.push(["getBounds(this).xMax", getBoundsThis.xMax]);
mcProps.push(["getBounds(this).yMin", getBoundsThis.yMin]);
mcProps.push(["getBounds(this).yMax", getBoundsThis.yMax]);
var getBoundsMc = mc.getBounds(mc);
mcProps.push(["getBounds(mc).xMin", getBoundsMc.xMin]);
mcProps.push(["getBounds(mc).xMax", getBoundsMc.xMax]);
mcProps.push(["getBounds(mc).yMin", getBoundsMc.yMin]);
mcProps.push(["getBounds(mc).yMax", getBoundsMc.yMax]);
mcProps.push(["getBytesLoaded()", mc.getBytesLoaded()]);
mcProps.push(["getBytesTotal()", mc.getBytesTotal()]);
mcProps.push(["getDepth()", mc.getDepth()]);
mcProps.push(["getInstanceAtDepth(0)", mc.getInstanceAtDepth(0)]);
mcProps.push(["getNextHighestDepth()", mc.getNextHighestDepth()]);
var getRectThis = mc.getBounds(this);
mcProps.push(["getRect(this).xMin", getRectThis.xMin]);
mcProps.push(["getRect(this).xMax", getRectThis.xMax]);
mcProps.push(["getRect(this).yMin", getRectThis.yMin]);
mcProps.push(["getRect(this).yMax", getRectThis.yMax]);
var getRectMc = mc.getBounds(mc);
mcProps.push(["getRect(mc).xMin", getRectMc.xMin]);
mcProps.push(["getRect(mc).xMax", getRectMc.xMax]);
mcProps.push(["getRect(mc).yMin", getRectMc.yMin]);
mcProps.push(["getRect(mc).yMax", getRectMc.yMax]);
mcProps.push(["getSWFVersion()", mc.getSWFVersion()]);
mcProps.push(["getTextSnapshot().getCount()", mc.getTextSnapshot().getCount()]);
return mcProps;
}

View File

@ -0,0 +1,2 @@
num_frames = 3
known_failure = true