avm1: Add allow_empty param to Activation:resolve_target_display_object

In some cases, the empty string path "" should resolve to the
starting clip. In other cases, it should be considered invalid.

Add a parameter to control this behavior, and set this to false
for MovieClip::hit_test to prevent `clip.hitTest("")` from
returning true.
This commit is contained in:
Mike Welsh 2020-11-18 14:10:17 -08:00
parent 4c01022a38
commit d3265bfd60
6 changed files with 29 additions and 11 deletions

View File

@ -681,7 +681,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let target = self.context.avm1.pop(); let target = self.context.avm1.pop();
let source = 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)?; 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()) { if let Some(movie_clip) = source_clip.and_then(|o| o.as_movie_clip()) {
let _ = globals::movie_clip::duplicate_movie_clip_with_bias( let _ = globals::movie_clip::duplicate_movie_clip_with_bias(
@ -1159,7 +1159,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let prop_index = self.context.avm1.pop().into_number_v1() as usize; let prop_index = self.context.avm1.pop().into_number_v1() as usize;
let path = self.context.avm1.pop(); let path = self.context.avm1.pop();
let ret = if let Some(target) = self.target_clip() { let ret = if let Some(target) = self.target_clip() {
if let Some(clip) = self.resolve_target_display_object(target, path)? { if let Some(clip) = self.resolve_target_display_object(target, path, true)? {
let display_properties = self.context.avm1.display_properties; let display_properties = self.context.avm1.display_properties;
let props = display_properties.write(self.context.gc_context); let props = display_properties.write(self.context.gc_context);
if let Some(property) = props.get_by_index(prop_index) { if let Some(property) = props.get_by_index(prop_index) {
@ -1264,7 +1264,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
target.as_display_object() target.as_display_object()
} else { } else {
let start = self.target_clip_or_root(); let start = self.target_clip_or_root();
self.resolve_target_display_object(start, target.clone())? self.resolve_target_display_object(start, target.clone(), true)?
} }
} else { } else {
Some(self.target_clip_or_root()) Some(self.target_clip_or_root())
@ -1794,7 +1794,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn action_remove_sprite(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> { fn action_remove_sprite(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
let target = self.context.avm1.pop(); 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)?; let target_clip = self.resolve_target_display_object(start_clip, target, true)?;
if let Some(target_clip) = target_clip.and_then(|o| o.as_movie_clip()) { if let Some(target_clip) = target_clip.and_then(|o| o.as_movie_clip()) {
let _ = globals::movie_clip::remove_movie_clip(target_clip, self, &[]); let _ = globals::movie_clip::remove_movie_clip(target_clip, self, &[]);
@ -1826,7 +1826,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let prop_index = self.context.avm1.pop().coerce_to_u32(self)? as usize; let prop_index = self.context.avm1.pop().coerce_to_u32(self)? as usize;
let path = self.context.avm1.pop(); let path = self.context.avm1.pop();
if let Some(target) = self.target_clip() { if let Some(target) = self.target_clip() {
if let Some(clip) = self.resolve_target_display_object(target, path)? { if let Some(clip) = self.resolve_target_display_object(target, path, true)? {
let display_properties = self.context.avm1.display_properties; let display_properties = self.context.avm1.display_properties;
let props = display_properties.read(); let props = display_properties.read();
if let Some(property) = props.get_by_index(prop_index) { if let Some(property) = props.get_by_index(prop_index) {
@ -1957,7 +1957,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn action_start_drag(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> { fn action_start_drag(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
let target = self.context.avm1.pop(); 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)?; let display_object = self.resolve_target_display_object(start_clip, target, true)?;
if let Some(display_object) = display_object { if let Some(display_object) = display_object {
let lock_center = self.context.avm1.pop(); let lock_center = self.context.avm1.pop();
let constrain = self.context.avm1.pop().as_bool(self.current_swf_version()); let constrain = self.context.avm1.pop().as_bool(self.current_swf_version());
@ -2406,10 +2406,13 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
/// ///
/// A target path always resolves via the display list. It can look /// A target path always resolves via the display list. It can look
/// at the prototype chain, but not the scope chain. /// at the prototype chain, but not the scope chain.
///
/// `allow_empty` will allow the empty string to resolve to the start movie clip.
pub fn resolve_target_display_object( pub fn resolve_target_display_object(
&mut self, &mut self,
start: DisplayObject<'gc>, start: DisplayObject<'gc>,
target: Value<'gc>, target: Value<'gc>,
allow_empty: bool,
) -> Result<Option<DisplayObject<'gc>>, Error<'gc>> { ) -> Result<Option<DisplayObject<'gc>>, Error<'gc>> {
// If the value you got was a display object, we can just toss it straight back. // If the value you got was a display object, we can just toss it straight back.
if let Value::Object(o) = target { if let Value::Object(o) = target {
@ -2422,6 +2425,11 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
// This means that values like `undefined` will resolve to clips with an instance name of // This means that values like `undefined` will resolve to clips with an instance name of
// `"undefined"`, for example. // `"undefined"`, for example.
let path = target.coerce_to_string(self)?; let path = target.coerce_to_string(self)?;
if !allow_empty && path.is_empty() {
return Ok(None);
}
let root = start.root(); let root = start.root();
let start = start.object().coerce_to_object(self); let start = start.object().coerce_to_object(self);
Ok(self Ok(self

View File

@ -82,9 +82,9 @@ fn target<'gc>(
// depending on which timeline its called from! // depending on which timeline its called from!
let target = this.get("target", activation)?; let target = this.get("target", activation)?;
// Undefined or empty target is no-op. // Undefined or empty target is no-op.
if target != Value::Undefined && !matches!(&target, &Value::String(ref s) if s.is_empty()) { 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) activation.resolve_target_display_object(start_clip, target, false)
} else { } else {
Ok(None) Ok(None)
} }

View File

@ -138,8 +138,11 @@ pub fn hit_test<'gc>(
return Ok(ret.into()); return Ok(ret.into());
} }
} else if args.len() == 1 { } else if args.len() == 1 {
let other = activation let other = activation.resolve_target_display_object(
.resolve_target_display_object(movie_clip.into(), args.get(0).unwrap().clone())?; movie_clip.into(),
args.get(0).unwrap().clone(),
false,
)?;
if let Some(other) = other { if let Some(other) = other {
return Ok(other return Ok(other
.world_bounds() .world_bounds()
@ -921,7 +924,9 @@ fn swap_depths<'gc>(
let mut depth = None; let mut depth = None;
if let Value::Number(n) = arg { if let Value::Number(n) = arg {
depth = Some(crate::ecma_conversions::f64_to_wrapping_i32(n).wrapping_add(AVM_DEPTH_BIAS)); depth = Some(crate::ecma_conversions::f64_to_wrapping_i32(n).wrapping_add(AVM_DEPTH_BIAS));
} else if let Some(target) = activation.resolve_target_display_object(movie_clip.into(), arg)? { } else if let Some(target) =
activation.resolve_target_display_object(movie_clip.into(), arg, false)?
{
if let Some(target_parent) = target.parent() { if let Some(target_parent) = target.parent() {
if DisplayObject::ptr_eq(target_parent, parent.into()) { if DisplayObject::ptr_eq(target_parent, parent.into()) {
depth = Some(target.depth()) depth = Some(target.depth())
@ -996,6 +1001,7 @@ fn get_bounds<'gc>(
activation.resolve_target_display_object( activation.resolve_target_display_object(
movie_clip.into(), movie_clip.into(),
AvmString::new(activation.context.gc_context, path.to_string()).into(), AvmString::new(activation.context.gc_context, path.to_string()).into(),
false,
)? )?
} }
None => Some(movie_clip.into()), None => Some(movie_clip.into()),

View File

@ -78,3 +78,7 @@ true
// circle.hitTest('/') // circle.hitTest('/')
true true
// String path, ''
// circle.hitTest('')
false