avm1: Simplify `TDisplayObject::avm1_root()`

Make it infailable.
This commit is contained in:
relrelb 2022-03-04 00:10:45 +02:00 committed by relrelb
parent 355bd35935
commit 5a1e417526
10 changed files with 44 additions and 88 deletions

View File

@ -654,7 +654,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let depth = self.context.avm1.pop();
let target = self.context.avm1.pop();
let source = self.context.avm1.pop();
let start_clip = self.target_clip_or_root()?;
let start_clip = self.target_clip_or_root();
let source_clip = self.resolve_target_display_object(start_clip, source, true)?;
if let Some(movie_clip) = source_clip.and_then(|o| o.as_movie_clip()) {
@ -722,7 +722,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn action_call(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
// Runs any actions on the given frame.
let arg = self.context.avm1.pop();
let target = self.target_clip_or_root()?;
let target = self.target_clip_or_root();
// The parameter can be a frame # or a path to a movie clip with a frame number.
let mut call_frame = None;
@ -778,7 +778,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let variable = self.get_variable(fn_name)?;
let result = variable.call_with_default_this(
self.target_clip_or_root()?.object().coerce_to_object(self),
self.target_clip_or_root().object().coerce_to_object(self),
fn_name,
self,
&args,
@ -1196,11 +1196,11 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
if let Value::Object(target) = target {
target.as_display_object()
} else {
let start = self.target_clip_or_root()?;
let start = self.target_clip_or_root();
self.resolve_target_display_object(start, target, true)?
}
} else {
Some(self.target_clip_or_root()?)
Some(self.target_clip_or_root())
};
if action.is_load_vars() {
@ -1313,7 +1313,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn action_goto_frame_2(&mut self, action: GotoFrame2) -> Result<FrameControl<'gc>, Error<'gc>> {
// Version 4+ gotoAndPlay/gotoAndStop
// Param can either be a frame number or a frame label.
if let Some(clip) = self.target_clip_or_root()?.as_movie_clip() {
if let Some(clip) = self.target_clip_or_root().as_movie_clip() {
let frame = self.context.avm1.pop();
let _ = globals::movie_clip::goto_frame(
clip,
@ -1764,7 +1764,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn action_remove_sprite(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
let target = self.context.avm1.pop();
let start_clip = self.target_clip_or_root()?;
let start_clip = self.target_clip_or_root();
let target_clip = self.resolve_target_display_object(start_clip, target, true)?;
if let Some(target_clip) = target_clip {
@ -1871,7 +1871,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
};
let scope = self.scope_cell();
let clip_obj = self.target_clip_or_root()?.object().coerce_to_object(self);
let clip_obj = self.target_clip_or_root().object().coerce_to_object(self);
self.set_scope(Scope::new_target_scope(
scope,
@ -1891,7 +1891,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn action_start_drag(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
let target = self.context.avm1.pop();
let start_clip = self.target_clip_or_root()?;
let start_clip = self.target_clip_or_root();
let display_object = self.resolve_target_display_object(start_clip, target, true)?;
if let Some(display_object) = display_object {
let lock_center = self.context.avm1.pop();
@ -2389,7 +2389,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
return Ok(None);
}
let root = start.avm1_root(&self.context)?;
let root = start.avm1_root();
let start = start.object().coerce_to_object(self);
Ok(self
.resolve_target_path(root, start, &path, false)?
@ -2475,7 +2475,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
if first_element && name == b"this" {
self.this_cell()
} else if first_element && name == b"_root" {
self.root_object()?
self.root_object()
} else {
// Get the value from the object.
// Resolves display object instances first, then local variables.
@ -2525,7 +2525,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let mut current_scope = Some(self.scope_cell());
while let Some(scope) = current_scope {
let avm1_root = start.avm1_root(&self.context)?;
let avm1_root = start.avm1_root();
if let Some(object) =
self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)?
{
@ -2569,7 +2569,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
/// scope chain.
pub fn get_variable(&mut self, path: AvmString<'gc>) -> Result<CallableValue<'gc>, Error<'gc>> {
// Resolve a variable path for a GetVariable action.
let start = self.target_clip_or_root()?;
let start = self.target_clip_or_root();
// Find the right-most : or . in the path.
// If we have one, we must resolve as a target path.
@ -2580,7 +2580,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let mut current_scope = Some(self.scope_cell());
while let Some(scope) = current_scope {
let avm1_root = start.avm1_root(&self.context)?;
let avm1_root = start.avm1_root();
if let Some(object) =
self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)?
{
@ -2599,7 +2599,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
if path.contains(b'/') {
let mut current_scope = Some(self.scope_cell());
while let Some(scope) = current_scope {
let avm1_root = start.avm1_root(&self.context)?;
let avm1_root = start.avm1_root();
if let Some(object) =
self.resolve_target_path(avm1_root, *scope.read().locals(), &path, false)?
{
@ -2642,7 +2642,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
value: Value<'gc>,
) -> Result<(), Error<'gc>> {
// Resolve a variable path for a GetVariable action.
let start = self.target_clip_or_root()?;
let start = self.target_clip_or_root();
// If the target clip is invalid, we default to root for the variable path.
if path.is_empty() {
@ -2660,7 +2660,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let mut current_scope = Some(self.scope_cell());
while let Some(scope) = current_scope {
let avm1_root = start.avm1_root(&self.context)?;
let avm1_root = start.avm1_root();
if let Some(object) =
self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)?
{
@ -2709,17 +2709,14 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
/// Actions that affect `root` after an invalid `tellTarget` will use this.
///
/// The `root` is determined relative to the base clip that defined the
pub fn target_clip_or_root(&self) -> Result<DisplayObject<'gc>, Error<'gc>> {
if let Some(target) = self.target_clip() {
return Ok(target);
}
self.base_clip().avm1_root(&self.context)
pub fn target_clip_or_root(&self) -> DisplayObject<'gc> {
self.target_clip()
.unwrap_or_else(|| self.base_clip().avm1_root())
}
/// Obtain the value of `_root`.
pub fn root_object(&self) -> Result<Value<'gc>, Error<'gc>> {
Ok(self.base_clip().avm1_root(&self.context)?.object())
pub fn root_object(&self) -> Value<'gc> {
self.base_clip().avm1_root().object()
}
/// Returns whether property keys should be case sensitive based on the current SWF version.
@ -2887,7 +2884,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn set_target(&mut self, target: &WStr) -> Result<FrameControl<'gc>, Error<'gc>> {
let base_clip = self.base_clip();
let new_target_clip;
let root = base_clip.avm1_root(&self.context)?;
let root = base_clip.avm1_root();
let start = base_clip.object().coerce_to_object(self);
if target.is_empty() {
new_target_clip = Some(base_clip);
@ -2925,7 +2922,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
self.set_target_clip(new_target_clip);
let scope = self.scope_cell();
let clip_obj = self.target_clip_or_root()?.object().coerce_to_object(self);
let clip_obj = self.target_clip_or_root().object().coerce_to_object(self);
self.set_scope(Scope::new_target_scope(
scope,

View File

@ -18,9 +18,6 @@ pub enum Error<'gc> {
#[error("Couldn't parse SWF")]
InvalidSwf(#[from] swf::error::Error),
#[error("Attempted to interact with a rootless display object in AVM1. Such objects can only be created in AS3, this is a runtime bug in Ruffle.")]
InvalidDisplayObjectHierarchy,
#[error("A script has thrown a custom error.")]
ThrownValue(Value<'gc>),
}

View File

@ -350,10 +350,7 @@ impl<'gc> Executable<'gc> {
}
if af.flags.contains(FunctionFlags::PRELOAD_ROOT) {
frame.set_local_register(
preload_r,
af.base_clip.avm1_root(&frame.context)?.object(),
);
frame.set_local_register(preload_r, af.base_clip.avm1_root().object());
preload_r += 1;
}

View File

@ -1149,7 +1149,7 @@ pub fn load_bitmap<'gc>(
let library = &*activation.context.library;
let movie = activation.target_clip_or_root()?.movie();
let movie = activation.target_clip_or_root().movie();
let renderer = &mut activation.context.renderer;

View File

@ -57,7 +57,7 @@ fn target<'gc>(
let target = this.get("target", activation)?;
// Undefined or empty target is no-op.
if target != Value::Undefined {
let start_clip = activation.target_clip_or_root()?;
let start_clip = activation.target_clip_or_root();
activation.resolve_target_display_object(start_clip, target, false)
} else {
Ok(None)

View File

@ -53,7 +53,7 @@ pub fn get_root<'gc>(
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
activation.root_object()
Ok(activation.root_object())
}
pub fn get_parent<'gc>(

View File

@ -134,7 +134,7 @@ pub fn hit_test<'gc>(
// The docs say the point is in "Stage coordinates", but actually they are in root coordinates.
// root can be moved via _root._x etc., so we actually have to transform from root to world space.
let point = movie_clip
.avm1_root(&activation.context)?
.avm1_root()
.local_to_global((Twips::from_pixels(x), Twips::from_pixels(y)));
let ret = if shape {
movie_clip.hit_test_shape(

View File

@ -46,7 +46,7 @@ fn load_clip<'gc>(
if let Value::String(url) = url {
let target = match target {
Value::String(_) => {
let start_clip = activation.target_clip_or_root()?;
let start_clip = activation.target_clip_or_root();
activation.resolve_target_display_object(start_clip, *target, true)?
}
Value::Number(level_id) => {
@ -90,7 +90,7 @@ fn unload_clip<'gc>(
if let [target, ..] = args {
let target = match target {
Value::String(_) => {
let start_clip = activation.target_clip_or_root()?;
let start_clip = activation.target_clip_or_root();
activation.resolve_target_display_object(start_clip, *target, true)?
}
Value::Number(level_id) => {
@ -123,7 +123,7 @@ fn get_progress<'gc>(
if let [target, ..] = args {
let target = match target {
Value::String(_) => {
let start_clip = activation.target_clip_or_root()?;
let start_clip = activation.target_clip_or_root();
activation.resolve_target_display_object(start_clip, *target, true)?
}
Value::Number(level_id) => {

View File

@ -39,7 +39,7 @@ pub fn constructor<'gc>(
// 1st parameter is the movie clip that "owns" all sounds started by this object.
// `Sound.setTransform`, `Sound.stop`, etc. will affect all sounds owned by this clip.
let owner = if let Some(owner) = args.get(0) {
let start_clip = activation.target_clip_or_root()?;
let start_clip = activation.target_clip_or_root();
activation.resolve_target_display_object(start_clip, *owner, false)?
} else {
None

View File

@ -1,6 +1,4 @@
use crate::avm1::{
Error as Avm1Error, Object as Avm1Object, TObject as Avm1TObject, Value as Avm1Value,
};
use crate::avm1::{Object as Avm1Object, TObject as Avm1TObject, Value as Avm1Value};
use crate::avm2::{
Activation as Avm2Activation, Avm2, Error as Avm2Error, Event as Avm2Event,
EventData as Avm2EventData, Namespace as Avm2Namespace, Object as Avm2Object,
@ -1314,55 +1312,22 @@ pub trait TDisplayObject<'gc>:
true
}
/// Obtain the top-most non-Stage parent of the display tree hierarchy, if
/// a suitable object exists. If none such object exists, this function
/// yields an AVM1 error (which shouldn't happen in normal usage).
/// Obtain the top-most non-Stage parent of the display tree hierarchy.
///
/// This function implements the AVM1 concept of root clips. For the AVM2
/// version, see `avm2_root`.
fn avm1_root(
&self,
context: &UpdateContext<'_, 'gc, '_>,
) -> Result<DisplayObject<'gc>, Avm1Error<'gc>> {
let mut parent = if self.lock_root() {
None
} else {
self.avm1_parent()
};
while let Some(p) = parent {
if p.lock_root() {
fn avm1_root(&self) -> DisplayObject<'gc> {
let mut root = (*self).into();
loop {
if root.lock_root() {
break;
}
let grandparent = p.avm1_parent();
if grandparent.is_none() {
break;
}
parent = grandparent;
root = match root.avm1_parent() {
Some(parent) => parent,
None => break,
};
}
parent
.ok_or(Avm1Error::InvalidDisplayObjectHierarchy)
.or_else(|_| {
if let Avm1Value::Object(object) = self.object() {
object
.as_display_object()
.ok_or(Avm1Error::InvalidDisplayObjectHierarchy)
} else if let Avm2Value::Object(object) = self.object2() {
if self.is_on_stage(context) {
object
.as_display_object()
.ok_or(Avm1Error::InvalidDisplayObjectHierarchy)
} else {
Err(Avm1Error::InvalidDisplayObjectHierarchy)
}
} else {
Err(Avm1Error::InvalidDisplayObjectHierarchy)
}
})
root
}
/// Obtain the top-most non-Stage parent of the display tree hierarchy, if