diff --git a/core/src/avm1/function.rs b/core/src/avm1/function.rs index 67d83517d..dbb572d4e 100644 --- a/core/src/avm1/function.rs +++ b/core/src/avm1/function.rs @@ -224,9 +224,57 @@ impl<'gc> Executable<'gc> { nf(activation, this, args) } Executable::Action(af) => { + let this_obj = match this { + Value::Object(obj) => Some(obj), + _ => None, + }; + + let target = activation.target_clip_or_root(); + let (swf_version, base_clip, parent_scope) = if activation.swf_version() >= 6 { + // Function calls in a v6+ SWF are proper closures, and "close" over the scope that defined the function: + // * Use the SWF version from the SWF that defined the function. + // * Use the base clip from when the function was defined. + // * Close over the scope from when the function was defined. + let base_clip = if !af.base_clip.removed() { + af.base_clip + } else { + this_obj + .and_then(|this| this.as_display_object()) + .unwrap_or(target) + }; + (af.swf_version(), base_clip, af.scope()) + } else { + // Function calls in a v5 SWF are *not* closures, and will use the settings of + // `this`, regardless of the function's origin: + // * Use the SWF version of `this`. + // * Use the base clip of `this`. + // * Allocate a new scope using the given base clip. No previous scope is closed over. + let base_clip = this_obj + .and_then(|this| this.as_display_object()) + .unwrap_or(target); + let swf_version = base_clip.swf_version(); + let base_clip_obj = match base_clip.object() { + Value::Object(o) => o, + _ => unreachable!(), + }; + // TODO: It would be nice to avoid these extra Scope allocs. + let scope = GcCell::allocate( + activation.context.gc_context, + Scope::new( + GcCell::allocate( + activation.context.gc_context, + Scope::from_global_object(activation.context.avm1.globals), + ), + super::scope::ScopeClass::Target, + base_clip_obj, + ), + ); + (swf_version, base_clip, scope) + }; + let child_scope = GcCell::allocate( activation.context.gc_context, - Scope::new_local_scope(af.scope(), activation.context.gc_context), + Scope::new_local_scope(parent_scope, activation.context.gc_context), ); let arguments = if af.flags.contains(FunctionFlags::SUPPRESS_ARGUMENTS) { @@ -252,11 +300,6 @@ impl<'gc> Executable<'gc> { Attribute::DONT_ENUM, ); - let this_obj = match this { - Value::Object(obj) => Some(obj), - _ => None, - }; - // TODO: `super` should only be defined if this was a method call (depth > 0?) // `f[""]()` emits a CallMethod op, causing `this` to be undefined, but `super` is a function; what is it? let super_object: Option> = this_obj.and_then(|this| { @@ -267,19 +310,6 @@ impl<'gc> Executable<'gc> { } }); - let effective_version = if activation.swf_version() > 5 { - if !af.base_clip.removed() { - af.base_clip.swf_version() - } else { - af.swf_version() - } - } else { - this_obj - .and_then(|this| this.as_display_object()) - .map(|dn| dn.swf_version()) - .unwrap_or(activation.context.player_version) - }; - let name = if cfg!(feature = "avm_debug") { let mut result = match af.name.map(ExecutionName::Dynamic).unwrap_or(name) { ExecutionName::Static(n) => n.to_owned(), @@ -301,17 +331,10 @@ impl<'gc> Executable<'gc> { }; let max_recursion_depth = activation.context.avm1.max_recursion_depth(); - let base_clip = if effective_version > 5 && !af.base_clip.removed() { - af.base_clip - } else { - this_obj - .and_then(|this| this.as_display_object()) - .unwrap_or_else(|| activation.base_clip()) - }; let mut frame = Activation::from_action( activation.context.reborrow(), activation.id.function(name, reason, max_recursion_depth)?, - effective_version, + swf_version, child_scope, af.constant_pool, base_clip,