diff --git a/core/src/avm1.rs b/core/src/avm1.rs index 253bf88a7..a3830ec29 100644 --- a/core/src/avm1.rs +++ b/core/src/avm1.rs @@ -368,6 +368,11 @@ impl<'gc> Avm1<'gc> { !self.stack_frames.is_empty() } + /// Remove the current stack frame. + pub fn pop_stack_frame(&mut self) { + self.stack_frames.pop(); + } + /// Get the currently executing SWF version. pub fn current_swf_version(&self) -> u8 { self.current_stack_frame() @@ -1034,6 +1039,57 @@ impl<'gc> Avm1<'gc> { Ok(()) } + pub fn resolve_text_field_variable_path<'s>( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + path: &'s str, + ) -> Result, &'s str)>, Error<'gc>> { + // Resolve a variable path for a GetVariable action. + 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. + // We also check for a / to skip some unnecessary work later. + let mut has_slash = false; + let mut var_iter = path.as_bytes().rsplitn(2, |c| match c { + b':' | b'.' => true, + b'/' => { + has_slash = true; + false + } + _ => false, + }); + + let b = var_iter.next(); + let a = var_iter.next(); + if let (Some(path), Some(var_name)) = (a, b) { + // We have a . or :, so this is a path to an object plus a variable name. + // We resolve it directly on the targeted object. + let path = unsafe { std::str::from_utf8_unchecked(path) }; + let var_name = unsafe { std::str::from_utf8_unchecked(var_name) }; + + let mut current_scope = Some(self.current_stack_frame().unwrap().read().scope_cell()); + while let Some(scope) = current_scope { + if let Some(object) = + self.resolve_target_path(context, start.root(), *scope.read().locals(), path)? + { + return Ok(Some((object, var_name))); + } + current_scope = scope.read().parent_cell(); + } + + return Ok(None); + } + + // Finally! It's a plain old variable name. + // Resolve using scope chain, as normal. + if let Value::Object(object) = start.object() { + Ok(Some((object, path))) + } else { + Ok(None) + } + } + /// Resolve a level by ID. /// /// If the level does not exist, then it will be created and instantiated diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index 69244f2f6..fc8d09313 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -1,6 +1,6 @@ //! `EditText` display object and support code. use crate::avm1::globals::text_field::attach_virtual_properties; -use crate::avm1::{Avm1, Object, StageObject, Value}; +use crate::avm1::{Activation, Avm1, Object, StageObject, TObject, Value}; use crate::context::{RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, TDisplayObject}; use crate::drawing::Drawing; @@ -610,7 +610,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> { fn post_instantiation( &mut self, - _avm: &mut Avm1<'gc>, + avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, display_object: DisplayObject<'gc>, _init_object: Option>, @@ -642,6 +642,41 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> { for layout_box in text.layout.iter() { new_layout.push(layout_box.duplicate(context.gc_context)); } + + // If this text field has a variable set, initialize text field binding + if let Some(var_path) = self.variable() { + let var_path = (*var_path).to_string(); + avm.insert_stack_frame(GcCell::allocate( + context.gc_context, + Activation::from_nothing( + context.swf.header().version, + avm.global_object_cell(), + context.gc_context, + self.parent().unwrap(), + ), + )); + + if let Ok(Some((object, property))) = + avm.resolve_text_field_variable_path(context, &*var_path) + { + // If the property exists on the object, we overwrite the text with the property's value. + if object.has_property(avm, context, property) { + let value = object.get(property, avm, context).unwrap(); + let _ = self.set_text( + value + .coerce_to_string(avm, context) + .unwrap_or_default() + .into_owned(), + context, + ); + } else { + // Otherwise, we initialize the proprty with the text field's text. + let _ = object.set(property, self.text().clone().into(), avm, context); + } + } + + avm.pop_stack_frame(); + } } fn object(&self) -> Value<'gc> {