diff --git a/core/src/avm1.rs b/core/src/avm1.rs index 519433a2b..12712a2df 100644 --- a/core/src/avm1.rs +++ b/core/src/avm1.rs @@ -1,22 +1,12 @@ -use crate::avm1::function::{Avm1Function, FunctionObject}; use crate::avm1::globals::create_globals; use crate::avm1::object::search_prototype; -use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; -use crate::backend::navigator::{NavigationMethod, RequestOptions}; use crate::context::UpdateContext; use crate::prelude::*; -use enumset::EnumSet; use gc_arena::{GcCell, MutationContext}; -use rand::Rng; -use std::collections::HashMap; -use std::convert::TryInto; -use url::form_urlencoded; use swf::avm1::read::Reader; -use swf::avm1::types::{Action, Function}; -use crate::display_object::{DisplayObject, MovieClip}; +use crate::display_object::DisplayObject; use crate::tag_utils::SwfSlice; #[cfg(test)] @@ -26,7 +16,7 @@ mod test_utils; #[macro_use] pub mod listeners; -mod activation; +pub mod activation; pub mod debug; pub mod error; mod fscommand; @@ -34,7 +24,6 @@ pub mod function; pub mod globals; pub mod object; mod property; -mod return_value; mod scope; pub mod script_object; pub mod shared_object; @@ -50,10 +39,9 @@ pub mod xml_object; #[cfg(test)] mod tests; +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::listeners::SystemListener; -use crate::avm1::value::f64_to_wrapping_u32; -pub use activation::Activation; pub use globals::SystemPrototypes; pub use object::{Object, ObjectPtr, TObject}; use scope::Scope; @@ -90,9 +78,6 @@ pub struct Avm1<'gc> { /// DisplayObject property map. display_properties: GcCell<'gc, stage_object::DisplayPropertyMap<'gc>>, - /// All activation records for the current execution context. - stack_frames: Vec>>, - /// The operand stack (shared across functions). stack: Vec>, @@ -113,7 +98,6 @@ unsafe impl<'gc> gc_arena::Collect for Avm1<'gc> { self.system_listeners.trace(cc); self.prototypes.trace(cc); self.display_properties.trace(cc); - self.stack_frames.trace(cc); self.stack.trace(cc); for register in &self.registers { @@ -133,7 +117,6 @@ impl<'gc> Avm1<'gc> { prototypes, system_listeners, display_properties: stage_object::DisplayPropertyMap::new(gc_context), - stack_frames: vec![], stack: vec![], registers: [ Value::Undefined, @@ -145,148 +128,68 @@ impl<'gc> Avm1<'gc> { } } - #[allow(dead_code)] - pub fn base_clip(&self) -> DisplayObject<'gc> { - self.current_stack_frame().unwrap().read().base_clip() - } - - /// The current target clip for the executing code. - /// This is the movie clip that contains the bytecode. - /// Timeline actions like `GotoFrame` use this because - /// a goto after an invalid tellTarget has no effect. - pub fn target_clip(&self) -> Option> { - self.current_stack_frame().unwrap().read().target_clip() - } - - /// The current target clip of the executing code, or `root` if there is none. - /// 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) -> DisplayObject<'gc> { - self.current_stack_frame() - .unwrap() - .read() - .target_clip() - .unwrap_or_else(|| self.base_clip().root()) - } - - /// Convert the current locals pool into a set of form values. - /// - /// This is necessary to support form submission from Flash via a couple of - /// legacy methods, such as the `ActionGetURL2` opcode or `getURL` function. - /// - /// WARNING: This does not support user defined virtual properties! - pub fn locals_into_form_values( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> HashMap { - let mut form_values = HashMap::new(); - let stack_frame = self.current_stack_frame().unwrap(); - let stack_frame = stack_frame.read(); - let scope = stack_frame.scope(); - let locals = scope.locals(); - let keys = locals.get_keys(self); - - for k in keys { - let v = locals.get(&k, self, context); - - //TODO: What happens if an error occurs inside a virtual property? - form_values.insert( - k, - v.ok() - .unwrap_or_else(|| Value::Undefined) - .coerce_to_string(self, context) - .unwrap_or_else(|_| Cow::Borrowed("undefined")) - .to_string(), - ); - } - - form_values - } - - /// Construct request options for a fetch operation that may send locals as - /// form data in the request body or URL. - pub fn locals_into_request_options<'a>( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - url: Cow<'a, str>, - method: Option, - ) -> (Cow<'a, str>, RequestOptions) { - match method { - Some(method) => { - let vars = self.locals_into_form_values(context); - let qstring = form_urlencoded::Serializer::new(String::new()) - .extend_pairs(vars.iter()) - .finish(); - - match method { - NavigationMethod::GET if url.find('?').is_none() => ( - Cow::Owned(format!("{}?{}", url, qstring)), - RequestOptions::get(), - ), - NavigationMethod::GET => ( - Cow::Owned(format!("{}&{}", url, qstring)), - RequestOptions::get(), - ), - NavigationMethod::POST => ( - url, - RequestOptions::post(Some(( - qstring.as_bytes().to_owned(), - "application/x-www-form-urlencoded".to_string(), - ))), - ), - } - } - None => (url, RequestOptions::get()), - } - } - /// Add a stack frame that executes code in timeline scope - pub fn insert_stack_frame_for_action( + /// + /// This creates a new frame stack. + pub fn run_stack_frame_for_action( &mut self, active_clip: DisplayObject<'gc>, swf_version: u8, code: SwfSlice, - action_context: &mut UpdateContext<'_, 'gc, '_>, + context: &mut UpdateContext<'_, 'gc, '_>, ) { if self.halted { // We've been told to ignore all future execution. return; } - let global_scope = GcCell::allocate( - action_context.gc_context, - Scope::from_global_object(self.globals), + let mut parent_activation = Activation::from_nothing( + self, + swf_version, + self.global_object_cell(), + context.gc_context, + active_clip, ); - let clip_obj = active_clip.object().coerce_to_object(self, action_context); + + let clip_obj = active_clip + .object() + .coerce_to_object(&mut parent_activation, context); let child_scope = GcCell::allocate( - action_context.gc_context, - Scope::new(global_scope, scope::ScopeClass::Target, clip_obj), - ); - self.stack_frames.push(GcCell::allocate( - action_context.gc_context, - Activation::from_action( - swf_version, - code, - child_scope, - self.constant_pool, - active_clip, + context.gc_context, + Scope::new( + parent_activation.scope_cell(), + scope::ScopeClass::Target, clip_obj, - None, ), - )); + ); + let constant_pool = parent_activation.avm().constant_pool; + let mut child_activation = Activation::from_action( + parent_activation.avm(), + swf_version, + child_scope, + constant_pool, + active_clip, + clip_obj, + None, + ); + if let Err(e) = child_activation.run_actions(context, code) { + root_error_handler(&mut child_activation, context, e); + } } - /// Add a stack frame that executes code in timeline scope - pub fn insert_stack_frame_for_display_object( + /// Add a stack frame that executes code in initializer scope. + /// + /// This creates a new frame stack. + pub fn run_with_stack_frame_for_display_object<'a, F, R>( &mut self, active_clip: DisplayObject<'gc>, swf_version: u8, - action_context: &mut UpdateContext<'_, 'gc, '_>, - ) { - use crate::tag_utils::SwfMovie; - use std::sync::Arc; - + action_context: &mut UpdateContext<'a, 'gc, '_>, + function: F, + ) -> R + where + for<'b> F: FnOnce(&mut Activation<'b, 'gc>, &mut UpdateContext<'a, 'gc, '_>) -> R, + { let clip_obj = match active_clip.object() { Value::Object(o) => o, _ => panic!("No script object for display object"), @@ -299,64 +202,73 @@ impl<'gc> Avm1<'gc> { action_context.gc_context, Scope::new(global_scope, scope::ScopeClass::Target, clip_obj), ); - self.stack_frames.push(GcCell::allocate( - action_context.gc_context, - Activation::from_action( - swf_version, - SwfSlice { - movie: Arc::new(SwfMovie::empty(swf_version)), - start: 0, - end: 0, - }, - child_scope, - self.constant_pool, - active_clip, - clip_obj, - None, - ), - )); + let mut activation = Activation::from_action( + self, + swf_version, + child_scope, + self.constant_pool, + active_clip, + clip_obj, + None, + ); + function(&mut activation, action_context) } - /// Add a stack frame that executes code in initializer scope - pub fn insert_stack_frame_for_init_action( + /// Add a stack frame that executes code in initializer scope. + /// + /// This creates a new frame stack. + pub fn run_stack_frame_for_init_action( &mut self, active_clip: DisplayObject<'gc>, swf_version: u8, code: SwfSlice, - action_context: &mut UpdateContext<'_, 'gc, '_>, + context: &mut UpdateContext<'_, 'gc, '_>, ) { if self.halted { // We've been told to ignore all future execution. return; } - let global_scope = GcCell::allocate( - action_context.gc_context, - Scope::from_global_object(self.globals), + let mut parent_activation = Activation::from_nothing( + self, + swf_version, + self.global_object_cell(), + context.gc_context, + active_clip, ); - let clip_obj = active_clip.object().coerce_to_object(self, action_context); + + let clip_obj = active_clip + .object() + .coerce_to_object(&mut parent_activation, context); let child_scope = GcCell::allocate( - action_context.gc_context, - Scope::new(global_scope, scope::ScopeClass::Target, clip_obj), - ); - self.push(Value::Undefined); - self.stack_frames.push(GcCell::allocate( - action_context.gc_context, - Activation::from_action( - swf_version, - code, - child_scope, - self.constant_pool, - active_clip, + context.gc_context, + Scope::new( + parent_activation.scope_cell(), + scope::ScopeClass::Target, clip_obj, - None, ), - )); + ); + parent_activation.avm().push(Value::Undefined); + let constant_pool = parent_activation.avm().constant_pool; + let mut child_activation = Activation::from_action( + parent_activation.avm(), + swf_version, + child_scope, + constant_pool, + active_clip, + clip_obj, + None, + ); + if let Err(e) = child_activation.run_actions(context, code) { + root_error_handler(&mut child_activation, context, e); + } } /// Add a stack frame that executes code in timeline scope for an object /// method, such as an event handler. - pub fn insert_stack_frame_for_method( + /// + /// This creates a new frame stack. + pub fn run_stack_frame_for_method( &mut self, active_clip: DisplayObject<'gc>, obj: Object<'gc>, @@ -370,56 +282,22 @@ impl<'gc> Avm1<'gc> { return; } - // Grab the property with the given name. - // Requires a dummy stack frame. - self.stack_frames.push(GcCell::allocate( + let mut activation = Activation::from_nothing( + self, + swf_version, + self.global_object_cell(), context.gc_context, - Activation::from_nothing(swf_version, self.globals, context.gc_context, active_clip), - )); - let search_result = search_prototype(Some(obj), name, self, context, obj) - .and_then(|r| Ok((r.0.resolve(self, context)?, r.1))); - self.stack_frames.pop(); + active_clip, + ); + + let search_result = + search_prototype(Some(obj), name, &mut activation, context, obj).map(|r| (r.0, r.1)); - // Run the callback. - // The function exec pushes its own stack frame. - // The function is now ready to execute with `run_stack_till_empty`. if let Ok((callback, base_proto)) = search_result { - let _ = callback.call(self, context, obj, base_proto, args); + let _ = callback.call(&mut activation, context, obj, base_proto, args); } } - /// Add a stack frame for any arbitrary code. - pub fn insert_stack_frame(&mut self, frame: GcCell<'gc, Activation<'gc>>) { - self.stack_frames.push(frame); - } - - /// Retrieve the current AVM execution frame. - /// - /// Yields None if there is no stack frame. - pub fn current_stack_frame(&self) -> Option>> { - self.stack_frames.last().copied() - } - - /// Checks if there is currently a stack frame. - /// - /// This is an indicator if you are currently running from inside or outside the AVM. - /// This method is cheaper than `current_stack_frame` as it doesn't need to perform a copy. - pub fn has_stack_frame(&self) -> bool { - !self.stack_frames.is_empty() - } - - /// Get the currently executing SWF version. - pub fn current_swf_version(&self) -> u8 { - self.current_stack_frame() - .map(|sf| sf.read().swf_version()) - .unwrap_or(self.player_version) - } - - /// Returns whether property keys should be case sensitive based on the current SWF version. - pub fn is_case_sensitive(&self) -> bool { - is_swf_case_sensitive(self.current_swf_version()) - } - pub fn notify_system_listeners( &mut self, active_clip: DisplayObject<'gc>, @@ -429,336 +307,22 @@ impl<'gc> Avm1<'gc> { method: &str, args: &[Value<'gc>], ) { - // Push a dummy stack frame. - self.stack_frames.push(GcCell::allocate( + let mut activation = Activation::from_nothing( + self, + swf_version, + self.global_object_cell(), context.gc_context, - Activation::from_nothing(swf_version, self.globals, context.gc_context, active_clip), - )); - let listeners = self.system_listeners.get(listener); - let mut handlers = listeners.prepare_handlers(self, context, method); - self.stack_frames.pop(); + active_clip, + ); + + let listeners = activation.avm().system_listeners.get(listener); + let mut handlers = listeners.prepare_handlers(&mut activation, context, method); - // Each callback exec pushes its own stack frame. - // The functions are now ready to execute with `run_stack_till_empty`. for (listener, handler) in handlers.drain(..) { - let _ = handler.call(self, context, listener, None, &args); + let _ = handler.call(&mut activation, context, listener, None, &args); } } - /// Perform some action with the current stack frame's reader. - /// - /// This function constructs a reader based off the current stack frame's - /// reader. You are permitted to mutate the stack frame as you wish. If the - /// stack frame we started with still exists in the same location on the - /// stack, it's PC will be updated to the Reader's current PC. - /// - /// Stack frame identity (for the purpose of the above paragraph) is - /// determined by the data pointed to by the `SwfSlice` of a given frame. - /// - /// # Warnings - /// - /// It is incorrect to call this function multiple times in the same stack. - /// Doing so will result in any changes in duplicate readers being ignored. - /// Always pass the borrowed reader into functions that need it. - fn with_current_reader_mut( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - func: F, - ) -> Result> - where - F: FnOnce( - &mut Self, - &mut Reader<'_>, - &mut UpdateContext<'_, 'gc, '_>, - ) -> Result>, - { - let (frame_cell, swf_version, data, pc) = { - let frame = self.stack_frames.last().ok_or(Error::NoStackFrame)?; - let mut frame_ref = frame.write(context.gc_context); - frame_ref.lock()?; - - ( - *frame, - frame_ref.swf_version(), - frame_ref.data(), - frame_ref.pc(), - ) - }; - - let mut read = Reader::new(data.as_ref(), swf_version); - read.seek(pc.try_into().unwrap()); - - let r = func(self, &mut read, context); - - let mut frame_ref = frame_cell.write(context.gc_context); - frame_ref.unlock_execution(); - frame_ref.set_pc(read.pos()); - - if let Err(error) = &r { - if error.is_halting() { - self.halt(); - } - } - - r - } - - /// Destroy the current stack frame (if there is one). - /// - /// The given return value will be pushed on the stack if there is a - /// function to return it to. Otherwise, it will be discarded. - /// - /// NOTE: This means that if you are starting a brand new AVM stack just to - /// get it's return value, you won't get that value. Instead, retain a cell - /// referencing the oldest activation frame and use that to retrieve the - /// return value. - pub fn retire_stack_frame( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - return_value: Value<'gc>, - ) { - if let Some(frame) = self.current_stack_frame() { - self.stack_frames.pop(); - - let can_return = frame.read().can_return() && !self.stack_frames.is_empty(); - if can_return { - frame - .write(context.gc_context) - .set_return_value(return_value.clone()); - - self.push(return_value); - } - } - } - - /// Execute the AVM stack until it is exhausted. - pub fn run_stack_till_empty( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - if self.halted { - // We've been told to ignore all future execution. - return Ok(()); - } - while !self.stack_frames.is_empty() { - if let Err(e) = self.with_current_reader_mut(context, |this, r, context| { - this.do_next_action(context, r) - }) { - if let Error::ThrownValue(error) = &e { - let string = error - .coerce_to_string(self, context) - .unwrap_or_else(|_| Cow::Borrowed("undefined")); - log::info!(target: "avm_trace", "{}", string); - } - return Err(e); - } - } - - // Operand stack should be empty at this point. - // This is probably a bug on our part, - // although bytecode could in theory leave data on the stack. - if !self.stack.is_empty() { - log::warn!("Operand stack is not empty after execution"); - self.stack.clear(); - } - - Ok(()) - } - - /// Execute the AVM stack until a given activation returns. - pub fn run_current_frame( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - stop_frame: GcCell<'gc, Activation<'gc>>, - ) -> Result<(), Error<'gc>> { - if self.halted { - // We've been told to ignore all future execution. - return Ok(()); - } - - let mut stop_frame_id = None; - for (index, frame) in self.stack_frames.iter().enumerate() { - if GcCell::ptr_eq(stop_frame, *frame) { - stop_frame_id = Some(index); - } - } - - if let Some(stop_frame_id) = stop_frame_id { - while self - .stack_frames - .get(stop_frame_id) - .map(|fr| GcCell::ptr_eq(stop_frame, *fr)) - .unwrap_or(false) - { - self.with_current_reader_mut(context, |this, r, context| { - this.do_next_action(context, r) - })?; - } - - Ok(()) - } else { - self.halt(); - Err(Error::FrameNotOnStack) - } - } - - /// Run a single action from a given action reader. - fn do_next_action( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - reader: &mut Reader<'_>, - ) -> Result<(), Error<'gc>> { - if self.halted { - // We've been told to ignore all future execution. - return Ok(()); - } - let data = self.current_stack_frame().unwrap().read().data(); - - if reader.pos() >= (data.end - data.start) { - //Executing beyond the end of a function constitutes an implicit return. - self.retire_stack_frame(context, Value::Undefined); - } else if let Some(action) = reader.read_action()? { - avm_debug!("Action: {:?}", action); - - let result = match action { - Action::Add => self.action_add(context), - Action::Add2 => self.action_add_2(context), - Action::And => self.action_and(context), - Action::AsciiToChar => self.action_ascii_to_char(context), - Action::BitAnd => self.action_bit_and(context), - Action::BitLShift => self.action_bit_lshift(context), - Action::BitOr => self.action_bit_or(context), - Action::BitRShift => self.action_bit_rshift(context), - Action::BitURShift => self.action_bit_urshift(context), - Action::BitXor => self.action_bit_xor(context), - Action::Call => self.action_call(context), - Action::CallFunction => self.action_call_function(context), - Action::CallMethod => self.action_call_method(context), - Action::CastOp => self.action_cast_op(context), - Action::CharToAscii => self.action_char_to_ascii(context), - Action::CloneSprite => self.action_clone_sprite(context), - Action::ConstantPool(constant_pool) => { - self.action_constant_pool(context, &constant_pool[..]) - } - Action::Decrement => self.action_decrement(context), - Action::DefineFunction { - name, - params, - actions, - } => self.action_define_function(context, &name, ¶ms[..], actions), - Action::DefineFunction2(func) => self.action_define_function_2(context, &func), - Action::DefineLocal => self.action_define_local(context), - Action::DefineLocal2 => self.action_define_local_2(context), - Action::Delete => self.action_delete(context), - Action::Delete2 => self.action_delete_2(context), - Action::Divide => self.action_divide(context), - Action::EndDrag => self.action_end_drag(context), - Action::Enumerate => self.action_enumerate(context), - Action::Enumerate2 => self.action_enumerate_2(context), - Action::Equals => self.action_equals(context), - Action::Equals2 => self.action_equals_2(context), - Action::Extends => self.action_extends(context), - Action::GetMember => self.action_get_member(context), - Action::GetProperty => self.action_get_property(context), - Action::GetTime => self.action_get_time(context), - Action::GetVariable => self.action_get_variable(context), - Action::GetUrl { url, target } => self.action_get_url(context, &url, &target), - Action::GetUrl2 { - send_vars_method, - is_target_sprite, - is_load_vars, - } => { - self.action_get_url_2(context, send_vars_method, is_target_sprite, is_load_vars) - } - Action::GotoFrame(frame) => self.action_goto_frame(context, frame), - Action::GotoFrame2 { - set_playing, - scene_offset, - } => self.action_goto_frame_2(context, set_playing, scene_offset), - Action::Greater => self.action_greater(context), - Action::GotoLabel(label) => self.action_goto_label(context, &label), - Action::If { offset } => self.action_if(context, offset, reader), - Action::Increment => self.action_increment(context), - Action::InitArray => self.action_init_array(context), - Action::InitObject => self.action_init_object(context), - Action::ImplementsOp => self.action_implements_op(context), - Action::InstanceOf => self.action_instance_of(context), - Action::Jump { offset } => self.action_jump(context, offset, reader), - Action::Less => self.action_less(context), - Action::Less2 => self.action_less_2(context), - Action::MBAsciiToChar => self.action_mb_ascii_to_char(context), - Action::MBCharToAscii => self.action_mb_char_to_ascii(context), - Action::MBStringLength => self.action_mb_string_length(context), - Action::MBStringExtract => self.action_mb_string_extract(context), - Action::Modulo => self.action_modulo(context), - Action::Multiply => self.action_multiply(context), - Action::NextFrame => self.action_next_frame(context), - Action::NewMethod => self.action_new_method(context), - Action::NewObject => self.action_new_object(context), - Action::Not => self.action_not(context), - Action::Or => self.action_or(context), - Action::Play => self.action_play(context), - Action::Pop => self.action_pop(context), - Action::PreviousFrame => self.action_prev_frame(context), - Action::Push(values) => self.action_push(context, &values[..]), - Action::PushDuplicate => self.action_push_duplicate(context), - Action::RandomNumber => self.action_random_number(context), - Action::RemoveSprite => self.action_remove_sprite(context), - Action::Return => self.action_return(context), - Action::SetMember => self.action_set_member(context), - Action::SetProperty => self.action_set_property(context), - Action::SetTarget(target) => self.action_set_target(context, &target), - Action::SetTarget2 => self.action_set_target2(context), - Action::SetVariable => self.action_set_variable(context), - Action::StackSwap => self.action_stack_swap(context), - Action::StartDrag => self.action_start_drag(context), - Action::Stop => self.action_stop(context), - Action::StopSounds => self.action_stop_sounds(context), - Action::StoreRegister(register) => self.action_store_register(context, register), - Action::StrictEquals => self.action_strict_equals(context), - Action::StringAdd => self.action_string_add(context), - Action::StringEquals => self.action_string_equals(context), - Action::StringExtract => self.action_string_extract(context), - Action::StringGreater => self.action_string_greater(context), - Action::StringLength => self.action_string_length(context), - Action::StringLess => self.action_string_less(context), - Action::Subtract => self.action_subtract(context), - Action::TargetPath => self.action_target_path(context), - Action::ToggleQuality => self.toggle_quality(context), - Action::ToInteger => self.action_to_integer(context), - Action::ToNumber => self.action_to_number(context), - Action::ToString => self.action_to_string(context), - Action::Trace => self.action_trace(context), - Action::TypeOf => self.action_type_of(context), - Action::WaitForFrame { - frame, - num_actions_to_skip, - } => self.action_wait_for_frame(context, frame, num_actions_to_skip, reader), - Action::WaitForFrame2 { - num_actions_to_skip, - } => self.action_wait_for_frame_2(context, num_actions_to_skip, reader), - Action::With { actions } => self.action_with(context, actions), - Action::Throw => self.action_throw(context), - _ => self.unknown_op(context, action), - }; - if let Err(e) = result { - match &e { - Error::ThrownValue(_) => {} - e => log::error!("AVM1 error: {}", e), - } - if e.is_halting() { - self.halt(); - } - return Err(e); - } - } else { - //The explicit end opcode was encountered so return here - self.retire_stack_frame(context, Value::Undefined); - } - - Ok(()) - } - /// Halts the AVM, preventing execution of any further actions. /// /// If the AVM is currently evaluating an action, it will continue until it realizes that it has @@ -772,390 +336,6 @@ impl<'gc> Avm1<'gc> { } } - /// Resolves a target value to a display object, relative to a starting display object. - /// - /// This is used by any action/function with a parameter that can be either - /// a display object or a string path referencing the display object. - /// For example, `removeMovieClip(mc)` takes either a string or a display object. - /// - /// This can be an object, dot path, slash path, or weird combination thereof: - /// `_root/movieClip`, `movieClip.child._parent`, `movieClip:child`, etc. - /// See the `target_path` test for many examples. - /// - /// A target path always resolves via the display list. It can look - /// at the prototype chain, but not the scope chain. - pub fn resolve_target_display_object( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - start: DisplayObject<'gc>, - target: Value<'gc>, - ) -> Result>, Error<'gc>> { - // If the value you got was a display object, we can just toss it straight back. - if let Value::Object(o) = target { - if let Some(o) = o.as_display_object() { - return Ok(Some(o)); - } - } - - // Otherwise, we coerce it into a string and try to resolve it as a path. - // This means that values like `undefined` will resolve to clips with an instance name of - // `"undefined"`, for example. - let path = target.coerce_to_string(self, context)?; - let root = start.root(); - let start = start.object().coerce_to_object(self, context); - Ok(self - .resolve_target_path(context, root, start, &path)? - .and_then(|o| o.as_display_object())) - } - - /// Resolves a target path string to an object. - /// This only returns `Object`; other values will bail out with `None`. - /// - /// This can be a dot path, slash path, or weird combination thereof: - /// `_root/movieClip`, `movieClip.child._parent`, `movieClip:child`, etc. - /// See the `target_path` test for many examples. - /// - /// A target path always resolves via the display list. It can look - /// at the prototype chain, but not the scope chain. - pub fn resolve_target_path( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - root: DisplayObject<'gc>, - start: Object<'gc>, - path: &str, - ) -> Result>, Error<'gc>> { - // Empty path resolves immediately to start clip. - if path.is_empty() { - return Ok(Some(start)); - } - - // Starting / means an absolute path starting from root. - // (`/bar` means `_root.bar`) - let mut path = path.as_bytes(); - let (mut object, mut is_slash_path) = if path[0] == b'/' { - path = &path[1..]; - (root.object().coerce_to_object(self, context), true) - } else { - (start, false) - }; - - let case_sensitive = self.is_case_sensitive(); - - // Iterate through each token in the path. - while !path.is_empty() { - // Skip any number of leading : - // `foo`, `:foo`, and `:::foo` are all the same - while path.get(0) == Some(&b':') { - path = &path[1..]; - } - - let val = if let b".." | b"../" | b"..:" = &path[..std::cmp::min(path.len(), 3)] { - // Check for .. - // SWF-4 style _parent - if path.get(2) == Some(&b'/') { - is_slash_path = true; - } - path = path.get(3..).unwrap_or(&[]); - if let Some(parent) = object.as_display_object().and_then(|o| o.parent()) { - parent.object() - } else { - // Tried to get parent of root, bail out. - return Ok(None); - } - } else { - // Step until the next delimiter. - // : . / all act as path delimiters. - // The only restriction is that after a / appears, - // . is no longer considered a delimiter. - // TODO: SWF4 is probably more restrictive. - let mut pos = 0; - while pos < path.len() { - match path[pos] { - b':' => break, - b'.' if !is_slash_path => break, - b'/' => { - is_slash_path = true; - break; - } - _ => (), - } - pos += 1; - } - - // Slice out the identifier and step the cursor past the delimiter. - let ident = &path[..pos]; - path = path.get(pos + 1..).unwrap_or(&[]); - - // Guaranteed to be valid UTF-8. - let name = unsafe { std::str::from_utf8_unchecked(ident) }; - - // Get the value from the object. - // Resolves display object instances first, then local variables. - // This is the opposite of general GetMember property access! - if let Some(child) = object - .as_display_object() - .and_then(|o| o.get_child_by_name(name, case_sensitive)) - { - child.object() - } else { - object.get(&name, self, context).unwrap() - } - }; - - // Resolve the value to an object while traversing the path. - object = if let Value::Object(o) = val { - o - } else { - return Ok(None); - }; - } - - Ok(Some(object)) - } - - /// Gets the value referenced by a target path string. - /// - /// This can be a raw variable name, a slash path, a dot path, or weird combination thereof. - /// For example: - /// `_root/movieClip.foo`, `movieClip:child:_parent`, `blah` - /// See the `target_path` test for many examples. - /// - /// The string first tries to resolve as target path with a variable name, such as - /// "a/b/c:foo". The right-most : or . delimits the variable name, with the left side - /// identifying the target object path. Note that the variable name on the right can - /// contain a slash in this case. This path is resolved on the scope chain; if - /// the path does not resolve to an existing property on a scope, the parent scope is - /// searched. Undefined is returned if no path resolves successfully. - /// - /// If there is no variable name, but the path contains slashes, the path will still try - /// to resolve on the scope chain as above. If this fails to resolve, we consider - /// it a simple variable name and fall through to the variable case - /// (i.e. "a/b/c" would be a variable named "a/b/c", not a path). - /// - /// Finally, if none of the above applies, it is a normal variable name resovled via the - /// scope chain. - pub fn get_variable<'s>( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - path: &'s str, - ) -> Result, 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)? - { - if object.has_property(self, context, var_name) { - return Ok(object.get(var_name, self, context)?.into()); - } - } - current_scope = scope.read().parent_cell(); - } - - return Ok(Value::Undefined.into()); - } - - // If it doesn't have a trailing variable, it can still be a slash path. - // We can skip this step if we didn't find a slash above. - if has_slash { - 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(object.into()); - } - current_scope = scope.read().parent_cell(); - } - } - - // Finally! It's a plain old variable name. - // Resolve using scope chain, as normal. - self.current_stack_frame() - .unwrap() - .read() - .resolve(&path, self, context) - } - - /// Sets the value referenced by a target path string. - /// - /// This can be a raw variable name, a slash path, a dot path, or weird combination thereof. - /// For example: - /// `_root/movieClip.foo`, `movieClip:child:_parent`, `blah` - /// See the `target_path` test for many examples. - /// - /// The string first tries to resolve as target path with a variable name, such as - /// "a/b/c:foo". The right-most : or . delimits the variable name, with the left side - /// identifying the target object path. Note that the variable name on the right can - /// contain a slash in this case. This target path (sans variable) is resolved on the - /// scope chain; if the path does not resolve to an existing property on a scope, the - /// parent scope is searched. If the path does not resolve on any scope, the set fails - /// and returns immediately. If the path does resolve, the variable name is created - /// or overwritten on the target scope. - /// - /// This differs from `get_variable` because slash paths with no variable segment are invalid; - /// For example, `foo/bar` sets a property named `foo/bar` on the current stack frame instead - /// of drilling into the display list. - /// - /// If the string does not resolve as a path, the path is considered a normal variable - /// name and is set on the scope chain as usual. - pub fn set_variable<'s>( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - path: &'s str, - value: Value<'gc>, - ) -> Result<(), Error<'gc>> { - // Resolve a variable path for a GetVariable action. - 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() { - return Ok(()); - } - - // Find the right-most : or . in the path. - // If we have one, we must resolve as a target path. - let mut var_iter = path.as_bytes().rsplitn(2, |&c| c == b':' || c == b'.'); - 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)? - { - object.set(var_name, value, self, context)?; - return Ok(()); - } - current_scope = scope.read().parent_cell(); - } - - return Ok(()); - } - - // Finally! It's a plain old variable name. - // Set using scope chain, as normal. - // This will overwrite the value if the property exists somewhere - // in the scope chain, otherwise it is created on the top-level object. - let sf = self.current_stack_frame().unwrap(); - let stack_frame = sf.read(); - let this = stack_frame.this_cell(); - let scope = stack_frame.scope_cell(); - scope.read().set(path, value, self, context, this)?; - Ok(()) - } - - /// Resolves a path for text field variable binding. - /// Returns the parent object that owns the variable, and the variable name. - /// Returns `None` if the path does not yet point to a valid object. - /// TODO: This can probably be merged with some of the above `resolve_target_path` methods. - pub fn resolve_text_field_variable_path<'s>( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - text_field_parent: DisplayObject<'gc>, - path: &'s str, - ) -> Result, &'s str)>, Error<'gc>> { - // Resolve a variable path for a GetVariable action. - let start = text_field_parent; - - // 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 - /// with a script object. - pub fn resolve_level( - &mut self, - level_id: u32, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> DisplayObject<'gc> { - if let Some(level) = context.levels.get(&level_id) { - *level - } else { - let mut level: DisplayObject<'_> = MovieClip::new( - SwfSlice::empty(self.base_clip().movie().unwrap()), - context.gc_context, - ) - .into(); - - level.set_depth(context.gc_context, level_id as i32); - context.levels.insert(level_id, level); - level.post_instantiation(self, context, level, None, false); - - level - } - } - fn push(&mut self, value: impl Into>) { let value = value.into(); avm_debug!("Stack push {}: {:?}", self.stack.len(), value); @@ -1174,720 +354,6 @@ impl<'gc> Avm1<'gc> { value } - /// Retrieve a given register value. - /// - /// If a given register does not exist, this function yields - /// Value::Undefined, which is also a valid register value. - pub fn current_register(&self, id: u8) -> Value<'gc> { - if self - .current_stack_frame() - .map(|sf| sf.read().has_local_register(id)) - .unwrap_or(false) - { - self.current_stack_frame() - .unwrap() - .read() - .local_register(id) - .unwrap_or(Value::Undefined) - } else { - self.registers - .get(id as usize) - .cloned() - .unwrap_or(Value::Undefined) - } - } - - /// Set a register to a given value. - /// - /// If a given register does not exist, this function does nothing. - pub fn set_current_register( - &mut self, - id: u8, - value: Value<'gc>, - context: &mut UpdateContext<'_, 'gc, '_>, - ) { - if self - .current_stack_frame() - .map(|sf| sf.read().has_local_register(id)) - .unwrap_or(false) - { - self.current_stack_frame() - .unwrap() - .write(context.gc_context) - .set_local_register(id, value, context.gc_context); - } else if let Some(v) = self.registers.get_mut(id as usize) { - *v = value; - } - } - - fn unknown_op( - &mut self, - _context: &mut UpdateContext, - action: swf::avm1::types::Action, - ) -> Result<(), Error<'gc>> { - log::error!("Unknown AVM1 opcode: {:?}", action); - Ok(()) - } - - fn action_add(&mut self, _context: &mut UpdateContext) -> Result<(), Error<'gc>> { - let a = self.pop(); - let b = self.pop(); - self.push(b.into_number_v1() + a.into_number_v1()); - Ok(()) - } - - fn action_add_2(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error<'gc>> { - // ECMA-262 s. 11.6.1 - let a = self.pop(); - let b = self.pop(); - - // TODO(Herschel): - if let Value::String(a) = a { - let mut s = b.coerce_to_string(self, context)?.to_string(); - s.push_str(&a); - self.push(s); - } else if let Value::String(mut b) = b { - b.push_str(&a.coerce_to_string(self, context)?); - self.push(b); - } else { - let result = b.coerce_to_f64(self, context)? + a.coerce_to_f64(self, context)?; - self.push(result); - } - Ok(()) - } - - fn action_and(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error<'gc>> { - // AS1 logical and - let a = self.pop(); - let b = self.pop(); - let version = self.current_swf_version(); - let result = b.as_bool(version) && a.as_bool(version); - self.push(Value::from_bool(result, self.current_swf_version())); - Ok(()) - } - - fn action_ascii_to_char( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // TODO(Herschel): Results on incorrect operands? - let val = (self.pop().coerce_to_f64(self, context)? as u8) as char; - self.push(val.to_string()); - Ok(()) - } - - fn action_char_to_ascii( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // TODO(Herschel): Results on incorrect operands? - let val = self.pop(); - let string = val.coerce_to_string(self, context)?; - let result = string.bytes().next().unwrap_or(0); - self.push(result); - Ok(()) - } - - fn action_clone_sprite( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let depth = self.pop(); - let target = self.pop(); - let source = self.pop(); - let start_clip = self.target_clip_or_root(); - let source_clip = self.resolve_target_display_object(context, start_clip, source)?; - - if let Some(movie_clip) = source_clip.and_then(|o| o.as_movie_clip()) { - let _ = globals::movie_clip::duplicate_movie_clip_with_bias( - movie_clip, - self, - context, - &[target, depth], - 0, - ); - } else { - log::warn!("CloneSprite: Source is not a movie clip"); - } - - Ok(()) - } - - fn action_bit_and( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let a = self.pop().coerce_to_u32(self, context)?; - let b = self.pop().coerce_to_u32(self, context)?; - let result = a & b; - self.push(result); - Ok(()) - } - - fn action_bit_lshift( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let a = self.pop().coerce_to_i32(self, context)? & 0b11111; // Only 5 bits used for shift count - let b = self.pop().coerce_to_i32(self, context)?; - let result = b << a; - self.push(result); - Ok(()) - } - - fn action_bit_or( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let a = self.pop().coerce_to_u32(self, context)?; - let b = self.pop().coerce_to_u32(self, context)?; - let result = a | b; - self.push(result); - Ok(()) - } - - fn action_bit_rshift( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let a = self.pop().coerce_to_i32(self, context)? & 0b11111; // Only 5 bits used for shift count - let b = self.pop().coerce_to_i32(self, context)?; - let result = b >> a; - self.push(result); - Ok(()) - } - - fn action_bit_urshift( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let a = self.pop().coerce_to_u32(self, context)? & 0b11111; // Only 5 bits used for shift count - let b = self.pop().coerce_to_u32(self, context)?; - let result = b >> a; - self.push(result); - Ok(()) - } - - fn action_bit_xor( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let a = self.pop().coerce_to_u32(self, context)?; - let b = self.pop().coerce_to_u32(self, context)?; - let result = b ^ a; - self.push(result); - Ok(()) - } - - fn action_call(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error<'gc>> { - // Runs any actions on the given frame. - let frame = self.pop(); - let clip = self.target_clip_or_root(); - if let Some(clip) = clip.as_movie_clip() { - // Use frame # if parameter is a number, otherwise cast to string and check for frame labels. - let frame = if let Value::Number(frame) = frame { - let frame = f64_to_wrapping_u32(frame); - if frame >= 1 && frame <= u32::from(clip.total_frames()) { - Some(frame as u16) - } else { - None - } - } else { - let frame_label = frame.coerce_to_string(self, context)?; - clip.frame_label_to_number(&frame_label) - }; - - if let Some(frame) = frame { - // We must run the actions in the order that the tags appear, - // so we want to push the stack frames in reverse order. - for action in clip.actions_on_frame(context, frame).rev() { - self.insert_stack_frame_for_action( - self.target_clip_or_root(), - self.current_swf_version(), - action, - context, - ); - } - } else { - log::warn!("Call: Invalid frame {:?}", frame); - } - } else { - log::warn!("Call: Expected MovieClip"); - } - Ok(()) - } - - fn action_call_function( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let fn_name_value = self.pop(); - let fn_name = fn_name_value.coerce_to_string(self, context)?; - let mut args = Vec::new(); - let num_args = self.pop().coerce_to_f64(self, context)? as i64; // TODO(Herschel): max arg count? - for _ in 0..num_args { - args.push(self.pop()); - } - - let target_fn = self - .get_variable(context, &fn_name)? - .resolve(self, context)?; - - let this = self - .target_clip_or_root() - .object() - .coerce_to_object(self, context); - let result = target_fn.call(self, context, this, None, &args)?; - self.push(result); - - Ok(()) - } - - fn action_call_method( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let method_name = self.pop(); - let object_val = self.pop(); - let object = value_object::ValueObject::boxed(self, context, object_val); - let num_args = self.pop().coerce_to_f64(self, context)? as i64; // TODO(Herschel): max arg count? - let mut args = Vec::new(); - for _ in 0..num_args { - args.push(self.pop()); - } - - match method_name { - Value::Undefined | Value::Null => { - let this = self - .target_clip_or_root() - .object() - .coerce_to_object(self, context); - let result = object.call(self, context, this, None, &args)?; - self.push(result); - } - Value::String(name) => { - if name.is_empty() { - let result = object.call(self, context, object, None, &args)?; - self.push(result); - } else { - let result = object.call_method(&name, &args, self, context)?; - self.push(result); - } - } - _ => { - self.push(Value::Undefined); - log::warn!( - "Invalid method name, expected string but found {:?}", - method_name - ); - } - } - - Ok(()) - } - - fn action_cast_op( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let obj = self.pop().coerce_to_object(self, context); - let constr = self.pop().coerce_to_object(self, context); - - let prototype = constr - .get("prototype", self, context)? - .coerce_to_object(self, context); - - if obj.is_instance_of(self, context, constr, prototype)? { - self.push(obj); - } else { - self.push(Value::Null); - } - - Ok(()) - } - - fn action_constant_pool( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - constant_pool: &[&str], - ) -> Result<(), Error<'gc>> { - self.constant_pool = GcCell::allocate( - context.gc_context, - constant_pool.iter().map(|s| (*s).to_string()).collect(), - ); - self.current_stack_frame() - .unwrap() - .write(context.gc_context) - .set_constant_pool(self.constant_pool); - - Ok(()) - } - - fn action_decrement( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let a = self.pop().coerce_to_f64(self, context)?; - self.push(a - 1.0); - Ok(()) - } - - fn action_define_function( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - name: &str, - params: &[&str], - actions: &[u8], - ) -> Result<(), Error<'gc>> { - let swf_version = self.current_stack_frame().unwrap().read().swf_version(); - let func_data = self - .current_stack_frame() - .unwrap() - .read() - .data() - .to_subslice(actions) - .unwrap(); - let scope = Scope::new_closure_scope( - self.current_stack_frame().unwrap().read().scope_cell(), - context.gc_context, - ); - let constant_pool = self.current_stack_frame().unwrap().read().constant_pool(); - let func = Avm1Function::from_df1( - swf_version, - func_data, - name, - params, - scope, - constant_pool, - self.target_clip_or_root(), - ); - let prototype = - ScriptObject::object(context.gc_context, Some(self.prototypes.object)).into(); - let func_obj = FunctionObject::function( - context.gc_context, - func, - Some(self.prototypes.function), - Some(prototype), - ); - if name == "" { - self.push(func_obj); - } else { - self.current_stack_frame() - .unwrap() - .read() - .define(name, func_obj, context.gc_context); - } - - Ok(()) - } - - fn action_define_function_2( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - action_func: &Function, - ) -> Result<(), Error<'gc>> { - let swf_version = self.current_stack_frame().unwrap().read().swf_version(); - let func_data = self - .current_stack_frame() - .unwrap() - .read() - .data() - .to_subslice(action_func.actions) - .unwrap(); - let scope = Scope::new_closure_scope( - self.current_stack_frame().unwrap().read().scope_cell(), - context.gc_context, - ); - let constant_pool = self.current_stack_frame().unwrap().read().constant_pool(); - let func = Avm1Function::from_df2( - swf_version, - func_data, - action_func, - scope, - constant_pool, - self.base_clip(), - ); - let prototype = - ScriptObject::object(context.gc_context, Some(self.prototypes.object)).into(); - let func_obj = FunctionObject::function( - context.gc_context, - func, - Some(self.prototypes.function), - Some(prototype), - ); - if action_func.name == "" { - self.push(func_obj); - } else { - self.current_stack_frame().unwrap().read().define( - action_func.name, - func_obj, - context.gc_context, - ); - } - - Ok(()) - } - - fn action_define_local( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // If the property does not exist on the local object's prototype chain, it is created on the local object. - // Otherwise, the property is set (including calling virtual setters). - let value = self.pop(); - let name_val = self.pop(); - let name = name_val.coerce_to_string(self, context)?; - let stack_frame = self.current_stack_frame().unwrap(); - let stack_frame = stack_frame.read(); - let scope = stack_frame.scope(); - scope.locals().set(&name, value, self, context) - } - - fn action_define_local_2( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // If the property does not exist on the local object's prototype chain, it is created on the local object. - // Otherwise, the property is unchanged. - let name_val = self.pop(); - let name = name_val.coerce_to_string(self, context)?; - let stack_frame = self.current_stack_frame().unwrap(); - let stack_frame = stack_frame.read(); - let scope = stack_frame.scope(); - if !scope.locals().has_property(self, context, &name) { - scope.locals().set(&name, Value::Undefined, self, context)?; - } - Ok(()) - } - - fn action_delete( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let name_val = self.pop(); - let name = name_val.coerce_to_string(self, context)?; - let object = self.pop(); - - if let Value::Object(object) = object { - let success = object.delete(self, context.gc_context, &name); - self.push(success); - } else { - log::warn!("Cannot delete property {} from {:?}", name, object); - self.push(false); - } - - Ok(()) - } - - fn action_delete_2( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let name_val = self.pop(); - let name = name_val.coerce_to_string(self, context)?; - - //Fun fact: This isn't in the Adobe SWF19 spec, but this opcode returns - //a boolean based on if the delete actually deleted something. - let did_exist = self - .current_stack_frame() - .unwrap() - .read() - .is_defined(self, context, &name); - - self.current_stack_frame().unwrap().read().scope().delete( - self, - context, - &name, - context.gc_context, - ); - self.push(did_exist); - - Ok(()) - } - - fn action_divide( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // AS1 divide - let a = self.pop().coerce_to_f64(self, context)?; - let b = self.pop().coerce_to_f64(self, context)?; - - // TODO(Herschel): SWF19: "If A is zero, the result NaN, Infinity, or -Infinity is pushed to the in SWF 5 and later. - // In SWF 4, the result is the string #ERROR#."" - // Seems to be untrue for SWF v4, I get 1.#INF. - - self.push(b / a); - Ok(()) - } - - fn action_end_drag(&mut self, context: &mut UpdateContext) -> Result<(), Error<'gc>> { - *context.drag_object = None; - Ok(()) - } - - fn action_enumerate( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let name_value = self.pop(); - let name = name_value.coerce_to_string(self, context)?; - self.push(Value::Null); // Sentinel that indicates end of enumeration - let object = self - .current_stack_frame() - .unwrap() - .read() - .resolve(&name, self, context)? - .resolve(self, context)?; - - match object { - Value::Object(ob) => { - for k in ob.get_keys(self).into_iter().rev() { - self.push(k); - } - } - _ => log::error!("Cannot enumerate properties of {}", name), - }; - - Ok(()) - } - - fn action_enumerate_2( - &mut self, - _context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let value = self.pop(); - - self.push(Value::Null); // Sentinel that indicates end of enumeration - - if let Value::Object(object) = value { - for k in object.get_keys(self).into_iter().rev() { - self.push(k); - } - } else { - log::warn!("Cannot enumerate {:?}", value); - } - - Ok(()) - } - - #[allow(clippy::float_cmp)] - fn action_equals( - &mut self, - _context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // AS1 equality - let a = self.pop(); - let b = self.pop(); - let result = b.into_number_v1() == a.into_number_v1(); - self.push(Value::from_bool(result, self.current_swf_version())); - Ok(()) - } - - #[allow(clippy::float_cmp)] - fn action_equals_2( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // Version >=5 equality - let a = self.pop(); - let b = self.pop(); - let result = b.abstract_eq(a, self, context, false)?; - self.push(result); - Ok(()) - } - - fn action_extends( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let superclass = self.pop().coerce_to_object(self, context); - let subclass = self.pop().coerce_to_object(self, context); - - //TODO: What happens if we try to extend an object which has no `prototype`? - //e.g. `class Whatever extends Object.prototype` or `class Whatever extends 5` - let super_proto = superclass - .get("prototype", self, context)? - .coerce_to_object(self, context); - - let mut sub_prototype: Object<'gc> = - ScriptObject::object(context.gc_context, Some(super_proto)).into(); - - sub_prototype.set("constructor", superclass.into(), self, context)?; - sub_prototype.set_attributes( - context.gc_context, - Some("constructor"), - Attribute::DontEnum.into(), - EnumSet::empty(), - ); - - sub_prototype.set("__constructor__", superclass.into(), self, context)?; - sub_prototype.set_attributes( - context.gc_context, - Some("__constructor__"), - Attribute::DontEnum.into(), - EnumSet::empty(), - ); - - subclass.set("prototype", sub_prototype.into(), self, context)?; - - Ok(()) - } - - fn action_get_member( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let name_val = self.pop(); - let name = name_val.coerce_to_string(self, context)?; - let object_val = self.pop(); - let object = value_object::ValueObject::boxed(self, context, object_val); - - let result = object.get(&name, self, context)?; - self.push(result); - - Ok(()) - } - - fn action_get_property( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let prop_index = self.pop().into_number_v1() as usize; - let path = self.pop(); - let ret = if let Some(target) = self.target_clip() { - if let Some(clip) = self.resolve_target_display_object(context, target, path)? { - let display_properties = self.display_properties; - let props = display_properties.write(context.gc_context); - if let Some(property) = props.get_by_index(prop_index) { - property.get(self, context, clip)? - } else { - log::warn!("GetProperty: Invalid property index {}", prop_index); - Value::Undefined - } - } else { - //log::warn!("GetProperty: Invalid target {}", path); - Value::Undefined - } - } else { - log::warn!("GetProperty: Invalid base clip"); - Value::Undefined - }; - self.push(ret); - Ok(()) - } - - fn action_get_time(&mut self, context: &mut UpdateContext) -> Result<(), Error<'gc>> { - let time = context.navigator.time_since_launch().as_millis() as u32; - self.push(time); - Ok(()) - } - - /// Obtain the value of `_root`. - pub fn root_object(&self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Value<'gc> { - self.base_clip().root().object() - } - /// Obtain the value of `_global`. pub fn global_object(&self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Value<'gc> { Value::Object(self.globals) @@ -1902,1148 +368,21 @@ impl<'gc> Avm1<'gc> { pub fn prototypes(&self) -> &globals::SystemPrototypes<'gc> { &self.prototypes } - - fn action_get_variable( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let var_path = self.pop(); - let path = var_path.coerce_to_string(self, context)?; - - self.get_variable(context, &path)?.push(self); - - Ok(()) - } - - fn action_get_url( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - url: &str, - target: &str, - ) -> Result<(), Error<'gc>> { - if target.starts_with("_level") && target.len() > 6 { - let url = url.to_string(); - match target[6..].parse::() { - Ok(level_id) => { - let fetch = context.navigator.fetch(&url, RequestOptions::get()); - let level = self.resolve_level(level_id, context); - - let process = context.load_manager.load_movie_into_clip( - context.player.clone().unwrap(), - level, - fetch, - None, - ); - context.navigator.spawn_future(process); - } - Err(e) => log::warn!( - "Couldn't parse level id {} for action_get_url: {}", - target, - e - ), - } - - return Ok(()); - } - - if let Some(fscommand) = fscommand::parse(url) { - return fscommand::handle(fscommand, self, context); - } - - context - .navigator - .navigate_to_url(url.to_owned(), Some(target.to_owned()), None); - - Ok(()) - } - - fn action_get_url_2( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - swf_method: swf::avm1::types::SendVarsMethod, - is_target_sprite: bool, - is_load_vars: bool, - ) -> Result<(), Error<'gc>> { - // TODO: Support `LoadVariablesFlag`, `LoadTargetFlag` - // TODO: What happens if there's only one string? - let target = self.pop(); - let url_val = self.pop(); - let url = url_val.coerce_to_string(self, context)?; - - if let Some(fscommand) = fscommand::parse(&url) { - return fscommand::handle(fscommand, self, context); - } - - let window_target = target.coerce_to_string(self, context)?; - let clip_target: Option> = if is_target_sprite { - if let Value::Object(target) = target { - target.as_display_object() - } else { - let start = self.target_clip_or_root(); - self.resolve_target_display_object(context, start, target.clone())? - } - } else { - Some(self.target_clip_or_root()) - }; - - if is_load_vars { - if let Some(clip_target) = clip_target { - let target_obj = clip_target - .as_movie_clip() - .unwrap() - .object() - .coerce_to_object(self, context); - let (url, opts) = self.locals_into_request_options( - context, - url, - NavigationMethod::from_send_vars_method(swf_method), - ); - let fetch = context.navigator.fetch(&url, opts); - let process = context.load_manager.load_form_into_object( - context.player.clone().unwrap(), - target_obj, - fetch, - ); - - context.navigator.spawn_future(process); - } - - return Ok(()); - } else if is_target_sprite { - if let Some(clip_target) = clip_target { - let (url, opts) = self.locals_into_request_options( - context, - url, - NavigationMethod::from_send_vars_method(swf_method), - ); - let fetch = context.navigator.fetch(&url, opts); - let process = context.load_manager.load_movie_into_clip( - context.player.clone().unwrap(), - clip_target, - fetch, - None, - ); - context.navigator.spawn_future(process); - } - - return Ok(()); - } else { - let vars = match NavigationMethod::from_send_vars_method(swf_method) { - Some(method) => Some((method, self.locals_into_form_values(context))), - None => None, - }; - - context.navigator.navigate_to_url( - url.to_string(), - Some(window_target.to_string()), - vars, - ); - } - - Ok(()) - } - - fn action_goto_frame( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - frame: u16, - ) -> Result<(), Error<'gc>> { - if let Some(clip) = self.target_clip() { - if let Some(clip) = clip.as_movie_clip() { - // The frame on the stack is 0-based, not 1-based. - clip.goto_frame(self, context, frame + 1, true); - } else { - log::error!("GotoFrame failed: Target is not a MovieClip"); - } - } else { - log::error!("GotoFrame failed: Invalid target"); - } - Ok(()) - } - - fn action_goto_frame_2( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - set_playing: bool, - scene_offset: u16, - ) -> Result<(), Error<'gc>> { - // Version 4+ gotoAndPlay/gotoAndStop - // Param can either be a frame number or a frame label. - if let Some(clip) = self.target_clip() { - if let Some(clip) = clip.as_movie_clip() { - let frame = self.pop(); - let _ = globals::movie_clip::goto_frame( - clip, - self, - context, - &[frame], - !set_playing, - scene_offset, - ); - } else { - log::warn!("GotoFrame2: Target is not a MovieClip"); - } - } else { - log::warn!("GotoFrame2: Invalid target"); - } - Ok(()) - } - - fn action_goto_label( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - label: &str, - ) -> Result<(), Error<'gc>> { - if let Some(clip) = self.target_clip() { - if let Some(clip) = clip.as_movie_clip() { - if let Some(frame) = clip.frame_label_to_number(label) { - clip.goto_frame(self, context, frame, true); - } else { - log::warn!("GoToLabel: Frame label '{}' not found", label); - } - } else { - log::warn!("GoToLabel: Target is not a MovieClip"); - } - } else { - log::warn!("GoToLabel: Invalid target"); - } - Ok(()) - } - - fn action_if( - &mut self, - _context: &mut UpdateContext, - jump_offset: i16, - reader: &mut Reader<'_>, - ) -> Result<(), Error<'gc>> { - let val = self.pop(); - if val.as_bool(self.current_swf_version()) { - reader.seek(jump_offset.into()); - } - Ok(()) - } - - fn action_increment( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let a = self.pop().coerce_to_f64(self, context)?; - self.push(a + 1.0); - Ok(()) - } - - fn action_init_array( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let num_elements = self.pop().coerce_to_f64(self, context)? as i64; - let array = ScriptObject::array(context.gc_context, Some(self.prototypes.array)); - - for i in 0..num_elements { - array.set_array_element(i as usize, self.pop(), context.gc_context); - } - - self.push(Value::Object(array.into())); - Ok(()) - } - - fn action_init_object( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let num_props = self.pop().coerce_to_f64(self, context)? as i64; - let object = ScriptObject::object(context.gc_context, Some(self.prototypes.object)); - for _ in 0..num_props { - let value = self.pop(); - let name_val = self.pop(); - let name = name_val.coerce_to_string(self, context)?; - object.set(&name, value, self, context)?; - } - - self.push(Value::Object(object.into())); - - Ok(()) - } - - fn action_implements_op( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let constr = self.pop().coerce_to_object(self, context); - let count = self.pop().coerce_to_f64(self, context)? as i64; //TODO: Is this coercion actually performed by Flash? - let mut interfaces = vec![]; - - //TODO: If one of the interfaces is not an object, do we leave the - //whole stack dirty, or...? - for _ in 0..count { - interfaces.push(self.pop().coerce_to_object(self, context)); - } - - let mut prototype = constr - .get("prototype", self, context)? - .coerce_to_object(self, context); - - prototype.set_interfaces(context.gc_context, interfaces); - - Ok(()) - } - - fn action_instance_of( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let constr = self.pop().coerce_to_object(self, context); - let obj = self.pop().coerce_to_object(self, context); - - let prototype = constr - .get("prototype", self, context)? - .coerce_to_object(self, context); - let is_instance_of = obj.is_instance_of(self, context, constr, prototype)?; - - self.push(is_instance_of); - Ok(()) - } - - fn action_jump( - &mut self, - _context: &mut UpdateContext, - jump_offset: i16, - reader: &mut Reader<'_>, - ) -> Result<(), Error<'gc>> { - // TODO(Herschel): Handle out-of-bounds. - reader.seek(jump_offset.into()); - Ok(()) - } - - fn action_less(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error<'gc>> { - // AS1 less than - let a = self.pop(); - let b = self.pop(); - let result = b.into_number_v1() < a.into_number_v1(); - self.push(Value::from_bool(result, self.current_swf_version())); - Ok(()) - } - - fn action_less_2( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // ECMA-262 s. 11.8.1 - let a = self.pop(); - let b = self.pop(); - - let result = b.abstract_lt(a, self, context)?; - - self.push(result); - Ok(()) - } - - fn action_greater( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // ECMA-262 s. 11.8.2 - let a = self.pop(); - let b = self.pop(); - - let result = a.abstract_lt(b, self, context)?; - - self.push(result); - Ok(()) - } - - fn action_mb_ascii_to_char( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // TODO(Herschel): Results on incorrect operands? - use std::convert::TryFrom; - let result = char::try_from(self.pop().coerce_to_f64(self, context)? as u32); - match result { - Ok(val) => self.push(val.to_string()), - Err(e) => log::warn!("Couldn't parse char for action_mb_ascii_to_char: {}", e), - } - Ok(()) - } - - fn action_mb_char_to_ascii( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // TODO(Herschel): Results on incorrect operands? - let val = self.pop(); - let s = val.coerce_to_string(self, context)?; - let result = s.chars().next().unwrap_or('\0') as u32; - self.push(result); - Ok(()) - } - - fn action_mb_string_extract( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // TODO(Herschel): Result with incorrect operands? - let len = self.pop().coerce_to_f64(self, context)? as usize; - let start = self.pop().coerce_to_f64(self, context)? as usize; - let val = self.pop(); - let s = val.coerce_to_string(self, context)?; - let result = s[len..len + start].to_string(); // TODO(Herschel): Flash uses UTF-16 internally. - self.push(result); - Ok(()) - } - - fn action_mb_string_length( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // TODO(Herschel): Result with non-string operands? - let val = self.pop(); - let len = val.coerce_to_string(self, context)?.len(); - self.push(len as f64); - Ok(()) - } - - fn action_multiply( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let a = self.pop().coerce_to_f64(self, context)?; - let b = self.pop().coerce_to_f64(self, context)?; - self.push(a * b); - Ok(()) - } - - fn action_modulo( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // TODO: Wrong operands? - let a = self.pop().coerce_to_f64(self, context)?; - let b = self.pop().coerce_to_f64(self, context)?; - self.push(b % a); - Ok(()) - } - - fn action_not(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error<'gc>> { - let version = self.current_swf_version(); - let val = !self.pop().as_bool(version); - self.push(Value::from_bool(val, version)); - Ok(()) - } - - fn action_next_frame( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - if let Some(clip) = self.target_clip() { - if let Some(clip) = clip.as_movie_clip() { - clip.next_frame(self, context); - } else { - log::warn!("NextFrame: Target is not a MovieClip"); - } - } else { - log::warn!("NextFrame: Invalid target"); - } - Ok(()) - } - - fn action_new_method( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let method_name = self.pop(); - let object_val = self.pop(); - let num_args = self.pop().coerce_to_f64(self, context)? as i64; - let mut args = Vec::new(); - for _ in 0..num_args { - args.push(self.pop()); - } - log::info!("ASDASDASD"); - let object = value_object::ValueObject::boxed(self, context, object_val); - let constructor = - object.get(&method_name.coerce_to_string(self, context)?, self, context)?; - if let Value::Object(constructor) = constructor { - let prototype = constructor - .get("prototype", self, context)? - .coerce_to_object(self, context); - let mut this = prototype.new(self, context, prototype, &args)?; - this.set("__constructor__", constructor.into(), self, context)?; - this.set_attributes( - context.gc_context, - Some("__constructor__"), - Attribute::DontEnum.into(), - EnumSet::empty(), - ); - if self.current_swf_version() < 7 { - this.set("constructor", constructor.into(), self, context)?; - this.set_attributes( - context.gc_context, - Some("constructor"), - Attribute::DontEnum.into(), - EnumSet::empty(), - ); - } - - //TODO: What happens if you `ActionNewMethod` without a method name? - constructor.call(self, context, this, None, &args)?; - - self.push(this); - } else { - log::warn!( - "Tried to construct with non-object constructor {:?}", - constructor - ); - self.push(Value::Undefined); - } - - Ok(()) - } - - fn action_new_object( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let fn_name_val = self.pop(); - let fn_name = fn_name_val.coerce_to_string(self, context)?; - let num_args = self.pop().coerce_to_f64(self, context)? as i64; - let mut args = Vec::new(); - for _ in 0..num_args { - args.push(self.pop()); - } - - let constructor = self - .stack_frames - .last() - .unwrap() - .clone() - .read() - .resolve(&fn_name, self, context)? - .resolve(self, context)? - .coerce_to_object(self, context); - let prototype = constructor - .get("prototype", self, context)? - .coerce_to_object(self, context); - let mut this = prototype.new(self, context, prototype, &args)?; - - this.set("__constructor__", constructor.into(), self, context)?; - this.set_attributes( - context.gc_context, - Some("__constructor__"), - Attribute::DontEnum.into(), - EnumSet::empty(), - ); - if self.current_swf_version() < 7 { - this.set("constructor", constructor.into(), self, context)?; - this.set_attributes( - context.gc_context, - Some("constructor"), - Attribute::DontEnum.into(), - EnumSet::empty(), - ); - } - - constructor.call(self, context, this, None, &args)?; - - self.push(this); - - Ok(()) - } - - fn action_or(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error<'gc>> { - // AS1 logical or - let a = self.pop(); - let b = self.pop(); - let version = self.current_swf_version(); - let result = b.as_bool(version) || a.as_bool(version); - self.push(Value::from_bool(result, version)); - Ok(()) - } - - fn action_play(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error<'gc>> { - if let Some(clip) = self.target_clip() { - if let Some(clip) = clip.as_movie_clip() { - clip.play(context) - } else { - log::warn!("Play: Target is not a MovieClip"); - } - } else { - log::warn!("Play: Invalid target"); - } - Ok(()) - } - - fn action_prev_frame( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - if let Some(clip) = self.target_clip() { - if let Some(clip) = clip.as_movie_clip() { - clip.prev_frame(self, context); - } else { - log::warn!("PrevFrame: Target is not a MovieClip"); - } - } else { - log::warn!("PrevFrame: Invalid target"); - } - Ok(()) - } - - fn action_pop(&mut self, _context: &mut UpdateContext) -> Result<(), Error<'gc>> { - self.pop(); - Ok(()) - } - - fn action_push( - &mut self, - _context: &mut UpdateContext, - values: &[swf::avm1::types::Value], - ) -> Result<(), Error<'gc>> { - for value in values { - use swf::avm1::types::Value as SwfValue; - let value = match value { - SwfValue::Undefined => Value::Undefined, - SwfValue::Null => Value::Null, - SwfValue::Bool(v) => Value::Bool(*v), - SwfValue::Int(v) => f64::from(*v).into(), - SwfValue::Float(v) => f64::from(*v).into(), - SwfValue::Double(v) => (*v).into(), - SwfValue::Str(v) => (*v).to_string().into(), - SwfValue::Register(v) => self.current_register(*v), - SwfValue::ConstantPool(i) => { - if let Some(value) = self - .current_stack_frame() - .unwrap() - .read() - .constant_pool() - .read() - .get(*i as usize) - { - value.to_string().into() - } else { - log::warn!( - "ActionPush: Constant pool index {} out of range (len = {})", - i, - self.current_stack_frame() - .unwrap() - .read() - .constant_pool() - .read() - .len() - ); - Value::Undefined - } - } - }; - self.push(value); - } - Ok(()) - } - - fn action_push_duplicate(&mut self, _context: &mut UpdateContext) -> Result<(), Error<'gc>> { - let val = self.pop(); - self.push(val.clone()); - self.push(val); - Ok(()) - } - - fn action_random_number(&mut self, context: &mut UpdateContext) -> Result<(), Error<'gc>> { - // A max value < 0 will always return 0, - // and the max value gets converted into an i32, so any number > 2^31 - 1 will return 0. - let max = self.pop().into_number_v1() as i32; - let val = if max > 0 { - context.rng.gen_range(0, max) - } else { - 0 - }; - self.push(val); - Ok(()) - } - - fn action_remove_sprite( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let target = self.pop(); - let start_clip = self.target_clip_or_root(); - let target_clip = self.resolve_target_display_object(context, start_clip, target)?; - - if let Some(target_clip) = target_clip.and_then(|o| o.as_movie_clip()) { - let _ = globals::movie_clip::remove_movie_clip_with_bias(target_clip, context, 0); - } else { - log::warn!("RemoveSprite: Source is not a movie clip"); - } - Ok(()) - } - - fn action_return( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let return_value = self.pop(); - self.retire_stack_frame(context, return_value); - - Ok(()) - } - - fn action_set_member( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let value = self.pop(); - let name_val = self.pop(); - let name = name_val.coerce_to_string(self, context)?; - - let object = self.pop().coerce_to_object(self, context); - object.set(&name, value, self, context)?; - - Ok(()) - } - - fn action_set_property( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let value = self.pop(); - let prop_index = self.pop().coerce_to_u32(self, context)? as usize; - let path = self.pop(); - if let Some(target) = self.target_clip() { - if let Some(clip) = self.resolve_target_display_object(context, target, path)? { - let display_properties = self.display_properties; - let props = display_properties.read(); - if let Some(property) = props.get_by_index(prop_index) { - property.set(self, context, clip, value)?; - } - } else { - log::warn!("SetProperty: Invalid target"); - } - } else { - log::warn!("SetProperty: Invalid base clip"); - } - Ok(()) - } - - fn action_set_variable( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // Flash 4-style variable - let value = self.pop(); - let var_path_val = self.pop(); - let var_path = var_path_val.coerce_to_string(self, context)?; - self.set_variable(context, &var_path, value) - } - - #[allow(clippy::float_cmp)] - fn action_strict_equals(&mut self, _context: &mut UpdateContext) -> Result<(), Error<'gc>> { - // The same as normal equality but types must match - let a = self.pop(); - let b = self.pop(); - let result = a == b; - self.push(result); - Ok(()) - } - - fn action_set_target( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - target: &str, - ) -> Result<(), Error<'gc>> { - let base_clip = self.base_clip(); - let new_target_clip; - let root = base_clip.root(); - let start = base_clip.object().coerce_to_object(self, context); - if target.is_empty() { - new_target_clip = Some(base_clip); - } else if let Some(clip) = self - .resolve_target_path(context, root, start, target)? - .and_then(|o| o.as_display_object()) - { - new_target_clip = Some(clip); - } else { - log::warn!("SetTarget failed: {} not found", target); - // TODO: Emulate AVM1 trace error message. - log::info!(target: "avm_trace", "Target not found: Target=\"{}\" Base=\"{}\"", target, base_clip.path()); - - // When SetTarget has an invalid target, subsequent GetVariables act - // as if they are targeting root, but subsequent Play/Stop/etc. - // fail silenty. - new_target_clip = None; - } - - let stack_frame = self.current_stack_frame().unwrap(); - let mut sf = stack_frame.write(context.gc_context); - sf.set_target_clip(new_target_clip); - - let scope = sf.scope_cell(); - let clip_obj = sf - .target_clip() - .unwrap_or_else(|| sf.base_clip().root()) - .object() - .coerce_to_object(self, context); - - sf.set_scope(Scope::new_target_scope(scope, clip_obj, context.gc_context)); - Ok(()) - } - - fn action_set_target2( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let target = self.pop(); - match target { - Value::String(target) => { - return self.action_set_target(context, &target); - } - Value::Undefined => { - // Reset - let stack_frame = self.current_stack_frame().unwrap(); - let mut sf = stack_frame.write(context.gc_context); - let base_clip = sf.base_clip(); - sf.set_target_clip(Some(base_clip)); - } - Value::Object(o) => { - if let Some(clip) = o.as_display_object() { - let stack_frame = self.current_stack_frame().unwrap(); - let mut sf = stack_frame.write(context.gc_context); - // Movieclips can be targetted directly - sf.set_target_clip(Some(clip)); - } else { - // Other objects get coerced to string - let target = target.coerce_to_string(self, context)?; - return self.action_set_target(context, &target); - } - } - _ => { - let target = target.coerce_to_string(self, context)?; - return self.action_set_target(context, &target); - } - }; - - let stack_frame = self.current_stack_frame().unwrap(); - let mut sf = stack_frame.write(context.gc_context); - let scope = sf.scope_cell(); - let clip_obj = sf - .target_clip() - .unwrap_or_else(|| sf.base_clip().root()) - .object() - .coerce_to_object(self, context); - sf.set_scope(Scope::new_target_scope(scope, clip_obj, context.gc_context)); - Ok(()) - } - - fn action_stack_swap(&mut self, _context: &mut UpdateContext) -> Result<(), Error<'gc>> { - let a = self.pop(); - let b = self.pop(); - self.push(a); - self.push(b); - Ok(()) - } - - fn action_start_drag( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let target = self.pop(); - let start_clip = self.target_clip_or_root(); - let display_object = self.resolve_target_display_object(context, start_clip, target)?; - if let Some(display_object) = display_object { - let lock_center = self.pop(); - let constrain = self.pop().as_bool(self.current_swf_version()); - if constrain { - let y2 = self.pop(); - let x2 = self.pop(); - let y1 = self.pop(); - let x1 = self.pop(); - start_drag( - display_object, - self, - context, - &[lock_center, x1, y1, x2, y2], - ); - } else { - start_drag(display_object, self, context, &[lock_center]); - }; - } else { - log::warn!("StartDrag: Invalid target"); - } - Ok(()) - } - - fn action_stop(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error<'gc>> { - if let Some(clip) = self.target_clip() { - if let Some(clip) = clip.as_movie_clip() { - clip.stop(context); - } else { - log::warn!("Stop: Target is not a MovieClip"); - } - } else { - log::warn!("Stop: Invalid target"); - } - Ok(()) - } - - fn action_stop_sounds(&mut self, context: &mut UpdateContext) -> Result<(), Error<'gc>> { - context.audio.stop_all_sounds(); - Ok(()) - } - - fn action_store_register( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - register: u8, - ) -> Result<(), Error<'gc>> { - // The value must remain on the stack. - let val = self.pop(); - self.push(val.clone()); - self.set_current_register(register, val, context); - - Ok(()) - } - - fn action_string_add( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // SWFv4 string concatenation - // TODO(Herschel): Result with non-string operands? - let a = self.pop(); - let mut b = self.pop().coerce_to_string(self, context)?.to_string(); - b.push_str(&a.coerce_to_string(self, context)?); - self.push(b); - Ok(()) - } - - fn action_string_equals( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // AS1 strcmp - let a = self.pop(); - let b = self.pop(); - let result = b.coerce_to_string(self, context)? == a.coerce_to_string(self, context)?; - self.push(Value::from_bool(result, self.current_swf_version())); - Ok(()) - } - - fn action_string_extract( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // SWFv4 substring - // TODO(Herschel): Result with incorrect operands? - let len = self.pop().coerce_to_f64(self, context)? as usize; - let start = self.pop().coerce_to_f64(self, context)? as usize; - let val = self.pop(); - let s = val.coerce_to_string(self, context)?; - // This is specifically a non-UTF8 aware substring. - // SWFv4 only used ANSI strings. - let result = s - .bytes() - .skip(start) - .take(len) - .map(|c| c as char) - .collect::(); - self.push(result); - Ok(()) - } - - fn action_string_greater( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // AS1 strcmp - let a = self.pop(); - let b = self.pop(); - // This is specifically a non-UTF8 aware comparison. - let result = b - .coerce_to_string(self, context)? - .bytes() - .gt(a.coerce_to_string(self, context)?.bytes()); - self.push(Value::from_bool(result, self.current_swf_version())); - Ok(()) - } - - fn action_string_length( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // AS1 strlen - // Only returns byte length. - // TODO(Herschel): Result with non-string operands? - let val = self.pop(); - let len = val.coerce_to_string(self, context)?.bytes().len() as f64; - self.push(len); - Ok(()) - } - - fn action_string_less( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // AS1 strcmp - let a = self.pop(); - let b = self.pop(); - // This is specifically a non-UTF8 aware comparison. - let result = b - .coerce_to_string(self, context)? - .bytes() - .lt(a.coerce_to_string(self, context)?.bytes()); - self.push(Value::from_bool(result, self.current_swf_version())); - Ok(()) - } - - fn action_subtract( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let a = self.pop().coerce_to_f64(self, context)?; - let b = self.pop().coerce_to_f64(self, context)?; - self.push(b - a); - Ok(()) - } - - fn action_target_path( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - // TODO(Herschel) - let _clip = self.pop().coerce_to_object(self, context); - self.push(Value::Undefined); - log::warn!("Unimplemented action: TargetPath"); - Ok(()) - } - - fn toggle_quality(&mut self, _context: &mut UpdateContext) -> Result<(), Error<'gc>> { - // TODO(Herschel): Noop for now? Could chang anti-aliasing on render backend. - Ok(()) - } - - fn action_to_integer( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let val = self.pop().coerce_to_f64(self, context)?; - self.push(val.trunc()); - Ok(()) - } - - fn action_to_number( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let val = self.pop().coerce_to_f64(self, context)?; - self.push(val); - Ok(()) - } - - fn action_to_string( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result<(), Error<'gc>> { - let val = self.pop(); - let string = val.coerce_to_string(self, context)?; - self.push(string); - Ok(()) - } - - fn action_trace(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error<'gc>> { - let val = self.pop(); - // trace always prints "undefined" even though SWF6 and below normally - // coerce undefined to "". - let out = if val == Value::Undefined { - Cow::Borrowed("undefined") - } else { - val.coerce_to_string(self, context)? - }; - log::info!(target: "avm_trace", "{}", out); - Ok(()) - } - - fn action_type_of(&mut self, _context: &mut UpdateContext) -> Result<(), Error<'gc>> { - let type_of = self.pop().type_of(); - self.push(type_of); - Ok(()) - } - - fn action_wait_for_frame( - &mut self, - _context: &mut UpdateContext, - _frame: u16, - num_actions_to_skip: u8, - r: &mut Reader<'_>, - ) -> Result<(), Error<'gc>> { - // TODO(Herschel): Always true for now. - let loaded = true; - if !loaded { - // Note that the offset is given in # of actions, NOT in bytes. - // Read the actions and toss them away. - skip_actions(r, num_actions_to_skip); - } - Ok(()) - } - - fn action_wait_for_frame_2( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - num_actions_to_skip: u8, - r: &mut Reader<'_>, - ) -> Result<(), Error<'gc>> { - // TODO(Herschel): Always true for now. - let _frame_num = self.pop().coerce_to_f64(self, context)? as u16; - let loaded = true; - if !loaded { - // Note that the offset is given in # of actions, NOT in bytes. - // Read the actions and toss them away. - skip_actions(r, num_actions_to_skip); - } - Ok(()) - } - - fn action_throw(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error<'gc>> { - let value = self.pop(); - avm_debug!( - "Thrown exception: {}", - value - .coerce_to_string(self, context) - .unwrap_or_else(|_| Cow::Borrowed("undefined")) - ); - self.retire_stack_frame(context, Value::Undefined); - Err(Error::ThrownValue(value)) - } - - fn action_with( - &mut self, - context: &mut UpdateContext<'_, 'gc, '_>, - actions: &[u8], - ) -> Result<(), Error<'gc>> { - let object = self.pop().coerce_to_object(self, context); - let block = self - .current_stack_frame() - .unwrap() - .read() - .data() - .to_subslice(actions) - .unwrap(); - let with_scope = Scope::new_with_scope( - self.current_stack_frame().unwrap().read().scope_cell(), - object, - context.gc_context, - ); - let new_activation = self - .current_stack_frame() - .unwrap() - .read() - .to_rescope(block, with_scope); - self.stack_frames - .push(GcCell::allocate(context.gc_context, new_activation)); - Ok(()) - } } -/// Returns whether the given SWF version is case-sensitive. -/// SWFv7 and above is case-sensitive. -pub fn is_swf_case_sensitive(swf_version: u8) -> bool { - swf_version > 6 +pub fn root_error_handler<'gc>( + activation: &mut Activation<'_, 'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + error: Error<'gc>, +) { + if let Error::ThrownValue(error) = error { + let string = error + .coerce_to_string(activation, context) + .unwrap_or_else(|_| Cow::Borrowed("undefined")); + log::info!(target: "avm_trace", "{}", string); + } else { + log::error!("Uncaught error: {:?}", error); + } } /// Utility function used by `Avm1::action_wait_for_frame` and @@ -3060,7 +399,7 @@ fn skip_actions(reader: &mut Reader<'_>, num_actions_to_skip: u8) { /// Runs via the `startDrag` method or `StartDrag` AVM1 action. pub fn start_drag<'gc>( display_object: DisplayObject<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], ) { @@ -3087,28 +426,28 @@ pub fn start_drag<'gc>( let mut x_min = args .get(1) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context) + .coerce_to_f64(activation, context) .map(|n| if n.is_finite() { n } else { 0.0 }) .map(Twips::from_pixels) .unwrap_or_default(); let mut y_min = args .get(2) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context) + .coerce_to_f64(activation, context) .map(|n| if n.is_finite() { n } else { 0.0 }) .map(Twips::from_pixels) .unwrap_or_default(); let mut x_max = args .get(3) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context) + .coerce_to_f64(activation, context) .map(|n| if n.is_finite() { n } else { 0.0 }) .map(Twips::from_pixels) .unwrap_or_default(); let mut y_max = args .get(4) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context) + .coerce_to_f64(activation, context) .map(|n| if n.is_finite() { n } else { 0.0 }) .map(Twips::from_pixels) .unwrap_or_default(); diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index c2d724593..a773432ed 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -1,16 +1,33 @@ -//! Activation records - use crate::avm1::error::Error; -use crate::avm1::return_value::ReturnValue; +use crate::avm1::function::{Avm1Function, FunctionObject}; +use crate::avm1::object::{Object, TObject}; +use crate::avm1::property::Attribute; use crate::avm1::scope::Scope; -use crate::avm1::{Avm1, Object, Value}; +use crate::avm1::value::f64_to_wrapping_u32; +use crate::avm1::{ + fscommand, globals, scope, skip_actions, start_drag, value_object, Avm1, ScriptObject, Value, +}; +use crate::backend::navigator::{NavigationMethod, RequestOptions}; use crate::context::UpdateContext; -use crate::display_object::DisplayObject; +use crate::display_object::{DisplayObject, MovieClip, TDisplayObject}; use crate::tag_utils::SwfSlice; -use gc_arena::{GcCell, MutationContext}; +use enumset::EnumSet; +use gc_arena::{Collect, GcCell, MutationContext}; +use rand::Rng; use smallvec::SmallVec; +use std::borrow::Cow; use std::cell::{Ref, RefMut}; -use std::sync::Arc; +use std::collections::HashMap; +use swf::avm1::read::Reader; +use swf::avm1::types::{Action, Function}; +use url::form_urlencoded; + +macro_rules! avm_debug { + ($($arg:tt)*) => ( + #[cfg(feature = "avm_debug")] + log::debug!($($arg)*) + ) +} /// Represents a particular register set. /// @@ -50,8 +67,32 @@ impl<'gc> RegisterSet<'gc> { } } -/// Represents a single activation of a given AVM1 function or keyframe. -pub struct Activation<'gc> { +#[derive(Debug, Clone)] +pub enum ReturnType<'gc> { + Implicit, + Explicit(Value<'gc>), +} + +impl<'gc> ReturnType<'gc> { + pub fn value(self) -> Value<'gc> { + match self { + ReturnType::Implicit => Value::Undefined, + ReturnType::Explicit(value) => value, + } + } +} + +#[derive(Debug, Clone)] +enum FrameControl<'gc> { + Continue, + Return(ReturnType<'gc>), +} + +#[derive(Collect)] +#[collect(no_drop)] +pub struct Activation<'a, 'gc: 'a> { + avm: &'a mut Avm1<'gc>, + /// Represents the SWF version of a given function. /// /// Certain AVM1 operations change behavior based on the version of the SWF @@ -59,12 +100,6 @@ pub struct Activation<'gc> { /// on the SWF version. swf_version: u8, - /// Action data being executed by the reader below. - data: SwfSlice, - - /// The current location of the instruction stream being executed. - pc: usize, - /// All defined local variables in this stack frame. scope: GcCell<'gc, Scope<'gc>>, @@ -75,14 +110,7 @@ pub struct Activation<'gc> { this: Object<'gc>, /// The arguments this function was called by. - arguments: Option>, - - /// The return value of the activation. - return_value: Option>, - - /// Indicates if this activation object represents a function or embedded - /// block (e.g. ActionWith). - is_function: bool, + pub arguments: Option>, /// Local registers, if any. /// @@ -111,69 +139,42 @@ pub struct Activation<'gc> { target_clip: Option>, } -unsafe impl<'gc> gc_arena::Collect for Activation<'gc> { - #[inline] - fn trace(&self, cc: gc_arena::CollectionContext) { - self.scope.trace(cc); - self.constant_pool.trace(cc); - self.this.trace(cc); - self.arguments.trace(cc); - self.return_value.trace(cc); - self.local_registers.trace(cc); - self.base_clip.trace(cc); - self.target_clip.trace(cc); - } -} - -impl<'gc> Activation<'gc> { +impl<'a, 'gc: 'a> Activation<'a, 'gc> { pub fn from_action( + avm: &'a mut Avm1<'gc>, swf_version: u8, - code: SwfSlice, scope: GcCell<'gc, Scope<'gc>>, constant_pool: GcCell<'gc, Vec>, base_clip: DisplayObject<'gc>, this: Object<'gc>, arguments: Option>, - ) -> Activation<'gc> { - Activation { + ) -> Self { + Self { + avm, swf_version, - data: code, - pc: 0, scope, constant_pool, base_clip, target_clip: Some(base_clip), this, arguments, - return_value: None, - is_function: false, local_registers: None, is_executing: false, } } - pub fn from_function( - swf_version: u8, - code: SwfSlice, - scope: GcCell<'gc, Scope<'gc>>, - constant_pool: GcCell<'gc, Vec>, - base_clip: DisplayObject<'gc>, - this: Object<'gc>, - arguments: Option>, - ) -> Activation<'gc> { + /// Create a new activation to run a block of code with a given scope. + pub fn with_new_scope<'b>(&'b mut self, scope: GcCell<'gc, Scope<'gc>>) -> Activation<'b, 'gc> { Activation { - swf_version, - data: code, - pc: 0, + avm: self.avm, + swf_version: self.swf_version, scope, - constant_pool, - base_clip, - target_clip: Some(base_clip), - this, - arguments, - return_value: None, - is_function: true, - local_registers: None, + constant_pool: self.constant_pool, + base_clip: self.base_clip, + target_clip: self.target_clip, + this: self.this, + arguments: self.arguments, + local_registers: self.local_registers, is_executing: false, } } @@ -183,89 +184,2615 @@ impl<'gc> Activation<'gc> { /// This is used by tests and by callback methods (`onEnterFrame`) to create a base /// activation frame with access to the global context. pub fn from_nothing( + avm: &'a mut Avm1<'gc>, swf_version: u8, globals: Object<'gc>, mc: MutationContext<'gc, '_>, base_clip: DisplayObject<'gc>, - ) -> Activation<'gc> { - use crate::tag_utils::SwfMovie; - + ) -> Self { let global_scope = GcCell::allocate(mc, Scope::from_global_object(globals)); let child_scope = GcCell::allocate(mc, Scope::new_local_scope(global_scope, mc)); let empty_constant_pool = GcCell::allocate(mc, Vec::new()); - Activation { + Self { + avm, swf_version, - data: SwfSlice { - movie: Arc::new(SwfMovie::empty(swf_version)), - start: 0, - end: 0, - }, - pc: 0, scope: child_scope, constant_pool: empty_constant_pool, base_clip, target_clip: Some(base_clip), this: globals, arguments: None, - return_value: None, - is_function: false, local_registers: None, is_executing: false, } } - /// Create a new activation to run a block of code with a given scope. - pub fn to_rescope(&self, code: SwfSlice, scope: GcCell<'gc, Scope<'gc>>) -> Self { - Activation { - swf_version: self.swf_version, - data: code, - pc: 0, - scope, - constant_pool: self.constant_pool, - base_clip: self.base_clip, - target_clip: self.target_clip, - this: self.this, - arguments: self.arguments, - return_value: None, - is_function: false, - local_registers: self.local_registers, - is_executing: false, + /// Add a stack frame that executes code in timeline scope + pub fn run_child_frame_for_action( + &mut self, + active_clip: DisplayObject<'gc>, + swf_version: u8, + code: SwfSlice, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let mut parent_activation = Activation::from_nothing( + self.avm, + swf_version, + self.avm.globals, + context.gc_context, + active_clip, + ); + let clip_obj = active_clip + .object() + .coerce_to_object(&mut parent_activation, context); + let child_scope = GcCell::allocate( + context.gc_context, + Scope::new( + parent_activation.scope_cell(), + scope::ScopeClass::Target, + clip_obj, + ), + ); + let constant_pool = parent_activation.avm().constant_pool; + let mut child_activation = Activation::from_action( + parent_activation.avm(), + swf_version, + child_scope, + constant_pool, + active_clip, + clip_obj, + None, + ); + child_activation.run_actions(context, code) + } + + /// Add a stack frame that executes code in initializer scope. + pub fn run_with_child_frame_for_display_object<'c, F, R>( + &mut self, + active_clip: DisplayObject<'gc>, + swf_version: u8, + action_context: &mut UpdateContext<'c, 'gc, '_>, + function: F, + ) -> R + where + for<'b> F: FnOnce(&mut Activation<'b, 'gc>, &mut UpdateContext<'c, 'gc, '_>) -> R, + { + let clip_obj = match active_clip.object() { + Value::Object(o) => o, + _ => panic!("No script object for display object"), + }; + let global_scope = GcCell::allocate( + action_context.gc_context, + Scope::from_global_object(self.avm.globals), + ); + let child_scope = GcCell::allocate( + action_context.gc_context, + Scope::new(global_scope, scope::ScopeClass::Target, clip_obj), + ); + let constant_pool = self.avm.constant_pool; + let mut activation = Activation::from_action( + self.avm(), + swf_version, + child_scope, + constant_pool, + active_clip, + clip_obj, + None, + ); + function(&mut activation, action_context) + } + + pub fn run_actions( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + code: SwfSlice, + ) -> Result, Error<'gc>> { + let mut read = Reader::new(code.as_ref(), self.swf_version()); + + loop { + let result = self.do_action(&code, context, &mut read); + match result { + Ok(FrameControl::Return(return_type)) => break Ok(return_type), + Ok(FrameControl::Continue) => {} + Err(e) => break Err(e), + } } } + /// Run a single action from a given action reader. + fn do_action( + &mut self, + data: &SwfSlice, + context: &mut UpdateContext<'_, 'gc, '_>, + reader: &mut Reader<'_>, + ) -> Result, Error<'gc>> { + if reader.pos() >= (data.end - data.start) { + //Executing beyond the end of a function constitutes an implicit return. + Ok(FrameControl::Return(ReturnType::Implicit)) + } else if let Some(action) = reader.read_action()? { + avm_debug!("Action: {:?}", action); + + let result = match action { + Action::Add => self.action_add(context), + Action::Add2 => self.action_add_2(context), + Action::And => self.action_and(context), + Action::AsciiToChar => self.action_ascii_to_char(context), + Action::BitAnd => self.action_bit_and(context), + Action::BitLShift => self.action_bit_lshift(context), + Action::BitOr => self.action_bit_or(context), + Action::BitRShift => self.action_bit_rshift(context), + Action::BitURShift => self.action_bit_urshift(context), + Action::BitXor => self.action_bit_xor(context), + Action::Call => self.action_call(context), + Action::CallFunction => self.action_call_function(context), + Action::CallMethod => self.action_call_method(context), + Action::CastOp => self.action_cast_op(context), + Action::CharToAscii => self.action_char_to_ascii(context), + Action::CloneSprite => self.action_clone_sprite(context), + Action::ConstantPool(constant_pool) => { + self.action_constant_pool(context, &constant_pool[..]) + } + Action::Decrement => self.action_decrement(context), + Action::DefineFunction { + name, + params, + actions, + } => self.action_define_function( + context, + &name, + ¶ms[..], + data.to_subslice(actions).unwrap(), + ), + Action::DefineFunction2(func) => { + self.action_define_function_2(context, &func, &data) + } + Action::DefineLocal => self.action_define_local(context), + Action::DefineLocal2 => self.action_define_local_2(context), + Action::Delete => self.action_delete(context), + Action::Delete2 => self.action_delete_2(context), + Action::Divide => self.action_divide(context), + Action::EndDrag => self.action_end_drag(context), + Action::Enumerate => self.action_enumerate(context), + Action::Enumerate2 => self.action_enumerate_2(context), + Action::Equals => self.action_equals(context), + Action::Equals2 => self.action_equals_2(context), + Action::Extends => self.action_extends(context), + Action::GetMember => self.action_get_member(context), + Action::GetProperty => self.action_get_property(context), + Action::GetTime => self.action_get_time(context), + Action::GetVariable => self.action_get_variable(context), + Action::GetUrl { url, target } => self.action_get_url(context, &url, &target), + Action::GetUrl2 { + send_vars_method, + is_target_sprite, + is_load_vars, + } => { + self.action_get_url_2(context, send_vars_method, is_target_sprite, is_load_vars) + } + Action::GotoFrame(frame) => self.action_goto_frame(context, frame), + Action::GotoFrame2 { + set_playing, + scene_offset, + } => self.action_goto_frame_2(context, set_playing, scene_offset), + Action::Greater => self.action_greater(context), + Action::GotoLabel(label) => self.action_goto_label(context, &label), + Action::If { offset } => self.action_if(context, offset, reader), + Action::Increment => self.action_increment(context), + Action::InitArray => self.action_init_array(context), + Action::InitObject => self.action_init_object(context), + Action::ImplementsOp => self.action_implements_op(context), + Action::InstanceOf => self.action_instance_of(context), + Action::Jump { offset } => self.action_jump(context, offset, reader), + Action::Less => self.action_less(context), + Action::Less2 => self.action_less_2(context), + Action::MBAsciiToChar => self.action_mb_ascii_to_char(context), + Action::MBCharToAscii => self.action_mb_char_to_ascii(context), + Action::MBStringLength => self.action_mb_string_length(context), + Action::MBStringExtract => self.action_mb_string_extract(context), + Action::Modulo => self.action_modulo(context), + Action::Multiply => self.action_multiply(context), + Action::NextFrame => self.action_next_frame(context), + Action::NewMethod => self.action_new_method(context), + Action::NewObject => self.action_new_object(context), + Action::Not => self.action_not(context), + Action::Or => self.action_or(context), + Action::Play => self.action_play(context), + Action::Pop => self.action_pop(context), + Action::PreviousFrame => self.action_prev_frame(context), + Action::Push(values) => self.action_push(context, &values[..]), + Action::PushDuplicate => self.action_push_duplicate(context), + Action::RandomNumber => self.action_random_number(context), + Action::RemoveSprite => self.action_remove_sprite(context), + Action::Return => self.action_return(), + Action::SetMember => self.action_set_member(context), + Action::SetProperty => self.action_set_property(context), + Action::SetTarget(target) => self.action_set_target(context, &target), + Action::SetTarget2 => self.action_set_target2(context), + Action::SetVariable => self.action_set_variable(context), + Action::StackSwap => self.action_stack_swap(context), + Action::StartDrag => self.action_start_drag(context), + Action::Stop => self.action_stop(context), + Action::StopSounds => self.action_stop_sounds(context), + Action::StoreRegister(register) => self.action_store_register(context, register), + Action::StrictEquals => self.action_strict_equals(context), + Action::StringAdd => self.action_string_add(context), + Action::StringEquals => self.action_string_equals(context), + Action::StringExtract => self.action_string_extract(context), + Action::StringGreater => self.action_string_greater(context), + Action::StringLength => self.action_string_length(context), + Action::StringLess => self.action_string_less(context), + Action::Subtract => self.action_subtract(context), + Action::TargetPath => self.action_target_path(context), + Action::ToggleQuality => self.toggle_quality(context), + Action::ToInteger => self.action_to_integer(context), + Action::ToNumber => self.action_to_number(context), + Action::ToString => self.action_to_string(context), + Action::Trace => self.action_trace(context), + Action::TypeOf => self.action_type_of(context), + Action::WaitForFrame { + frame, + num_actions_to_skip, + } => self.action_wait_for_frame(context, frame, num_actions_to_skip, reader), + Action::WaitForFrame2 { + num_actions_to_skip, + } => self.action_wait_for_frame_2(context, num_actions_to_skip, reader), + Action::With { actions } => { + self.action_with(context, data.to_subslice(actions).unwrap()) + } + Action::Throw => self.action_throw(context), + _ => self.unknown_op(context, action), + }; + if let Err(e) = result { + match &e { + Error::ThrownValue(_) => {} + e => log::error!("AVM1 error: {}", e), + } + if e.is_halting() { + self.avm.halt(); + } + return Err(e); + } + result + } else { + //The explicit end opcode was encountered so return here + Ok(FrameControl::Return(ReturnType::Implicit)) + } + } + + fn unknown_op( + &mut self, + _context: &mut UpdateContext, + action: swf::avm1::types::Action, + ) -> Result, Error<'gc>> { + log::error!("Unknown AVM1 opcode: {:?}", action); + Ok(FrameControl::Continue) + } + + fn action_add( + &mut self, + _context: &mut UpdateContext, + ) -> Result, Error<'gc>> { + let a = self.avm.pop(); + let b = self.avm.pop(); + self.avm.push(b.into_number_v1() + a.into_number_v1()); + Ok(FrameControl::Continue) + } + + fn action_add_2( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // ECMA-262 s. 11.6.1 + let a = self.avm.pop(); + let b = self.avm.pop(); + + // TODO(Herschel): + if let Value::String(a) = a { + let mut s = b.coerce_to_string(self, context)?.to_string(); + s.push_str(&a); + self.avm.push(s); + } else if let Value::String(mut b) = b { + b.push_str(&a.coerce_to_string(self, context)?); + self.avm.push(b); + } else { + let result = b.coerce_to_f64(self, context)? + a.coerce_to_f64(self, context)?; + self.avm.push(result); + } + Ok(FrameControl::Continue) + } + + fn action_and( + &mut self, + _context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // AS1 logical and + let a = self.avm.pop(); + let b = self.avm.pop(); + let version = self.current_swf_version(); + let result = b.as_bool(version) && a.as_bool(version); + self.avm + .push(Value::from_bool(result, self.current_swf_version())); + Ok(FrameControl::Continue) + } + + fn action_ascii_to_char( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // TODO(Herschel): Results on incorrect operands? + let val = (self.avm.pop().coerce_to_f64(self, context)? as u8) as char; + self.avm.push(val.to_string()); + Ok(FrameControl::Continue) + } + + fn action_char_to_ascii( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // TODO(Herschel): Results on incorrect operands? + let val = self.avm.pop(); + let string = val.coerce_to_string(self, context)?; + let result = string.bytes().next().unwrap_or(0); + self.avm.push(result); + Ok(FrameControl::Continue) + } + + fn action_clone_sprite( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let depth = self.avm.pop(); + let target = self.avm.pop(); + let source = self.avm.pop(); + let start_clip = self.target_clip_or_root(); + let source_clip = self.resolve_target_display_object(context, start_clip, source)?; + + if let Some(movie_clip) = source_clip.and_then(|o| o.as_movie_clip()) { + let _ = globals::movie_clip::duplicate_movie_clip_with_bias( + movie_clip, + self, + context, + &[target, depth], + 0, + ); + } else { + log::warn!("CloneSprite: Source is not a movie clip"); + } + + Ok(FrameControl::Continue) + } + + fn action_bit_and( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let a = self.avm.pop().coerce_to_u32(self, context)?; + let b = self.avm.pop().coerce_to_u32(self, context)?; + let result = a & b; + self.avm.push(result); + Ok(FrameControl::Continue) + } + + fn action_bit_lshift( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let a = self.avm.pop().coerce_to_i32(self, context)? & 0b11111; // Only 5 bits used for shift count + let b = self.avm.pop().coerce_to_i32(self, context)?; + let result = b << a; + self.avm.push(result); + Ok(FrameControl::Continue) + } + + fn action_bit_or( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let a = self.avm.pop().coerce_to_u32(self, context)?; + let b = self.avm.pop().coerce_to_u32(self, context)?; + let result = a | b; + self.avm.push(result); + Ok(FrameControl::Continue) + } + + fn action_bit_rshift( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let a = self.avm.pop().coerce_to_i32(self, context)? & 0b11111; // Only 5 bits used for shift count + let b = self.avm.pop().coerce_to_i32(self, context)?; + let result = b >> a; + self.avm.push(result); + Ok(FrameControl::Continue) + } + + fn action_bit_urshift( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let a = self.avm.pop().coerce_to_u32(self, context)? & 0b11111; // Only 5 bits used for shift count + let b = self.avm.pop().coerce_to_u32(self, context)?; + let result = b >> a; + self.avm.push(result); + Ok(FrameControl::Continue) + } + + fn action_bit_xor( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let a = self.avm.pop().coerce_to_u32(self, context)?; + let b = self.avm.pop().coerce_to_u32(self, context)?; + let result = b ^ a; + self.avm.push(result); + Ok(FrameControl::Continue) + } + + fn action_call( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // Runs any actions on the given frame. + let frame = self.avm.pop(); + let clip = self.target_clip_or_root(); + if let Some(clip) = clip.as_movie_clip() { + // Use frame # if parameter is a number, otherwise cast to string and check for frame labels. + let frame = if let Value::Number(frame) = frame { + let frame = f64_to_wrapping_u32(frame); + if frame >= 1 && frame <= u32::from(clip.total_frames()) { + Some(frame as u16) + } else { + None + } + } else { + let frame_label = frame.coerce_to_string(self, context)?; + clip.frame_label_to_number(&frame_label) + }; + + if let Some(frame) = frame { + for action in clip.actions_on_frame(context, frame) { + let _ = self.run_child_frame_for_action( + self.target_clip_or_root(), + self.current_swf_version(), + action, + context, + )?; + } + } else { + log::warn!("Call: Invalid frame {:?}", frame); + } + } else { + log::warn!("Call: Expected MovieClip"); + } + Ok(FrameControl::Continue) + } + + fn action_call_function( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let fn_name_value = self.avm.pop(); + let fn_name = fn_name_value.coerce_to_string(self, context)?; + let mut args = Vec::new(); + let num_args = self.avm.pop().coerce_to_f64(self, context)? as i64; // TODO(Herschel): max arg count? + for _ in 0..num_args { + args.push(self.avm.pop()); + } + + let target_fn = self.get_variable(context, &fn_name)?; + + let this = self + .target_clip_or_root() + .object() + .coerce_to_object(self, context); + let result = target_fn.call(self, context, this, None, &args)?; + self.avm.push(result); + + Ok(FrameControl::Continue) + } + + fn action_call_method( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let method_name = self.avm.pop(); + let object_val = self.avm.pop(); + let object = value_object::ValueObject::boxed(self, context, object_val); + let num_args = self.avm.pop().coerce_to_f64(self, context)? as i64; // TODO(Herschel): max arg count? + let mut args = Vec::new(); + for _ in 0..num_args { + args.push(self.avm.pop()); + } + + match method_name { + Value::Undefined | Value::Null => { + let this = self + .target_clip_or_root() + .object() + .coerce_to_object(self, context); + let result = object.call(self, context, this, None, &args)?; + self.avm.push(result); + } + Value::String(name) => { + if name.is_empty() { + let result = object.call(self, context, object, None, &args)?; + self.avm.push(result); + } else { + let result = object.call_method(&name, &args, self, context)?; + self.avm.push(result); + } + } + _ => { + self.avm.push(Value::Undefined); + log::warn!( + "Invalid method name, expected string but found {:?}", + method_name + ); + } + } + + Ok(FrameControl::Continue) + } + + fn action_cast_op( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let obj = self.avm.pop().coerce_to_object(self, context); + let constr = self.avm.pop().coerce_to_object(self, context); + + let prototype = constr + .get("prototype", self, context)? + .coerce_to_object(self, context); + + if obj.is_instance_of(self, context, constr, prototype)? { + self.avm.push(obj); + } else { + self.avm.push(Value::Null); + } + + Ok(FrameControl::Continue) + } + + fn action_constant_pool( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + constant_pool: &[&str], + ) -> Result, Error<'gc>> { + self.avm.constant_pool = GcCell::allocate( + context.gc_context, + constant_pool.iter().map(|s| (*s).to_string()).collect(), + ); + self.set_constant_pool(self.avm.constant_pool); + + Ok(FrameControl::Continue) + } + + fn action_decrement( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let a = self.avm.pop().coerce_to_f64(self, context)?; + self.avm.push(a - 1.0); + Ok(FrameControl::Continue) + } + + fn action_define_function( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + name: &str, + params: &[&str], + actions: SwfSlice, + ) -> Result, Error<'gc>> { + let swf_version = self.swf_version(); + let scope = Scope::new_closure_scope(self.scope_cell(), context.gc_context); + let constant_pool = self.constant_pool(); + let func = Avm1Function::from_df1( + swf_version, + actions, + name, + params, + scope, + constant_pool, + self.target_clip_or_root(), + ); + let prototype = + ScriptObject::object(context.gc_context, Some(self.avm.prototypes.object)).into(); + let func_obj = FunctionObject::function( + context.gc_context, + func, + Some(self.avm.prototypes.function), + Some(prototype), + ); + if name == "" { + self.avm.push(func_obj); + } else { + self.define(name, func_obj, context.gc_context); + } + + Ok(FrameControl::Continue) + } + + fn action_define_function_2( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + action_func: &Function, + parent_data: &SwfSlice, + ) -> Result, Error<'gc>> { + let swf_version = self.swf_version(); + let func_data = parent_data.to_subslice(action_func.actions).unwrap(); + let scope = Scope::new_closure_scope(self.scope_cell(), context.gc_context); + let constant_pool = self.constant_pool(); + let func = Avm1Function::from_df2( + swf_version, + func_data, + action_func, + scope, + constant_pool, + self.base_clip(), + ); + let prototype = + ScriptObject::object(context.gc_context, Some(self.avm.prototypes.object)).into(); + let func_obj = FunctionObject::function( + context.gc_context, + func, + Some(self.avm.prototypes.function), + Some(prototype), + ); + if action_func.name == "" { + self.avm.push(func_obj); + } else { + self.define(action_func.name, func_obj, context.gc_context); + } + + Ok(FrameControl::Continue) + } + + fn action_define_local( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // If the property does not exist on the local object's prototype chain, it is created on the local object. + // Otherwise, the property is set (including calling virtual setters). + let value = self.avm.pop(); + let name_val = self.avm.pop(); + let name = name_val.coerce_to_string(self, context)?; + let scope = self.scope_cell(); + scope + .write(context.gc_context) + .locals() + .set(&name, value, self, context)?; + Ok(FrameControl::Continue) + } + + fn action_define_local_2( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // If the property does not exist on the local object's prototype chain, it is created on the local object. + // Otherwise, the property is unchanged. + let name_val = self.avm.pop(); + let name = name_val.coerce_to_string(self, context)?; + let scope = self.scope_cell(); + if !scope.read().locals().has_property(self, context, &name) { + scope + .write(context.gc_context) + .locals() + .set(&name, Value::Undefined, self, context)?; + } + Ok(FrameControl::Continue) + } + + fn action_delete( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let name_val = self.avm.pop(); + let name = name_val.coerce_to_string(self, context)?; + let object = self.avm.pop(); + + if let Value::Object(object) = object { + let success = object.delete(self, context.gc_context, &name); + self.avm.push(success); + } else { + log::warn!("Cannot delete property {} from {:?}", name, object); + self.avm.push(false); + } + + Ok(FrameControl::Continue) + } + + fn action_delete_2( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let name_val = self.avm.pop(); + let name = name_val.coerce_to_string(self, context)?; + + //Fun fact: This isn't in the Adobe SWF19 spec, but this opcode returns + //a boolean based on if the delete actually deleted something. + let did_exist = self.is_defined(context, &name); + + self.scope_cell() + .read() + .delete(self, context, &name, context.gc_context); + self.avm.push(did_exist); + + Ok(FrameControl::Continue) + } + + fn action_divide( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // AS1 divide + let a = self.avm.pop().coerce_to_f64(self, context)?; + let b = self.avm.pop().coerce_to_f64(self, context)?; + + // TODO(Herschel): SWF19: "If A is zero, the result NaN, Infinity, or -Infinity is pushed to the in SWF 5 and later. + // In SWF 4, the result is the string #ERROR#."" + // Seems to be untrue for SWF v4, I get 1.#INF. + + self.avm.push(b / a); + Ok(FrameControl::Continue) + } + + fn action_end_drag( + &mut self, + context: &mut UpdateContext, + ) -> Result, Error<'gc>> { + *context.drag_object = None; + Ok(FrameControl::Continue) + } + + fn action_enumerate( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let name_value = self.avm.pop(); + let name = name_value.coerce_to_string(self, context)?; + self.avm.push(Value::Null); // Sentinel that indicates end of enumeration + let object = self.resolve(&name, context)?; + + match object { + Value::Object(ob) => { + for k in ob.get_keys(self).into_iter().rev() { + self.avm.push(k); + } + } + _ => log::error!("Cannot enumerate properties of {}", name), + }; + + Ok(FrameControl::Continue) + } + + fn action_enumerate_2( + &mut self, + _context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let value = self.avm.pop(); + + self.avm.push(Value::Null); // Sentinel that indicates end of enumeration + + if let Value::Object(object) = value { + for k in object.get_keys(self).into_iter().rev() { + self.avm.push(k); + } + } else { + log::warn!("Cannot enumerate {:?}", value); + } + + Ok(FrameControl::Continue) + } + + #[allow(clippy::float_cmp)] + fn action_equals( + &mut self, + _context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // AS1 equality + let a = self.avm.pop(); + let b = self.avm.pop(); + let result = b.into_number_v1() == a.into_number_v1(); + self.avm + .push(Value::from_bool(result, self.current_swf_version())); + Ok(FrameControl::Continue) + } + + #[allow(clippy::float_cmp)] + fn action_equals_2( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // Version >=5 equality + let a = self.avm.pop(); + let b = self.avm.pop(); + let result = b.abstract_eq(a, self, context, false)?; + self.avm.push(result); + Ok(FrameControl::Continue) + } + + fn action_extends( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let superclass = self.avm.pop().coerce_to_object(self, context); + let subclass = self.avm.pop().coerce_to_object(self, context); + + //TODO: What happens if we try to extend an object which has no `prototype`? + //e.g. `class Whatever extends Object.prototype` or `class Whatever extends 5` + let super_proto = superclass + .get("prototype", self, context)? + .coerce_to_object(self, context); + + let mut sub_prototype: Object<'gc> = + ScriptObject::object(context.gc_context, Some(super_proto)).into(); + + sub_prototype.set("constructor", superclass.into(), self, context)?; + sub_prototype.set_attributes( + context.gc_context, + Some("constructor"), + Attribute::DontEnum.into(), + EnumSet::empty(), + ); + + sub_prototype.set("__constructor__", superclass.into(), self, context)?; + sub_prototype.set_attributes( + context.gc_context, + Some("__constructor__"), + Attribute::DontEnum.into(), + EnumSet::empty(), + ); + + subclass.set("prototype", sub_prototype.into(), self, context)?; + + Ok(FrameControl::Continue) + } + + fn action_get_member( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let name_val = self.avm.pop(); + let name = name_val.coerce_to_string(self, context)?; + let object_val = self.avm.pop(); + let object = value_object::ValueObject::boxed(self, context, object_val); + + let result = object.get(&name, self, context)?; + self.avm.push(result); + + Ok(FrameControl::Continue) + } + + fn action_get_property( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let prop_index = self.avm.pop().into_number_v1() as usize; + let path = self.avm.pop(); + let ret = if let Some(target) = self.target_clip() { + if let Some(clip) = self.resolve_target_display_object(context, target, path)? { + let display_properties = self.avm.display_properties; + let props = display_properties.write(context.gc_context); + if let Some(property) = props.get_by_index(prop_index) { + property.get(self, context, clip)? + } else { + log::warn!("GetProperty: Invalid property index {}", prop_index); + Value::Undefined + } + } else { + //log::warn!("GetProperty: Invalid target {}", path); + Value::Undefined + } + } else { + log::warn!("GetProperty: Invalid base clip"); + Value::Undefined + }; + self.avm.push(ret); + Ok(FrameControl::Continue) + } + + fn action_get_time( + &mut self, + context: &mut UpdateContext, + ) -> Result, Error<'gc>> { + let time = context.navigator.time_since_launch().as_millis() as u32; + self.avm.push(time); + Ok(FrameControl::Continue) + } + + fn action_get_variable( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let var_path = self.avm.pop(); + let path = var_path.coerce_to_string(self, context)?; + + let value = self.get_variable(context, &path)?; + self.avm.push(value); + + Ok(FrameControl::Continue) + } + + fn action_get_url( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + url: &str, + target: &str, + ) -> Result, Error<'gc>> { + if target.starts_with("_level") && target.len() > 6 { + let url = url.to_string(); + match target[6..].parse::() { + Ok(level_id) => { + let fetch = context.navigator.fetch(&url, RequestOptions::get()); + let level = self.resolve_level(level_id, context); + + let process = context.load_manager.load_movie_into_clip( + context.player.clone().unwrap(), + level, + fetch, + None, + ); + context.navigator.spawn_future(process); + } + Err(e) => log::warn!( + "Couldn't parse level id {} for action_get_url: {}", + target, + e + ), + } + + return Ok(FrameControl::Continue); + } + + if let Some(fscommand) = fscommand::parse(url) { + fscommand::handle(fscommand, self, context)?; + } else { + context + .navigator + .navigate_to_url(url.to_owned(), Some(target.to_owned()), None); + } + + Ok(FrameControl::Continue) + } + + fn action_get_url_2( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + swf_method: swf::avm1::types::SendVarsMethod, + is_target_sprite: bool, + is_load_vars: bool, + ) -> Result, Error<'gc>> { + // TODO: Support `LoadVariablesFlag`, `LoadTargetFlag` + // TODO: What happens if there's only one string? + let target = self.avm.pop(); + let url_val = self.avm.pop(); + let url = url_val.coerce_to_string(self, context)?; + + if let Some(fscommand) = fscommand::parse(&url) { + fscommand::handle(fscommand, self, context)?; + return Ok(FrameControl::Continue); + } + + let window_target = target.coerce_to_string(self, context)?; + let clip_target: Option> = if is_target_sprite { + if let Value::Object(target) = target { + target.as_display_object() + } else { + let start = self.target_clip_or_root(); + self.resolve_target_display_object(context, start, target.clone())? + } + } else { + Some(self.target_clip_or_root()) + }; + + if is_load_vars { + if let Some(clip_target) = clip_target { + let target_obj = clip_target + .as_movie_clip() + .unwrap() + .object() + .coerce_to_object(self, context); + let (url, opts) = self.locals_into_request_options( + context, + url, + NavigationMethod::from_send_vars_method(swf_method), + ); + let fetch = context.navigator.fetch(&url, opts); + let process = context.load_manager.load_form_into_object( + context.player.clone().unwrap(), + target_obj, + fetch, + ); + + context.navigator.spawn_future(process); + } + + return Ok(FrameControl::Continue); + } else if is_target_sprite { + if let Some(clip_target) = clip_target { + let (url, opts) = self.locals_into_request_options( + context, + url, + NavigationMethod::from_send_vars_method(swf_method), + ); + let fetch = context.navigator.fetch(&url, opts); + let process = context.load_manager.load_movie_into_clip( + context.player.clone().unwrap(), + clip_target, + fetch, + None, + ); + context.navigator.spawn_future(process); + } + + return Ok(FrameControl::Continue); + } else { + let vars = match NavigationMethod::from_send_vars_method(swf_method) { + Some(method) => Some((method, self.locals_into_form_values(context))), + None => None, + }; + + context.navigator.navigate_to_url( + url.to_string(), + Some(window_target.to_string()), + vars, + ); + } + + Ok(FrameControl::Continue) + } + + fn action_goto_frame( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + frame: u16, + ) -> Result, Error<'gc>> { + if let Some(clip) = self.target_clip() { + if let Some(clip) = clip.as_movie_clip() { + // The frame on the stack is 0-based, not 1-based. + clip.goto_frame(self.avm, context, frame + 1, true); + } else { + log::error!("GotoFrame failed: Target is not a MovieClip"); + } + } else { + log::error!("GotoFrame failed: Invalid target"); + } + Ok(FrameControl::Continue) + } + + fn action_goto_frame_2( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + set_playing: bool, + scene_offset: u16, + ) -> Result, Error<'gc>> { + // Version 4+ gotoAndPlay/gotoAndStop + // Param can either be a frame number or a frame label. + if let Some(clip) = self.target_clip() { + if let Some(clip) = clip.as_movie_clip() { + let frame = self.avm.pop(); + let _ = globals::movie_clip::goto_frame( + clip, + self, + context, + &[frame], + !set_playing, + scene_offset, + ); + } else { + log::warn!("GotoFrame2: Target is not a MovieClip"); + } + } else { + log::warn!("GotoFrame2: Invalid target"); + } + Ok(FrameControl::Continue) + } + + fn action_goto_label( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + label: &str, + ) -> Result, Error<'gc>> { + if let Some(clip) = self.target_clip() { + if let Some(clip) = clip.as_movie_clip() { + if let Some(frame) = clip.frame_label_to_number(label) { + clip.goto_frame(self.avm, context, frame, true); + } else { + log::warn!("GoToLabel: Frame label '{}' not found", label); + } + } else { + log::warn!("GoToLabel: Target is not a MovieClip"); + } + } else { + log::warn!("GoToLabel: Invalid target"); + } + Ok(FrameControl::Continue) + } + + fn action_if( + &mut self, + _context: &mut UpdateContext, + jump_offset: i16, + reader: &mut Reader<'_>, + ) -> Result, Error<'gc>> { + let val = self.avm.pop(); + if val.as_bool(self.current_swf_version()) { + reader.seek(jump_offset.into()); + } + Ok(FrameControl::Continue) + } + + fn action_increment( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let a = self.avm.pop().coerce_to_f64(self, context)?; + self.avm.push(a + 1.0); + Ok(FrameControl::Continue) + } + + fn action_init_array( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let num_elements = self.avm.pop().coerce_to_f64(self, context)? as i64; + let array = ScriptObject::array(context.gc_context, Some(self.avm.prototypes.array)); + + for i in 0..num_elements { + array.set_array_element(i as usize, self.avm.pop(), context.gc_context); + } + + self.avm.push(Value::Object(array.into())); + Ok(FrameControl::Continue) + } + + fn action_init_object( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let num_props = self.avm.pop().coerce_to_f64(self, context)? as i64; + let object = ScriptObject::object(context.gc_context, Some(self.avm.prototypes.object)); + for _ in 0..num_props { + let value = self.avm.pop(); + let name_val = self.avm.pop(); + let name = name_val.coerce_to_string(self, context)?; + object.set(&name, value, self, context)?; + } + + self.avm.push(Value::Object(object.into())); + + Ok(FrameControl::Continue) + } + + fn action_implements_op( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let constr = self.avm.pop().coerce_to_object(self, context); + let count = self.avm.pop().coerce_to_f64(self, context)? as i64; //TODO: Is this coercion actually performed by Flash? + let mut interfaces = vec![]; + + //TODO: If one of the interfaces is not an object, do we leave the + //whole stack dirty, or...? + for _ in 0..count { + interfaces.push(self.avm.pop().coerce_to_object(self, context)); + } + + let mut prototype = constr + .get("prototype", self, context)? + .coerce_to_object(self, context); + + prototype.set_interfaces(context.gc_context, interfaces); + + Ok(FrameControl::Continue) + } + + fn action_instance_of( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let constr = self.avm.pop().coerce_to_object(self, context); + let obj = self.avm.pop().coerce_to_object(self, context); + + let prototype = constr + .get("prototype", self, context)? + .coerce_to_object(self, context); + let is_instance_of = obj.is_instance_of(self, context, constr, prototype)?; + + self.avm.push(is_instance_of); + Ok(FrameControl::Continue) + } + + fn action_jump( + &mut self, + _context: &mut UpdateContext, + jump_offset: i16, + reader: &mut Reader<'_>, + ) -> Result, Error<'gc>> { + // TODO(Herschel): Handle out-of-bounds. + reader.seek(jump_offset.into()); + Ok(FrameControl::Continue) + } + + fn action_less( + &mut self, + _context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // AS1 less than + let a = self.avm.pop(); + let b = self.avm.pop(); + let result = b.into_number_v1() < a.into_number_v1(); + self.avm + .push(Value::from_bool(result, self.current_swf_version())); + Ok(FrameControl::Continue) + } + + fn action_less_2( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // ECMA-262 s. 11.8.1 + let a = self.avm.pop(); + let b = self.avm.pop(); + + let result = b.abstract_lt(a, self, context)?; + + self.avm.push(result); + Ok(FrameControl::Continue) + } + + fn action_greater( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // ECMA-262 s. 11.8.2 + let a = self.avm.pop(); + let b = self.avm.pop(); + + let result = a.abstract_lt(b, self, context)?; + + self.avm.push(result); + Ok(FrameControl::Continue) + } + + fn action_mb_ascii_to_char( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // TODO(Herschel): Results on incorrect operands? + use std::convert::TryFrom; + let result = char::try_from(self.avm.pop().coerce_to_f64(self, context)? as u32); + match result { + Ok(val) => self.avm.push(val.to_string()), + Err(e) => log::warn!("Couldn't parse char for action_mb_ascii_to_char: {}", e), + } + Ok(FrameControl::Continue) + } + + fn action_mb_char_to_ascii( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // TODO(Herschel): Results on incorrect operands? + let val = self.avm.pop(); + let s = val.coerce_to_string(self, context)?; + let result = s.chars().next().unwrap_or('\0') as u32; + self.avm.push(result); + Ok(FrameControl::Continue) + } + + fn action_mb_string_extract( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // TODO(Herschel): Result with incorrect operands? + let len = self.avm.pop().coerce_to_f64(self, context)? as usize; + let start = self.avm.pop().coerce_to_f64(self, context)? as usize; + let val = self.avm.pop(); + let s = val.coerce_to_string(self, context)?; + let result = s[len..len + start].to_string(); // TODO(Herschel): Flash uses UTF-16 internally. + self.avm.push(result); + Ok(FrameControl::Continue) + } + + fn action_mb_string_length( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // TODO(Herschel): Result with non-string operands? + let val = self.avm.pop(); + let len = val.coerce_to_string(self, context)?.len(); + self.avm.push(len as f64); + Ok(FrameControl::Continue) + } + + fn action_multiply( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let a = self.avm.pop().coerce_to_f64(self, context)?; + let b = self.avm.pop().coerce_to_f64(self, context)?; + self.avm.push(a * b); + Ok(FrameControl::Continue) + } + + fn action_modulo( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // TODO: Wrong operands? + let a = self.avm.pop().coerce_to_f64(self, context)?; + let b = self.avm.pop().coerce_to_f64(self, context)?; + self.avm.push(b % a); + Ok(FrameControl::Continue) + } + + fn action_not( + &mut self, + _context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let version = self.current_swf_version(); + let val = !self.avm.pop().as_bool(version); + self.avm.push(Value::from_bool(val, version)); + Ok(FrameControl::Continue) + } + + fn action_next_frame( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + if let Some(clip) = self.target_clip() { + if let Some(clip) = clip.as_movie_clip() { + clip.next_frame(self.avm, context); + } else { + log::warn!("NextFrame: Target is not a MovieClip"); + } + } else { + log::warn!("NextFrame: Invalid target"); + } + Ok(FrameControl::Continue) + } + + fn action_new_method( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let method_name = self.avm.pop(); + let object_val = self.avm.pop(); + let num_args = self.avm.pop().coerce_to_f64(self, context)? as i64; + let mut args = Vec::new(); + for _ in 0..num_args { + args.push(self.avm.pop()); + } + + let object = value_object::ValueObject::boxed(self, context, object_val); + let constructor = + object.get(&method_name.coerce_to_string(self, context)?, self, context)?; + if let Value::Object(constructor) = constructor { + let prototype = constructor + .get("prototype", self, context)? + .coerce_to_object(self, context); + + let mut this = prototype.new(self, context, prototype, &args)?; + this.set("__constructor__", constructor.into(), self, context)?; + this.set_attributes( + context.gc_context, + Some("__constructor__"), + Attribute::DontEnum.into(), + EnumSet::empty(), + ); + if self.current_swf_version() < 7 { + this.set("constructor", constructor.into(), self, context)?; + this.set_attributes( + context.gc_context, + Some("constructor"), + Attribute::DontEnum.into(), + EnumSet::empty(), + ); + } + + //TODO: What happens if you `ActionNewMethod` without a method name? + constructor.call(self, context, this, None, &args)?; + + self.avm.push(this); + } else { + log::warn!( + "Tried to construct with non-object constructor {:?}", + constructor + ); + self.avm.push(Value::Undefined); + } + + Ok(FrameControl::Continue) + } + + fn action_new_object( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let fn_name_val = self.avm.pop(); + let fn_name = fn_name_val.coerce_to_string(self, context)?; + let num_args = self.avm.pop().coerce_to_f64(self, context)? as i64; + let mut args = Vec::new(); + for _ in 0..num_args { + args.push(self.avm.pop()); + } + + let constructor = self + .resolve(&fn_name, context)? + .coerce_to_object(self, context); + let prototype = constructor + .get("prototype", self, context)? + .coerce_to_object(self, context); + + let mut this = prototype.new(self, context, prototype, &args)?; + this.set("__constructor__", constructor.into(), self, context)?; + this.set_attributes( + context.gc_context, + Some("__constructor__"), + Attribute::DontEnum.into(), + EnumSet::empty(), + ); + if self.current_swf_version() < 7 { + this.set("constructor", constructor.into(), self, context)?; + this.set_attributes( + context.gc_context, + Some("constructor"), + Attribute::DontEnum.into(), + EnumSet::empty(), + ); + } + + constructor.call(self, context, this, None, &args)?; + + self.avm.push(this); + + Ok(FrameControl::Continue) + } + + fn action_or( + &mut self, + _context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // AS1 logical or + let a = self.avm.pop(); + let b = self.avm.pop(); + let version = self.current_swf_version(); + let result = b.as_bool(version) || a.as_bool(version); + self.avm.push(Value::from_bool(result, version)); + Ok(FrameControl::Continue) + } + + fn action_play( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + if let Some(clip) = self.target_clip() { + if let Some(clip) = clip.as_movie_clip() { + clip.play(context) + } else { + log::warn!("Play: Target is not a MovieClip"); + } + } else { + log::warn!("Play: Invalid target"); + } + Ok(FrameControl::Continue) + } + + fn action_prev_frame( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + if let Some(clip) = self.target_clip() { + if let Some(clip) = clip.as_movie_clip() { + clip.prev_frame(self.avm, context); + } else { + log::warn!("PrevFrame: Target is not a MovieClip"); + } + } else { + log::warn!("PrevFrame: Invalid target"); + } + Ok(FrameControl::Continue) + } + + fn action_pop( + &mut self, + _context: &mut UpdateContext, + ) -> Result, Error<'gc>> { + self.avm.pop(); + Ok(FrameControl::Continue) + } + + fn action_push( + &mut self, + _context: &mut UpdateContext, + values: &[swf::avm1::types::Value], + ) -> Result, Error<'gc>> { + for value in values { + use swf::avm1::types::Value as SwfValue; + let value = match value { + SwfValue::Undefined => Value::Undefined, + SwfValue::Null => Value::Null, + SwfValue::Bool(v) => Value::Bool(*v), + SwfValue::Int(v) => f64::from(*v).into(), + SwfValue::Float(v) => f64::from(*v).into(), + SwfValue::Double(v) => (*v).into(), + SwfValue::Str(v) => (*v).to_string().into(), + SwfValue::Register(v) => self.current_register(*v), + SwfValue::ConstantPool(i) => { + if let Some(value) = self.constant_pool().read().get(*i as usize) { + value.to_string().into() + } else { + log::warn!( + "ActionPush: Constant pool index {} out of range (len = {})", + i, + self.constant_pool().read().len() + ); + Value::Undefined + } + } + }; + self.avm.push(value); + } + Ok(FrameControl::Continue) + } + + fn action_push_duplicate( + &mut self, + _context: &mut UpdateContext, + ) -> Result, Error<'gc>> { + let val = self.avm.pop(); + self.avm.push(val.clone()); + self.avm.push(val); + Ok(FrameControl::Continue) + } + + fn action_random_number( + &mut self, + context: &mut UpdateContext, + ) -> Result, Error<'gc>> { + // A max value < 0 will always return 0, + // and the max value gets converted into an i32, so any number > 2^31 - 1 will return 0. + let max = self.avm.pop().into_number_v1() as i32; + let val = if max > 0 { + context.rng.gen_range(0, max) + } else { + 0 + }; + self.avm.push(val); + Ok(FrameControl::Continue) + } + + fn action_remove_sprite( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let target = self.avm.pop(); + let start_clip = self.target_clip_or_root(); + let target_clip = self.resolve_target_display_object(context, start_clip, target)?; + + if let Some(target_clip) = target_clip.and_then(|o| o.as_movie_clip()) { + let _ = globals::movie_clip::remove_movie_clip_with_bias(target_clip, context, 0); + } else { + log::warn!("RemoveSprite: Source is not a movie clip"); + } + Ok(FrameControl::Continue) + } + + fn action_return(&mut self) -> Result, Error<'gc>> { + let return_value = self.avm.pop(); + + Ok(FrameControl::Return(ReturnType::Explicit(return_value))) + } + + fn action_set_member( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let value = self.avm.pop(); + let name_val = self.avm.pop(); + let name = name_val.coerce_to_string(self, context)?; + + let object = self.avm.pop().coerce_to_object(self, context); + object.set(&name, value, self, context)?; + + Ok(FrameControl::Continue) + } + + fn action_set_property( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let value = self.avm.pop(); + let prop_index = self.avm.pop().coerce_to_u32(self, context)? as usize; + let path = self.avm.pop(); + if let Some(target) = self.target_clip() { + if let Some(clip) = self.resolve_target_display_object(context, target, path)? { + let display_properties = self.avm.display_properties; + let props = display_properties.read(); + if let Some(property) = props.get_by_index(prop_index) { + property.set(self, context, clip, value)?; + } + } else { + log::warn!("SetProperty: Invalid target"); + } + } else { + log::warn!("SetProperty: Invalid base clip"); + } + Ok(FrameControl::Continue) + } + + fn action_set_variable( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // Flash 4-style variable + let value = self.avm.pop(); + let var_path_val = self.avm.pop(); + let var_path = var_path_val.coerce_to_string(self, context)?; + self.set_variable(context, &var_path, value)?; + Ok(FrameControl::Continue) + } + + #[allow(clippy::float_cmp)] + fn action_strict_equals( + &mut self, + _context: &mut UpdateContext, + ) -> Result, Error<'gc>> { + // The same as normal equality but types must match + let a = self.avm.pop(); + let b = self.avm.pop(); + let result = a == b; + self.avm.push(result); + Ok(FrameControl::Continue) + } + + fn action_set_target( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + target: &str, + ) -> Result, Error<'gc>> { + let base_clip = self.base_clip(); + let new_target_clip; + let root = base_clip.root(); + let start = base_clip.object().coerce_to_object(self, context); + if target.is_empty() { + new_target_clip = Some(base_clip); + } else if let Some(clip) = self + .resolve_target_path(context, root, start, target)? + .and_then(|o| o.as_display_object()) + { + new_target_clip = Some(clip); + } else { + log::warn!("SetTarget failed: {} not found", target); + // TODO: Emulate AVM1 trace error message. + log::info!(target: "avm_trace", "Target not found: Target=\"{}\" Base=\"{}\"", target, base_clip.path()); + + // When SetTarget has an invalid target, subsequent GetVariables act + // as if they are targeting root, but subsequent Play/Stop/etc. + // fail silenty. + new_target_clip = None; + } + + self.set_target_clip(new_target_clip); + + let scope = self.scope_cell(); + let clip_obj = self + .target_clip() + .unwrap_or_else(|| self.base_clip().root()) + .object() + .coerce_to_object(self, context); + + self.set_scope(Scope::new_target_scope(scope, clip_obj, context.gc_context)); + Ok(FrameControl::Continue) + } + + fn action_set_target2( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let target = self.avm.pop(); + match target { + Value::String(target) => { + return self.action_set_target(context, &target); + } + Value::Undefined => { + // Reset + let base_clip = self.base_clip(); + self.set_target_clip(Some(base_clip)); + } + Value::Object(o) => { + if let Some(clip) = o.as_display_object() { + // Movieclips can be targetted directly + self.set_target_clip(Some(clip)); + } else { + // Other objects get coerced to string + let target = target.coerce_to_string(self, context)?; + return self.action_set_target(context, &target); + } + } + _ => { + let target = target.coerce_to_string(self, context)?; + return self.action_set_target(context, &target); + } + }; + + let scope = self.scope_cell(); + let clip_obj = self + .target_clip() + .unwrap_or_else(|| self.base_clip().root()) + .object() + .coerce_to_object(self, context); + self.set_scope(Scope::new_target_scope(scope, clip_obj, context.gc_context)); + Ok(FrameControl::Continue) + } + + fn action_stack_swap( + &mut self, + _context: &mut UpdateContext, + ) -> Result, Error<'gc>> { + let a = self.avm.pop(); + let b = self.avm.pop(); + self.avm.push(a); + self.avm.push(b); + Ok(FrameControl::Continue) + } + + fn action_start_drag( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let target = self.avm.pop(); + let start_clip = self.target_clip_or_root(); + let display_object = self.resolve_target_display_object(context, start_clip, target)?; + if let Some(display_object) = display_object { + let lock_center = self.avm.pop(); + let constrain = self.avm.pop().as_bool(self.current_swf_version()); + if constrain { + let y2 = self.avm.pop(); + let x2 = self.avm.pop(); + let y1 = self.avm.pop(); + let x1 = self.avm.pop(); + start_drag( + display_object, + self, + context, + &[lock_center, x1, y1, x2, y2], + ); + } else { + start_drag(display_object, self, context, &[lock_center]); + }; + } else { + log::warn!("StartDrag: Invalid target"); + } + Ok(FrameControl::Continue) + } + + fn action_stop( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + if let Some(clip) = self.target_clip() { + if let Some(clip) = clip.as_movie_clip() { + clip.stop(context); + } else { + log::warn!("Stop: Target is not a MovieClip"); + } + } else { + log::warn!("Stop: Invalid target"); + } + Ok(FrameControl::Continue) + } + + fn action_stop_sounds( + &mut self, + context: &mut UpdateContext, + ) -> Result, Error<'gc>> { + context.audio.stop_all_sounds(); + Ok(FrameControl::Continue) + } + + fn action_store_register( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + register: u8, + ) -> Result, Error<'gc>> { + // The value must remain on the stack. + let val = self.avm.pop(); + self.avm.push(val.clone()); + self.set_current_register(register, val, context); + + Ok(FrameControl::Continue) + } + + fn action_string_add( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // SWFv4 string concatenation + // TODO(Herschel): Result with non-string operands? + let a = self.avm.pop(); + let mut b = self.avm.pop().coerce_to_string(self, context)?.to_string(); + b.push_str(&a.coerce_to_string(self, context)?); + self.avm.push(b); + Ok(FrameControl::Continue) + } + + fn action_string_equals( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // AS1 strcmp + let a = self.avm.pop(); + let b = self.avm.pop(); + let result = b.coerce_to_string(self, context)? == a.coerce_to_string(self, context)?; + self.avm + .push(Value::from_bool(result, self.current_swf_version())); + Ok(FrameControl::Continue) + } + + fn action_string_extract( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // SWFv4 substring + // TODO(Herschel): Result with incorrect operands? + let len = self.avm.pop().coerce_to_f64(self, context)? as usize; + let start = self.avm.pop().coerce_to_f64(self, context)? as usize; + let val = self.avm.pop(); + let s = val.coerce_to_string(self, context)?; + // This is specifically a non-UTF8 aware substring. + // SWFv4 only used ANSI strings. + let result = s + .bytes() + .skip(start) + .take(len) + .map(|c| c as char) + .collect::(); + self.avm.push(result); + Ok(FrameControl::Continue) + } + + fn action_string_greater( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // AS1 strcmp + let a = self.avm.pop(); + let b = self.avm.pop(); + // This is specifically a non-UTF8 aware comparison. + let result = b + .coerce_to_string(self, context)? + .bytes() + .gt(a.coerce_to_string(self, context)?.bytes()); + self.avm + .push(Value::from_bool(result, self.current_swf_version())); + Ok(FrameControl::Continue) + } + + fn action_string_length( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // AS1 strlen + // Only returns byte length. + // TODO(Herschel): Result with non-string operands? + let val = self.avm.pop(); + let len = val.coerce_to_string(self, context)?.bytes().len() as f64; + self.avm.push(len); + Ok(FrameControl::Continue) + } + + fn action_string_less( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // AS1 strcmp + let a = self.avm.pop(); + let b = self.avm.pop(); + // This is specifically a non-UTF8 aware comparison. + let result = b + .coerce_to_string(self, context)? + .bytes() + .lt(a.coerce_to_string(self, context)?.bytes()); + self.avm + .push(Value::from_bool(result, self.current_swf_version())); + Ok(FrameControl::Continue) + } + + fn action_subtract( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let a = self.avm.pop().coerce_to_f64(self, context)?; + let b = self.avm.pop().coerce_to_f64(self, context)?; + self.avm.push(b - a); + Ok(FrameControl::Continue) + } + + fn action_target_path( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + // TODO(Herschel) + let _clip = self.avm.pop().coerce_to_object(self, context); + self.avm.push(Value::Undefined); + log::warn!("Unimplemented action: TargetPath"); + Ok(FrameControl::Continue) + } + + fn toggle_quality( + &mut self, + _context: &mut UpdateContext, + ) -> Result, Error<'gc>> { + // TODO(Herschel): Noop for now? Could chang anti-aliasing on render backend. + Ok(FrameControl::Continue) + } + + fn action_to_integer( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let val = self.avm.pop().coerce_to_f64(self, context)?; + self.avm.push(val.trunc()); + Ok(FrameControl::Continue) + } + + fn action_to_number( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let val = self.avm.pop().coerce_to_f64(self, context)?; + self.avm.push(val); + Ok(FrameControl::Continue) + } + + fn action_to_string( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let val = self.avm.pop(); + let string = val.coerce_to_string(self, context)?; + self.avm.push(string); + Ok(FrameControl::Continue) + } + + fn action_trace( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let val = self.avm.pop(); + // trace always prints "undefined" even though SWF6 and below normally + // coerce undefined to "". + let out = if val == Value::Undefined { + Cow::Borrowed("undefined") + } else { + val.coerce_to_string(self, context)? + }; + log::info!(target: "avm_trace", "{}", out); + Ok(FrameControl::Continue) + } + + fn action_type_of( + &mut self, + _context: &mut UpdateContext, + ) -> Result, Error<'gc>> { + let type_of = self.avm.pop().type_of(); + self.avm.push(type_of); + Ok(FrameControl::Continue) + } + + fn action_wait_for_frame( + &mut self, + _context: &mut UpdateContext, + _frame: u16, + num_actions_to_skip: u8, + r: &mut Reader<'_>, + ) -> Result, Error<'gc>> { + // TODO(Herschel): Always true for now. + let loaded = true; + if !loaded { + // Note that the offset is given in # of actions, NOT in bytes. + // Read the actions and toss them away. + skip_actions(r, num_actions_to_skip); + } + Ok(FrameControl::Continue) + } + + fn action_wait_for_frame_2( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + num_actions_to_skip: u8, + r: &mut Reader<'_>, + ) -> Result, Error<'gc>> { + // TODO(Herschel): Always true for now. + let _frame_num = self.avm.pop().coerce_to_f64(self, context)? as u16; + let loaded = true; + if !loaded { + // Note that the offset is given in # of actions, NOT in bytes. + // Read the actions and toss them away. + skip_actions(r, num_actions_to_skip); + } + Ok(FrameControl::Continue) + } + + #[allow(unused_variables)] + fn action_throw( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + let value = self.avm.pop(); + avm_debug!( + "Thrown exception: {}", + value + .coerce_to_string(self, context) + .unwrap_or_else(|_| Cow::Borrowed("undefined")) + ); + Err(Error::ThrownValue(value)) + } + + fn action_with( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + code: SwfSlice, + ) -> Result, Error<'gc>> { + let object = self.avm.pop().coerce_to_object(self, context); + let with_scope = Scope::new_with_scope(self.scope_cell(), object, context.gc_context); + let mut new_activation = self.with_new_scope(with_scope); + let _ = new_activation.run_actions(context, code)?; + Ok(FrameControl::Continue) + } + + /// Retrieve a given register value. + /// + /// If a given register does not exist, this function yields + /// Value::Undefined, which is also a valid register value. + pub fn current_register(&self, id: u8) -> Value<'gc> { + if self.has_local_register(id) { + self.local_register(id).unwrap_or(Value::Undefined) + } else { + self.avm + .registers + .get(id as usize) + .cloned() + .unwrap_or(Value::Undefined) + } + } + + /// Set a register to a given value. + /// + /// If a given register does not exist, this function does nothing. + pub fn set_current_register( + &mut self, + id: u8, + value: Value<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) { + if self.has_local_register(id) { + self.set_local_register(id, value, context.gc_context); + } else if let Some(v) = self.avm.registers.get_mut(id as usize) { + *v = value; + } + } + + pub fn avm(&mut self) -> &mut Avm1<'gc> { + self.avm + } + + /// Convert the current locals pool into a set of form values. + /// + /// This is necessary to support form submission from Flash via a couple of + /// legacy methods, such as the `ActionGetURL2` opcode or `getURL` function. + /// + /// WARNING: This does not support user defined virtual properties! + pub fn locals_into_form_values( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> HashMap { + let mut form_values = HashMap::new(); + let scope = self.scope_cell(); + let locals = scope.read().locals_cell(); + let keys = locals.get_keys(self); + + for k in keys { + let v = locals.get(&k, self, context); + + //TODO: What happens if an error occurs inside a virtual property? + form_values.insert( + k, + v.ok() + .unwrap_or_else(|| Value::Undefined) + .coerce_to_string(self, context) + .unwrap_or_else(|_| Cow::Borrowed("undefined")) + .to_string(), + ); + } + + form_values + } + + /// Construct request options for a fetch operation that may send locals as + /// form data in the request body or URL. + pub fn locals_into_request_options<'b>( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + url: Cow<'b, str>, + method: Option, + ) -> (Cow<'b, str>, RequestOptions) { + match method { + Some(method) => { + let vars = self.locals_into_form_values(context); + let qstring = form_urlencoded::Serializer::new(String::new()) + .extend_pairs(vars.iter()) + .finish(); + + match method { + NavigationMethod::GET if url.find('?').is_none() => ( + Cow::Owned(format!("{}?{}", url, qstring)), + RequestOptions::get(), + ), + NavigationMethod::GET => ( + Cow::Owned(format!("{}&{}", url, qstring)), + RequestOptions::get(), + ), + NavigationMethod::POST => ( + url, + RequestOptions::post(Some(( + qstring.as_bytes().to_owned(), + "application/x-www-form-urlencoded".to_string(), + ))), + ), + } + } + None => (url, RequestOptions::get()), + } + } + + /// Resolves a target value to a display object, relative to a starting display object. + /// + /// This is used by any action/function with a parameter that can be either + /// a display object or a string path referencing the display object. + /// For example, `removeMovieClip(mc)` takes either a string or a display object. + /// + /// This can be an object, dot path, slash path, or weird combination thereof: + /// `_root/movieClip`, `movieClip.child._parent`, `movieClip:child`, etc. + /// See the `target_path` test for many examples. + /// + /// A target path always resolves via the display list. It can look + /// at the prototype chain, but not the scope chain. + pub fn resolve_target_display_object( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + start: DisplayObject<'gc>, + target: Value<'gc>, + ) -> Result>, Error<'gc>> { + // If the value you got was a display object, we can just toss it straight back. + if let Value::Object(o) = target { + if let Some(o) = o.as_display_object() { + return Ok(Some(o)); + } + } + + // Otherwise, we coerce it into a string and try to resolve it as a path. + // This means that values like `undefined` will resolve to clips with an instance name of + // `"undefined"`, for example. + let path = target.coerce_to_string(self, context)?; + let root = start.root(); + let start = start.object().coerce_to_object(self, context); + Ok(self + .resolve_target_path(context, root, start, &path)? + .and_then(|o| o.as_display_object())) + } + + /// Resolves a target path string to an object. + /// This only returns `Object`; other values will bail out with `None`. + /// + /// This can be a dot path, slash path, or weird combination thereof: + /// `_root/movieClip`, `movieClip.child._parent`, `movieClip:child`, etc. + /// See the `target_path` test for many examples. + /// + /// A target path always resolves via the display list. It can look + /// at the prototype chain, but not the scope chain. + pub fn resolve_target_path( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + root: DisplayObject<'gc>, + start: Object<'gc>, + path: &str, + ) -> Result>, Error<'gc>> { + // Empty path resolves immediately to start clip. + if path.is_empty() { + return Ok(Some(start)); + } + + // Starting / means an absolute path starting from root. + // (`/bar` means `_root.bar`) + let mut path = path.as_bytes(); + let (mut object, mut is_slash_path) = if path[0] == b'/' { + path = &path[1..]; + (root.object().coerce_to_object(self, context), true) + } else { + (start, false) + }; + + let case_sensitive = self.is_case_sensitive(); + + // Iterate through each token in the path. + while !path.is_empty() { + // Skip any number of leading : + // `foo`, `:foo`, and `:::foo` are all the same + while path.get(0) == Some(&b':') { + path = &path[1..]; + } + + let val = if let b".." | b"../" | b"..:" = &path[..std::cmp::min(path.len(), 3)] { + // Check for .. + // SWF-4 style _parent + if path.get(2) == Some(&b'/') { + is_slash_path = true; + } + path = path.get(3..).unwrap_or(&[]); + if let Some(parent) = object.as_display_object().and_then(|o| o.parent()) { + parent.object() + } else { + // Tried to get parent of root, bail out. + return Ok(None); + } + } else { + // Step until the next delimiter. + // : . / all act as path delimiters. + // The only restriction is that after a / appears, + // . is no longer considered a delimiter. + // TODO: SWF4 is probably more restrictive. + let mut pos = 0; + while pos < path.len() { + match path[pos] { + b':' => break, + b'.' if !is_slash_path => break, + b'/' => { + is_slash_path = true; + break; + } + _ => (), + } + pos += 1; + } + + // Slice out the identifier and step the cursor past the delimiter. + let ident = &path[..pos]; + path = path.get(pos + 1..).unwrap_or(&[]); + + // Guaranteed to be valid UTF-8. + let name = unsafe { std::str::from_utf8_unchecked(ident) }; + + // Get the value from the object. + // Resolves display object instances first, then local variables. + // This is the opposite of general GetMember property access! + if let Some(child) = object + .as_display_object() + .and_then(|o| o.get_child_by_name(name, case_sensitive)) + { + child.object() + } else { + object.get(&name, self, context).unwrap() + } + }; + + // Resolve the value to an object while traversing the path. + object = if let Value::Object(o) = val { + o + } else { + return Ok(None); + }; + } + + Ok(Some(object)) + } + + /// Resolves a path for text field variable binding. + /// Returns the parent object that owns the variable, and the variable name. + /// Returns `None` if the path does not yet point to a valid object. + /// TODO: This can probably be merged with some of the above `resolve_target_path` methods. + pub fn resolve_text_field_variable_path<'s>( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + text_field_parent: DisplayObject<'gc>, + path: &'s str, + ) -> Result, &'s str)>, Error<'gc>> { + // Resolve a variable path for a GetVariable action. + let start = text_field_parent; + + // 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.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) + } + } + + /// Gets the value referenced by a target path string. + /// + /// This can be a raw variable name, a slash path, a dot path, or weird combination thereof. + /// For example: + /// `_root/movieClip.foo`, `movieClip:child:_parent`, `blah` + /// See the `target_path` test for many examples. + /// + /// The string first tries to resolve as target path with a variable name, such as + /// "a/b/c:foo". The right-most : or . delimits the variable name, with the left side + /// identifying the target object path. Note that the variable name on the right can + /// contain a slash in this case. This path is resolved on the scope chain; if + /// the path does not resolve to an existing property on a scope, the parent scope is + /// searched. Undefined is returned if no path resolves successfully. + /// + /// If there is no variable name, but the path contains slashes, the path will still try + /// to resolve on the scope chain as above. If this fails to resolve, we consider + /// it a simple variable name and fall through to the variable case + /// (i.e. "a/b/c" would be a variable named "a/b/c", not a path). + /// + /// Finally, if none of the above applies, it is a normal variable name resovled via the + /// scope chain. + pub fn get_variable<'s>( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + path: &'s str, + ) -> Result, 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.scope_cell()); + while let Some(scope) = current_scope { + if let Some(object) = + self.resolve_target_path(context, start.root(), *scope.read().locals(), path)? + { + if object.has_property(self, context, var_name) { + return Ok(object.get(var_name, self, context)?); + } + } + current_scope = scope.read().parent_cell(); + } + + return Ok(Value::Undefined); + } + + // If it doesn't have a trailing variable, it can still be a slash path. + // We can skip this step if we didn't find a slash above. + if has_slash { + let mut current_scope = Some(self.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(object.into()); + } + current_scope = scope.read().parent_cell(); + } + } + + // Finally! It's a plain old variable name. + // Resolve using scope chain, as normal. + self.resolve(&path, context) + } + + /// Sets the value referenced by a target path string. + /// + /// This can be a raw variable name, a slash path, a dot path, or weird combination thereof. + /// For example: + /// `_root/movieClip.foo`, `movieClip:child:_parent`, `blah` + /// See the `target_path` test for many examples. + /// + /// The string first tries to resolve as target path with a variable name, such as + /// "a/b/c:foo". The right-most : or . delimits the variable name, with the left side + /// identifying the target object path. Note that the variable name on the right can + /// contain a slash in this case. This target path (sans variable) is resolved on the + /// scope chain; if the path does not resolve to an existing property on a scope, the + /// parent scope is searched. If the path does not resolve on any scope, the set fails + /// and returns immediately. If the path does resolve, the variable name is created + /// or overwritten on the target scope. + /// + /// This differs from `get_variable` because slash paths with no variable segment are invalid; + /// For example, `foo/bar` sets a property named `foo/bar` on the current stack frame instead + /// of drilling into the display list. + /// + /// If the string does not resolve as a path, the path is considered a normal variable + /// name and is set on the scope chain as usual. + pub fn set_variable<'s>( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + path: &'s str, + value: Value<'gc>, + ) -> Result<(), Error<'gc>> { + // Resolve a variable path for a GetVariable action. + 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() { + return Ok(()); + } + + // Find the right-most : or . in the path. + // If we have one, we must resolve as a target path. + let mut var_iter = path.as_bytes().rsplitn(2, |&c| c == b':' || c == b'.'); + 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.scope_cell()); + while let Some(scope) = current_scope { + if let Some(object) = + self.resolve_target_path(context, start.root(), *scope.read().locals(), path)? + { + object.set(var_name, value, self, context)?; + return Ok(()); + } + current_scope = scope.read().parent_cell(); + } + + return Ok(()); + } + + // Finally! It's a plain old variable name. + // Set using scope chain, as normal. + // This will overwrite the value if the property exists somewhere + // in the scope chain, otherwise it is created on the top-level object. + let this = self.this_cell(); + let scope = self.scope_cell(); + scope.read().set(path, value, self, context, this)?; + Ok(()) + } + + /// Resolve a level by ID. + /// + /// If the level does not exist, then it will be created and instantiated + /// with a script object. + pub fn resolve_level( + &mut self, + level_id: u32, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> DisplayObject<'gc> { + if let Some(level) = context.levels.get(&level_id) { + *level + } else { + let mut level: DisplayObject<'_> = MovieClip::new( + SwfSlice::empty(self.base_clip().movie().unwrap()), + context.gc_context, + ) + .into(); + + level.set_depth(context.gc_context, level_id as i32); + context.levels.insert(level_id, level); + level.post_instantiation(self.avm, context, level, None, false); + + level + } + } + + /// The current target clip of the executing code. + /// 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) -> DisplayObject<'gc> { + self.target_clip() + .unwrap_or_else(|| self.base_clip().root()) + } + + /// Obtain the value of `_root`. + pub fn root_object(&self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Value<'gc> { + self.base_clip().root().object() + } + + /// Get the currently executing SWF version. + pub fn current_swf_version(&self) -> u8 { + self.swf_version() + } + + /// Returns whether property keys should be case sensitive based on the current SWF version. + pub fn is_case_sensitive(&self) -> bool { + self.current_swf_version() > 6 + } + + /// Resolve a particular named local variable within this activation. + /// + /// Because scopes are object chains, the same rules for `Object::get` + /// still apply here. + pub fn resolve( + &mut self, + name: &str, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Result, Error<'gc>> { + if name == "this" { + return Ok(Value::Object(self.this_cell())); + } + + if name == "arguments" && self.arguments.is_some() { + return Ok(Value::Object(self.arguments.unwrap())); + } + + self.scope_cell() + .read() + .resolve(name, self, context, self.this_cell()) + } + + /// Check if a particular property in the scope chain is defined. + pub fn is_defined(&mut self, context: &mut UpdateContext<'_, 'gc, '_>, name: &str) -> bool { + if name == "this" { + return true; + } + + if name == "arguments" && self.arguments.is_some() { + return true; + } + + self.scope_cell().read().is_defined(self, context, name) + } + /// Returns the SWF version of the action or function being executed. pub fn swf_version(&self) -> u8 { self.swf_version } - /// Returns the data this stack frame executes from. - pub fn data(&self) -> SwfSlice { - self.data.clone() - } - - /// Change the data being executed. - #[allow(dead_code)] - pub fn set_data(&mut self, new_data: SwfSlice) { - self.data = new_data; - } - - /// Determines if a stack frame references the same function as a given - /// SwfSlice. - #[allow(dead_code)] - pub fn is_identical_fn(&self, other: &SwfSlice) -> bool { - Arc::ptr_eq(&self.data.movie, &other.movie) - } - - /// Returns a mutable reference to the current data offset. - pub fn pc(&self) -> usize { - self.pc - } - /// Change the current PC. - pub fn set_pc(&mut self, new_pc: usize) { - self.pc = new_pc; - } - /// Returns AVM local variable scope. pub fn scope(&self) -> Ref> { self.scope.read() @@ -305,50 +2832,6 @@ impl<'gc> Activation<'gc> { self.target_clip = value; } - /// Indicates whether or not the end of this scope should return a value. - pub fn can_return(&self) -> bool { - self.is_function - } - - /// Resolve a particular named local variable within this activation. - /// - /// Because scopes are object chains, the same rules for `Object::get` - /// still apply here. - pub fn resolve( - &self, - name: &str, - avm: &mut Avm1<'gc>, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error<'gc>> { - if name == "this" { - return Ok(Value::Object(self.this).into()); - } - - if name == "arguments" && self.arguments.is_some() { - return Ok(Value::Object(self.arguments.unwrap()).into()); - } - - self.scope().resolve(name, avm, context, self.this) - } - - /// Check if a particular property in the scope chain is defined. - pub fn is_defined( - &self, - avm: &mut Avm1<'gc>, - context: &mut UpdateContext<'_, 'gc, '_>, - name: &str, - ) -> bool { - if name == "this" { - return true; - } - - if name == "arguments" && self.arguments.is_some() { - return true; - } - - self.scope().is_defined(avm, context, name) - } - /// Define a named local variable within this activation. pub fn define(&self, name: &str, value: impl Into>, mc: MutationContext<'gc, '_>) { self.scope().define(name, value, mc) @@ -403,35 +2886,4 @@ impl<'gc> Activation<'gc> { pub fn set_constant_pool(&mut self, constant_pool: GcCell<'gc, Vec>) { self.constant_pool = constant_pool; } - - /// Attempts to lock the activation frame for execution. - /// - /// If this frame is already executing, that is an error condition. - pub fn lock(&mut self) -> Result<(), Error<'gc>> { - if self.is_executing { - return Err(Error::AlreadyExecutingFrame); - } - - self.is_executing = true; - - Ok(()) - } - - /// Unlock the activation object. This allows future execution to run on it - /// again. - pub fn unlock_execution(&mut self) { - self.is_executing = false; - } - - /// Retrieve the return value from a completed activation, if the function - /// has already returned. - #[allow(dead_code)] - pub fn return_value(&self) -> Option> { - self.return_value.clone() - } - - /// Set the return value. - pub fn set_return_value(&mut self, value: Value<'gc>) { - self.return_value = Some(value); - } } diff --git a/core/src/avm1/debug.rs b/core/src/avm1/debug.rs index 7b827adc3..2678b295b 100644 --- a/core/src/avm1/debug.rs +++ b/core/src/avm1/debug.rs @@ -1,4 +1,5 @@ -use crate::avm1::{Avm1, Object, ObjectPtr, TObject, Value}; +use crate::avm1::activation::Activation; +use crate::avm1::{Object, ObjectPtr, TObject, Value}; use crate::context::UpdateContext; #[allow(dead_code)] @@ -23,11 +24,11 @@ impl<'a> VariableDumper<'a> { pub fn dump<'gc>( value: &Value<'gc>, indent: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> String { let mut dumper = VariableDumper::new(indent); - dumper.print_value(value, avm, context); + dumper.print_value(value, activation, context); dumper.output } @@ -84,7 +85,7 @@ impl<'a> VariableDumper<'a> { pub fn print_object<'gc>( &mut self, object: &Object<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) { let (id, new) = self.object_id(object); @@ -93,7 +94,7 @@ impl<'a> VariableDumper<'a> { self.output.push_str("]"); if new { - self.print_properties(object, avm, context); + self.print_properties(object, activation, context); } } @@ -101,12 +102,12 @@ impl<'a> VariableDumper<'a> { &mut self, object: &Object<'gc>, key: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) { - match object.get(&key, avm, context) { + match object.get(&key, activation, context) { Ok(value) => { - self.print_value(&value, avm, context); + self.print_value(&value, activation, context); } Err(e) => { self.output.push_str("Error: \""); @@ -119,10 +120,10 @@ impl<'a> VariableDumper<'a> { pub fn print_properties<'gc>( &mut self, object: &Object<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) { - let keys = object.get_keys(avm); + let keys = object.get_keys(activation); if keys.is_empty() { self.output.push_str(" {}"); } else { @@ -133,7 +134,7 @@ impl<'a> VariableDumper<'a> { self.indent(); self.output.push_str(&key); self.output.push_str(": "); - self.print_property(object, &key, avm, context); + self.print_property(object, &key, activation, context); self.output.push_str("\n"); } @@ -146,7 +147,7 @@ impl<'a> VariableDumper<'a> { pub fn print_value<'gc>( &mut self, value: &Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) { match value { @@ -158,7 +159,7 @@ impl<'a> VariableDumper<'a> { self.print_string(value); } Value::Object(object) => { - self.print_object(object, avm, context); + self.print_object(object, activation, context); } } } @@ -168,10 +169,10 @@ impl<'a> VariableDumper<'a> { header: &str, name: &str, object: &Object<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) { - let keys = object.get_keys(avm); + let keys = object.get_keys(activation); if keys.is_empty() { return; } @@ -183,7 +184,7 @@ impl<'a> VariableDumper<'a> { for key in keys.into_iter() { self.output.push_str(&format!("{}.{}", name, key)); self.output.push_str(" = "); - self.print_property(object, &key, avm, context); + self.print_property(object, &key, activation, context); self.output.push_str("\n"); } @@ -197,25 +198,24 @@ mod tests { use super::*; use crate::avm1::error::Error; use crate::avm1::function::Executable; - use crate::avm1::return_value::ReturnValue; use crate::avm1::test_utils::with_avm; use crate::avm1::ScriptObject; use enumset::EnumSet; fn throw_error<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], - ) -> Result, Error<'gc>> { + ) -> Result, Error<'gc>> { Err(Error::PrototypeRecursionLimit) } #[test] fn dump_undefined() { - with_avm(19, |avm, context, _root| -> Result<(), Error> { + with_avm(19, |activation, context, _root| -> Result<(), Error> { assert_eq!( - VariableDumper::dump(&Value::Undefined, " ", avm, context), + VariableDumper::dump(&Value::Undefined, " ", activation, context), "undefined" ); Ok(()) @@ -224,9 +224,9 @@ mod tests { #[test] fn dump_null() { - with_avm(19, |avm, context, _root| -> Result<(), Error> { + with_avm(19, |activation, context, _root| -> Result<(), Error> { assert_eq!( - VariableDumper::dump(&Value::Null, " ", avm, context), + VariableDumper::dump(&Value::Null, " ", activation, context), "null" ); Ok(()) @@ -235,13 +235,13 @@ mod tests { #[test] fn dump_bool() { - with_avm(19, |avm, context, _root| -> Result<(), Error> { + with_avm(19, |activation, context, _root| -> Result<(), Error> { assert_eq!( - VariableDumper::dump(&Value::Bool(true), " ", avm, context), + VariableDumper::dump(&Value::Bool(true), " ", activation, context), "true" ); assert_eq!( - VariableDumper::dump(&Value::Bool(false), " ", avm, context), + VariableDumper::dump(&Value::Bool(false), " ", activation, context), "false" ); Ok(()) @@ -250,13 +250,13 @@ mod tests { #[test] fn dump_number() { - with_avm(19, |avm, context, _root| -> Result<(), Error> { + with_avm(19, |activation, context, _root| -> Result<(), Error> { assert_eq!( - VariableDumper::dump(&Value::Number(1000.0), " ", avm, context), + VariableDumper::dump(&Value::Number(1000.0), " ", activation, context), "1000" ); assert_eq!( - VariableDumper::dump(&Value::Number(-0.05), " ", avm, context), + VariableDumper::dump(&Value::Number(-0.05), " ", activation, context), "-0.05" ); Ok(()) @@ -265,13 +265,18 @@ mod tests { #[test] fn dump_string() { - with_avm(19, |avm, context, _root| -> Result<(), Error> { + with_avm(19, |activation, context, _root| -> Result<(), Error> { assert_eq!( - VariableDumper::dump(&Value::String("".to_string()), " ", avm, context), + VariableDumper::dump(&Value::String("".to_string()), " ", activation, context), "\"\"" ); assert_eq!( - VariableDumper::dump(&Value::String("HELLO WORLD".to_string()), " ", avm, context), + VariableDumper::dump( + &Value::String("HELLO WORLD".to_string()), + " ", + activation, + context + ), "\"HELLO WORLD\"" ); assert_eq!( @@ -280,7 +285,7 @@ mod tests { "Escape \"this\" string\nplease! \u{0008}\u{000C}\n\r\t\"\\".to_string() ), " ", - avm, + activation, context ), "\"Escape \\\"this\\\" string\\nplease! \\b\\f\\n\\r\\t\\\"\\\\\"" @@ -291,10 +296,10 @@ mod tests { #[test] fn dump_empty_object() { - with_avm(19, |avm, context, _root| -> Result<(), Error> { + with_avm(19, |activation, context, _root| -> Result<(), Error> { let object = ScriptObject::object(context.gc_context, None); assert_eq!( - VariableDumper::dump(&object.into(), " ", avm, context), + VariableDumper::dump(&object.into(), " ", activation, context), "[object #0] {}" ); Ok(()) @@ -303,16 +308,21 @@ mod tests { #[test] fn dump_object() { - with_avm(19, |avm, context, _root| -> Result<(), Error> { + with_avm(19, |activation, context, _root| -> Result<(), Error> { let object = ScriptObject::object(context.gc_context, None); let child = ScriptObject::object(context.gc_context, None); - object.set("self", object.into(), avm, context)?; - object.set("test", Value::String("value".to_string()), avm, context)?; - object.set("child", child.into(), avm, context)?; - child.set("parent", object.into(), avm, context)?; - child.set("age", Value::Number(6.0), avm, context)?; + object.set("self", object.into(), activation, context)?; + object.set( + "test", + Value::String("value".to_string()), + activation, + context, + )?; + object.set("child", child.into(), activation, context)?; + child.set("parent", object.into(), activation, context)?; + child.set("age", Value::Number(6.0), activation, context)?; assert_eq!( - VariableDumper::dump(&object.into(), " ", avm, context), + VariableDumper::dump(&object.into(), " ", activation, context), "[object #0] {\n child: [object #1] {\n age: 6\n parent: [object #0]\n }\n test: \"value\"\n self: [object #0]\n}", ); Ok(()) @@ -321,7 +331,7 @@ mod tests { #[test] fn dump_object_with_error() { - with_avm(19, |avm, context, _root| -> Result<(), Error> { + with_avm(19, |activation, context, _root| -> Result<(), Error> { let object = ScriptObject::object(context.gc_context, None); object.add_property( context.gc_context, @@ -331,7 +341,7 @@ mod tests { EnumSet::empty(), ); assert_eq!( - VariableDumper::dump(&object.into(), " ", avm, context), + VariableDumper::dump(&object.into(), " ", activation, context), "[object #0] {\n broken_value: Error: \"Prototype recursion limit has been exceeded\"\n}" ); Ok(()) @@ -340,16 +350,21 @@ mod tests { #[test] fn dump_variables() { - with_avm(19, |avm, context, _root| -> Result<(), Error> { + with_avm(19, |activation, context, _root| -> Result<(), Error> { let object = ScriptObject::object(context.gc_context, None); let child = ScriptObject::object(context.gc_context, None); - object.set("self", object.into(), avm, context)?; - object.set("test", Value::String("value".to_string()), avm, context)?; - object.set("child", child.into(), avm, context)?; - child.set("parent", object.into(), avm, context)?; - child.set("age", Value::Number(6.0), avm, context)?; + object.set("self", object.into(), activation, context)?; + object.set( + "test", + Value::String("value".to_string()), + activation, + context, + )?; + object.set("child", child.into(), activation, context)?; + child.set("parent", object.into(), activation, context)?; + child.set("age", Value::Number(6.0), activation, context)?; let mut dumper = VariableDumper::new(" "); - dumper.print_variables("Variables:", "object", &object.into(), avm, context); + dumper.print_variables("Variables:", "object", &object.into(), activation, context); assert_eq!( dumper.output, "Variables:\nobject.child = [object #0] {\n age: 6\n parent: [object #1] {\n child: [object #0]\n test: \"value\"\n self: [object #1]\n }\n }\nobject.test = \"value\"\nobject.self = [object #1]\n\n" diff --git a/core/src/avm1/error.rs b/core/src/avm1/error.rs index 83b50df9e..0e757dd58 100644 --- a/core/src/avm1/error.rs +++ b/core/src/avm1/error.rs @@ -9,15 +9,6 @@ pub enum Error<'gc> { #[error("Couldn't parse SWF. This may or may not be a bug in Ruffle, please help us by reporting it to https://github.com/ruffle-rs/ruffle/issues and include the swf that triggered it.")] InvalidSwf(#[from] swf::error::Error), - #[error("No stack frame to execute. This is probably a bug in Ruffle, please report it to https://github.com/ruffle-rs/ruffle/issues and include the swf that triggered it.")] - NoStackFrame, - - #[error("Attempted to run a frame not on the current interpreter stack. This is probably a bug in Ruffle, please report it to https://github.com/ruffle-rs/ruffle/issues and include the swf that triggered it.")] - FrameNotOnStack, - - #[error("Attempted to execute the same frame twice. This is probably a bug in Ruffle, please report it to https://github.com/ruffle-rs/ruffle/issues and include the swf that triggered it.")] - AlreadyExecutingFrame, - #[error("A script has thrown a custom error.")] ThrownValue(Value<'gc>), } @@ -27,9 +18,6 @@ impl Error<'_> { match self { Error::PrototypeRecursionLimit => true, Error::InvalidSwf(_) => true, - Error::NoStackFrame => true, - Error::FrameNotOnStack => true, - Error::AlreadyExecutingFrame => false, Error::ThrownValue(_) => false, } } diff --git a/core/src/avm1/fscommand.rs b/core/src/avm1/fscommand.rs index 7c575b8fa..b0b9e14ea 100644 --- a/core/src/avm1/fscommand.rs +++ b/core/src/avm1/fscommand.rs @@ -1,8 +1,8 @@ //! FSCommand handling +use crate::avm1::activation::Activation; use crate::avm1::error::Error; -use crate::avm1::{Avm1, UpdateContext}; - +use crate::avm1::UpdateContext; /// Parse an FSCommand URL. pub fn parse(url: &str) -> Option<&str> { log::info!("Checking {}", url); @@ -16,7 +16,7 @@ pub fn parse(url: &str) -> Option<&str> { /// TODO: FSCommand URL handling pub fn handle<'gc>( fscommand: &str, - _avm: &mut Avm1, + _activation: &mut Activation, _ac: &mut UpdateContext, ) -> Result<(), Error<'gc>> { log::warn!("Unhandled FSCommand: {}", fscommand); diff --git a/core/src/avm1/function.rs b/core/src/avm1/function.rs index 5e82390b1..fb8bc710a 100644 --- a/core/src/avm1/function.rs +++ b/core/src/avm1/function.rs @@ -3,11 +3,10 @@ use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::property::{Attribute, Attribute::*}; -use crate::avm1::return_value::ReturnValue; use crate::avm1::scope::Scope; use crate::avm1::super_object::SuperObject; use crate::avm1::value::Value; -use crate::avm1::{Avm1, Object, ObjectPtr, ScriptObject, TObject, UpdateContext}; +use crate::avm1::{Object, ObjectPtr, ScriptObject, TObject, UpdateContext}; use crate::display_object::{DisplayObject, TDisplayObject}; use crate::tag_utils::SwfSlice; use enumset::EnumSet; @@ -31,11 +30,11 @@ use swf::avm1::types::FunctionParam; /// your function yields `None`, you must ensure that the top-most activation /// in the AVM1 runtime will return with the value of this function. pub type NativeFunction<'gc> = fn( - &mut Avm1<'gc>, + &mut Activation<'_, 'gc>, &mut UpdateContext<'_, 'gc, '_>, Object<'gc>, &[Value<'gc>], -) -> Result, Error<'gc>>; +) -> Result, Error<'gc>>; /// Represents a function defined in the AVM1 runtime, either through /// `DefineFunction` or `DefineFunction2`. @@ -223,20 +222,21 @@ impl<'gc> Executable<'gc> { /// create a new stack frame and execute the action data yourself. pub fn exec( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, args: &[Value<'gc>], - ) -> Result, Error<'gc>> { + ) -> Result, Error<'gc>> { match self { - Executable::Native(nf) => nf(avm, ac, this, args), + Executable::Native(nf) => nf(activation, ac, this, args), Executable::Action(af) => { let child_scope = GcCell::allocate( ac.gc_context, Scope::new_local_scope(af.scope(), ac.gc_context), ); - let arguments = ScriptObject::object(ac.gc_context, Some(avm.prototypes().object)); + let arguments = + ScriptObject::object(ac.gc_context, Some(activation.avm().prototypes().object)); if !af.suppress_arguments { for i in 0..args.len() { arguments.define_value( @@ -261,7 +261,7 @@ impl<'gc> Executable<'gc> { SuperObject::from_this_and_base_proto( this, base_proto.unwrap_or(this), - avm, + activation, ac, )? .into(), @@ -270,7 +270,7 @@ impl<'gc> Executable<'gc> { None }; - let effective_ver = if avm.current_swf_version() > 5 { + let effective_ver = if activation.current_swf_version() > 5 { af.swf_version() } else { this.as_display_object() @@ -278,19 +278,15 @@ impl<'gc> Executable<'gc> { .unwrap_or(ac.player_version) }; - let frame_cell = GcCell::allocate( - ac.gc_context, - Activation::from_function( - effective_ver, - af.data(), - child_scope, - af.constant_pool, - af.base_clip, - this, - Some(argcell), - ), + let mut frame = Activation::from_action( + activation.avm(), + effective_ver, + child_scope, + af.constant_pool, + af.base_clip, + this, + Some(argcell), ); - let mut frame = frame_cell.write(ac.gc_context); frame.allocate_local_registers(af.register_count(), ac.gc_context); @@ -341,7 +337,8 @@ impl<'gc> Executable<'gc> { } if af.preload_global { - frame.set_local_register(preload_r, avm.global_object(ac), ac.gc_context); + let global = frame.avm().global_object(ac); + frame.set_local_register(preload_r, global, ac.gc_context); } //TODO: What happens if the argument registers clash with the @@ -357,9 +354,8 @@ impl<'gc> Executable<'gc> { _ => {} } } - avm.insert_stack_frame(frame_cell); - Ok(frame_cell.into()) + Ok(frame.run_actions(ac, af.data.clone())?.value()) } } } @@ -456,34 +452,33 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn get_local( &self, name: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, ) -> Result, Error<'gc>> { - self.base.get_local(name, avm, context, this) + self.base.get_local(name, activation, context, this) } fn set( &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error<'gc>> { - self.base.set(name, value, avm, context) + self.base.set(name, value, activation, context) } fn call( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(exec) = self.as_executable() { - exec.exec(avm, context, this, base_proto, args)? - .resolve(avm, context) + exec.exec(activation, context, this, base_proto, args) } else { Ok(Value::Undefined) } @@ -493,17 +488,16 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, - ) -> Result, Error<'gc>> { - self.base.call_setter(name, value, avm, context, this) + ) -> Option> { + self.base.call_setter(name, value, activation, context) } #[allow(clippy::new_ret_no_self)] fn new( &self, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, prototype: Object<'gc>, _args: &[Value<'gc>], @@ -525,11 +519,11 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn delete( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, ) -> bool { - self.base.delete(avm, gc_context, name) + self.base.delete(activation, gc_context, name) } fn proto(&self) -> Option> { @@ -575,7 +569,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { fn add_property_with_case( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, get: Executable<'gc>, @@ -583,46 +577,46 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { attributes: EnumSet, ) { self.base - .add_property_with_case(avm, gc_context, name, get, set, attributes) + .add_property_with_case(activation, gc_context, name, get, set, attributes) } fn has_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base.has_property(avm, context, name) + self.base.has_property(activation, context, name) } fn has_own_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base.has_own_property(avm, context, name) + self.base.has_own_property(activation, context, name) } fn has_own_virtual( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base.has_own_virtual(avm, context, name) + self.base.has_own_virtual(activation, context, name) } - fn is_property_overwritable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.base.is_property_overwritable(avm, name) + fn is_property_overwritable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.base.is_property_overwritable(activation, name) } - fn is_property_enumerable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.base.is_property_enumerable(avm, name) + fn is_property_enumerable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.base.is_property_enumerable(activation, name) } - fn get_keys(&self, avm: &mut Avm1<'gc>) -> Vec { - self.base.get_keys(avm) + fn get_keys(&self, activation: &mut Activation<'_, 'gc>) -> Vec { + self.base.get_keys(activation) } fn as_string(&self) -> Cow { diff --git a/core/src/avm1/globals.rs b/core/src/avm1/globals.rs index 13e1f10f4..af6da3e73 100644 --- a/core/src/avm1/globals.rs +++ b/core/src/avm1/globals.rs @@ -1,9 +1,9 @@ +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::fscommand; use crate::avm1::function::{Executable, FunctionObject}; use crate::avm1::listeners::SystemListeners; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, UpdateContext, Value}; +use crate::avm1::{Object, ScriptObject, TObject, UpdateContext, Value}; use crate::backend::navigator::NavigationMethod; use enumset::EnumSet; use gc_arena::MutationContext; @@ -40,21 +40,21 @@ mod xml; #[allow(non_snake_case, unused_must_use)] //can't use errors yet pub fn getURL<'a, 'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'a, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { //TODO: Error behavior if no arguments are present if let Some(url_val) = args.get(0) { - let url = url_val.coerce_to_string(avm, context)?; + let url = url_val.coerce_to_string(activation, context)?; if let Some(fscommand) = fscommand::parse(&url) { - fscommand::handle(fscommand, avm, context); - return Ok(Value::Undefined.into()); + fscommand::handle(fscommand, activation, context); + return Ok(Value::Undefined); } let window = if let Some(window) = args.get(1) { - Some(window.coerce_to_string(avm, context)?.to_string()) + Some(window.coerce_to_string(activation, context)?.to_string()) } else { None }; @@ -63,64 +63,67 @@ pub fn getURL<'a, 'gc>( Some(Value::String(s)) if s == "POST" => Some(NavigationMethod::POST), _ => None, }; - let vars_method = method.map(|m| (m, avm.locals_into_form_values(context))); + let vars_method = method.map(|m| (m, activation.locals_into_form_values(context))); context .navigator .navigate_to_url(url.to_string(), window, vars_method); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn random<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { match args.get(0) { Some(Value::Number(max)) => Ok(action_context.rng.gen_range(0.0f64, max).floor().into()), - _ => Ok(Value::Undefined.into()), //TODO: Shouldn't this be an error condition? + _ => Ok(Value::Undefined), //TODO: Shouldn't this be an error condition? } } pub fn is_nan<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(val) = args.get(0) { - Ok(val.coerce_to_f64(avm, action_context)?.is_nan().into()) + Ok(val + .coerce_to_f64(activation, action_context)? + .is_nan() + .into()) } else { Ok(true.into()) } } pub fn get_infinity<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if avm.current_swf_version() > 4 { +) -> Result, Error<'gc>> { + if activation.current_swf_version() > 4 { Ok(f64::INFINITY.into()) } else { - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } pub fn get_nan<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if avm.current_swf_version() > 4 { +) -> Result, Error<'gc>> { + if activation.current_swf_version() > 4 { Ok(f64::NAN.into()) } else { - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } @@ -473,7 +476,10 @@ pub fn create_globals<'gc>( mod tests { use super::*; - fn setup<'gc>(_avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) -> Object<'gc> { + fn setup<'gc>( + _activation: &mut Activation<'_, 'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Object<'gc> { create_globals(context.gc_context).1 } diff --git a/core/src/avm1/globals/array.rs b/core/src/avm1/globals/array.rs index 5cee1705a..41a1c8551 100644 --- a/core/src/avm1/globals/array.rs +++ b/core/src/avm1/globals/array.rs @@ -1,10 +1,10 @@ //! Array class +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::{Executable, FunctionObject}; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, UpdateContext, Value}; +use crate::avm1::{Object, ScriptObject, TObject, UpdateContext, Value}; use enumset::EnumSet; use gc_arena::MutationContext; use smallvec::alloc::borrow::Cow; @@ -25,7 +25,12 @@ const DEFAULT_ORDERING: Ordering = Ordering::Equal; // Compare function used by sort and sortOn. type CompareFn<'a, 'gc> = Box< dyn 'a - + FnMut(&mut Avm1<'gc>, &mut UpdateContext<'_, 'gc, '_>, &Value<'gc>, &Value<'gc>) -> Ordering, + + FnMut( + &mut Activation<'_, 'gc>, + &mut UpdateContext<'_, 'gc, '_>, + &Value<'gc>, + &Value<'gc>, + ) -> Ordering, >; pub fn create_array_object<'gc>( @@ -84,16 +89,16 @@ pub fn create_array_object<'gc>( /// Implements `Array` pub fn constructor<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let mut consumed = false; if args.len() == 1 { let arg = args.get(0).unwrap(); - if let Ok(length) = arg.coerce_to_f64(avm, context) { + if let Ok(length) = arg.coerce_to_f64(activation, context) { if length >= 0.0 { this.set_length(context.gc_context, length as usize); consumed = true; @@ -115,15 +120,15 @@ pub fn constructor<'gc>( this.set_length(context.gc_context, length); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn push<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let old_length = this.length(); let new_length = old_length + args.len(); this.set_length(context.gc_context, new_length); @@ -140,11 +145,11 @@ pub fn push<'gc>( } pub fn unshift<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let old_length = this.length(); let new_length = old_length + args.len(); let offset = new_length - old_length; @@ -165,14 +170,14 @@ pub fn unshift<'gc>( } pub fn shift<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let old_length = this.length(); if old_length == 0 { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } let new_length = old_length - 1; @@ -184,41 +189,41 @@ pub fn shift<'gc>( } this.delete_array_element(new_length, context.gc_context); - this.delete(avm, context.gc_context, &new_length.to_string()); + this.delete(activation, context.gc_context, &new_length.to_string()); this.set_length(context.gc_context, new_length); - Ok(removed.into()) + Ok(removed) } pub fn pop<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let old_length = this.length(); if old_length == 0 { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } let new_length = old_length - 1; let removed = this.array_element(new_length); this.delete_array_element(new_length, context.gc_context); - this.delete(avm, context.gc_context, &new_length.to_string()); + this.delete(activation, context.gc_context, &new_length.to_string()); this.set_length(context.gc_context, new_length); - Ok(removed.into()) + Ok(removed) } pub fn reverse<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let length = this.length(); let mut values = this.array().to_vec(); @@ -226,25 +231,25 @@ pub fn reverse<'gc>( this.set_array_element(i, values.pop().unwrap(), context.gc_context); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn join<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let separator = args .get(0) - .and_then(|v| v.coerce_to_string(avm, context).ok()) + .and_then(|v| v.coerce_to_string(activation, context).ok()) .unwrap_or_else(|| Cow::Borrowed(",")); let values: Vec> = this.array(); Ok(values .iter() .map(|v| { - v.coerce_to_string(avm, context) + v.coerce_to_string(activation, context) .unwrap_or_else(|_| Cow::Borrowed("undefined")) }) .collect::>>() @@ -264,23 +269,23 @@ fn make_index_absolute(mut index: i32, length: usize) -> usize { } pub fn slice<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let start = args .get(0) - .and_then(|v| v.coerce_to_f64(avm, context).ok()) + .and_then(|v| v.coerce_to_f64(activation, context).ok()) .map(|v| make_index_absolute(v as i32, this.length())) .unwrap_or(0); let end = args .get(1) - .and_then(|v| v.coerce_to_f64(avm, context).ok()) + .and_then(|v| v.coerce_to_f64(activation, context).ok()) .map(|v| make_index_absolute(v as i32, this.length())) .unwrap_or_else(|| this.length()); - let array = ScriptObject::array(context.gc_context, Some(avm.prototypes.array)); + let array = ScriptObject::array(context.gc_context, Some(activation.avm().prototypes.array)); if start < end { let length = end - start; @@ -295,31 +300,31 @@ pub fn slice<'gc>( } pub fn splice<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if args.is_empty() { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } let old_length = this.length(); let start = args .get(0) - .and_then(|v| v.coerce_to_f64(avm, context).ok()) + .and_then(|v| v.coerce_to_f64(activation, context).ok()) .map(|v| make_index_absolute(v as i32, old_length)) .unwrap_or(0); let count = args .get(1) - .and_then(|v| v.coerce_to_f64(avm, context).ok()) + .and_then(|v| v.coerce_to_f64(activation, context).ok()) .map(|v| v as i32) .unwrap_or(old_length as i32); if count < 0 { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } - let removed = ScriptObject::array(context.gc_context, Some(avm.prototypes.array)); + let removed = ScriptObject::array(context.gc_context, Some(activation.avm().prototypes.array)); let to_remove = count.min(old_length as i32 - start as i32).max(0) as usize; let to_add = if args.len() > 2 { &args[2..] } else { &[] }; let offset = to_remove as i32 - to_add.len() as i32; @@ -358,7 +363,7 @@ pub fn splice<'gc>( for i in new_length..old_length { this.delete_array_element(i, context.gc_context); - this.delete(avm, context.gc_context, &i.to_string()); + this.delete(activation, context.gc_context, &i.to_string()); } this.set_length(context.gc_context, new_length); @@ -367,17 +372,17 @@ pub fn splice<'gc>( } pub fn concat<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let array = ScriptObject::array(context.gc_context, Some(avm.prototypes.array)); +) -> Result, Error<'gc>> { + let array = ScriptObject::array(context.gc_context, Some(activation.avm().prototypes.array)); let mut length = 0; for i in 0..this.length() { let old = this - .get(&i.to_string(), avm, context) + .get(&i.to_string(), activation, context) .unwrap_or(Value::Undefined); array.define_value( context.gc_context, @@ -393,11 +398,11 @@ pub fn concat<'gc>( if let Value::Object(object) = arg { let object = *object; - if avm.prototypes.array.is_prototype_of(object) { + if activation.avm().prototypes.array.is_prototype_of(object) { added = true; for i in 0..object.length() { let old = object - .get(&i.to_string(), avm, context) + .get(&i.to_string(), activation, context) .unwrap_or(Value::Undefined); array.define_value( context.gc_context, @@ -427,20 +432,20 @@ pub fn concat<'gc>( } pub fn to_string<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - join(avm, context, this, &[]) +) -> Result, Error<'gc>> { + join(activation, context, this, &[]) } fn sort<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { // Overloads: // 1) a.sort(flags: Number = 0): Sorts with the given flags. // 2) a.sort(compare_fn: Object, flags: Number = 0): Sorts using the given compare function and flags. @@ -453,7 +458,7 @@ fn sort<'gc>( } [compare_fn @ Value::Object(_), ..] => (Some(compare_fn), 0), [] => (None, 0), - _ => return Ok(Value::Undefined.into()), + _ => return Ok(Value::Undefined), }; let numeric = (flags & NUMERIC) != 0; @@ -466,10 +471,11 @@ fn sort<'gc>( }; let compare_fn: CompareFn<'_, 'gc> = if let Some(f) = compare_fn { - let this = crate::avm1::value_object::ValueObject::boxed(avm, context, Value::Undefined); + let this = + crate::avm1::value_object::ValueObject::boxed(activation, context, Value::Undefined); // this is undefined in the compare function - Box::new(move |avm, context, a: &Value<'gc>, b: &Value<'gc>| { - sort_compare_custom(avm, context, this, a, b, &f) + Box::new(move |activation, context, a: &Value<'gc>, b: &Value<'gc>| { + sort_compare_custom(activation, context, this, a, b, &f) }) } else if numeric { Box::new(sort_compare_numeric(string_compare_fn)) @@ -477,15 +483,15 @@ fn sort<'gc>( Box::new(string_compare_fn) }; - sort_with_function(avm, context, this, compare_fn, flags) + sort_with_function(activation, context, this, compare_fn, flags) } fn sort_on<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { // a.sortOn(field_name, flags: Number = 0): Sorts with the given flags. // a.sortOn(field_names: Array, flags: Number = 0): Sorts with fields in order of precedence with the given flags. // a.sortOn(field_names: Array, flags: Array: Sorts with fields in order of precedence with the given flags respectively. @@ -494,15 +500,17 @@ fn sort_on<'gc>( // Array of field names. let mut field_names = vec![]; for name in array.array() { - field_names.push(name.coerce_to_string(avm, context)?.to_string()); + field_names.push(name.coerce_to_string(activation, context)?.to_string()); } field_names } Some(field_name) => { // Single field. - vec![field_name.coerce_to_string(avm, context)?.to_string()] + vec![field_name + .coerce_to_string(activation, context)? + .to_string()] } - None => return Ok(Value::Undefined.into()), + None => return Ok(Value::Undefined), }; // Bail out if we don't have any fields. @@ -516,7 +524,7 @@ fn sort_on<'gc>( if array.length() == fields.len() { let mut flags = vec![]; for flag in array.array() { - flags.push(flag.coerce_to_i32(avm, context)?); + flags.push(flag.coerce_to_i32(activation, context)?); } flags } else { @@ -526,7 +534,7 @@ fn sort_on<'gc>( } Some(flags) => { // Single field. - let flags = flags.coerce_to_i32(avm, context)?; + let flags = flags.coerce_to_i32(activation, context)?; std::iter::repeat(flags).take(fields.len()).collect() } None => std::iter::repeat(0).take(fields.len()).collect(), @@ -558,24 +566,24 @@ fn sort_on<'gc>( let compare_fn = sort_compare_fields(fields, field_compare_fns); - sort_with_function(avm, context, this, compare_fn, main_flags) + sort_with_function(activation, context, this, compare_fn, main_flags) } fn sort_with_function<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, mut compare_fn: impl FnMut( - &mut Avm1<'gc>, + &mut Activation<'_, 'gc>, &mut UpdateContext<'_, 'gc, '_>, &Value<'gc>, &Value<'gc>, ) -> Ordering, flags: i32, -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let length = this.length(); let mut values: Vec<(usize, Value<'gc>)> = this.array().into_iter().enumerate().collect(); - let array_proto = avm.prototypes.array; + let array_proto = activation.avm().prototypes.array; let descending = (flags & DESCENDING) != 0; let unique_sort = (flags & UNIQUE_SORT) != 0; @@ -583,7 +591,7 @@ fn sort_with_function<'gc>( let mut is_unique = true; values.sort_unstable_by(|a, b| { - let mut ret = compare_fn(avm, context, &a.1, &b.1); + let mut ret = compare_fn(activation, context, &a.1, &b.1); if descending { ret = ret.reverse(); } @@ -710,13 +718,13 @@ pub fn create_proto<'gc>( } fn sort_compare_string<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, a: &Value<'gc>, b: &Value<'gc>, ) -> Ordering { - let a_str = a.coerce_to_string(avm, context); - let b_str = b.coerce_to_string(avm, context); + let a_str = a.coerce_to_string(activation, context); + let b_str = b.coerce_to_string(activation, context); // TODO: Handle errors. if let (Ok(a_str), Ok(b_str)) = (a_str, b_str) { a_str.cmp(&b_str) @@ -726,13 +734,13 @@ fn sort_compare_string<'gc>( } fn sort_compare_string_ignore_case<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, a: &Value<'gc>, b: &Value<'gc>, ) -> Ordering { - let a_str = a.coerce_to_string(avm, context); - let b_str = b.coerce_to_string(avm, context); + let a_str = a.coerce_to_string(activation, context); + let b_str = b.coerce_to_string(activation, context); // TODO: Handle errors. if let (Ok(a_str), Ok(b_str)) = (a_str, b_str) { crate::string_utils::swf_string_cmp_ignore_case(&a_str, &b_str) @@ -743,18 +751,22 @@ fn sort_compare_string_ignore_case<'gc>( fn sort_compare_numeric<'gc>( mut string_compare_fn: impl FnMut( - &mut Avm1<'gc>, + &mut Activation<'_, 'gc>, &mut UpdateContext<'_, 'gc, '_>, &Value<'gc>, &Value<'gc>, ) -> Ordering, -) -> impl FnMut(&mut Avm1<'gc>, &mut UpdateContext<'_, 'gc, '_>, &Value<'gc>, &Value<'gc>) -> Ordering -{ - move |avm, context, a, b| { +) -> impl FnMut( + &mut Activation<'_, 'gc>, + &mut UpdateContext<'_, 'gc, '_>, + &Value<'gc>, + &Value<'gc>, +) -> Ordering { + move |activation, context, a, b| { if let (Value::Number(a), Value::Number(b)) = (a, b) { a.partial_cmp(b).unwrap_or(DEFAULT_ORDERING) } else { - string_compare_fn(avm, context, a, b) + string_compare_fn(activation, context, a, b) } } } @@ -762,17 +774,22 @@ fn sort_compare_numeric<'gc>( fn sort_compare_fields<'a, 'gc: 'a>( field_names: Vec, mut compare_fns: Vec>, -) -> impl 'a + FnMut(&mut Avm1<'gc>, &mut UpdateContext<'_, 'gc, '_>, &Value<'gc>, &Value<'gc>) -> Ordering -{ +) -> impl 'a + + FnMut( + &mut Activation<'_, 'gc>, + &mut UpdateContext<'_, 'gc, '_>, + &Value<'gc>, + &Value<'gc>, +) -> Ordering { use crate::avm1::value_object::ValueObject; - move |avm, context, a, b| { + move |activation, context, a, b| { for (field_name, compare_fn) in field_names.iter().zip(compare_fns.iter_mut()) { - let a_object = ValueObject::boxed(avm, context, a.clone()); - let b_object = ValueObject::boxed(avm, context, b.clone()); - let a_prop = a_object.get(field_name, avm, context).unwrap(); - let b_prop = b_object.get(field_name, avm, context).unwrap(); + let a_object = ValueObject::boxed(activation, context, a.clone()); + let b_object = ValueObject::boxed(activation, context, b.clone()); + let a_prop = a_object.get(field_name, activation, context).unwrap(); + let b_prop = b_object.get(field_name, activation, context).unwrap(); - let result = compare_fn(avm, context, &a_prop, &b_prop); + let result = compare_fn(activation, context, &a_prop, &b_prop); if result != Ordering::Equal { return result; } @@ -784,7 +801,7 @@ fn sort_compare_fields<'a, 'gc: 'a>( // Returning an impl Trait here doesn't work yet because of https://github.com/rust-lang/rust/issues/65805 (?) fn sort_compare_custom<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, a: &Value<'gc>, @@ -794,7 +811,7 @@ fn sort_compare_custom<'gc>( // TODO: Handle errors. let args = [a.clone(), b.clone()]; let ret = compare_fn - .call(avm, context, this, None, &args) + .call(activation, context, this, None, &args) .unwrap_or(Value::Undefined); match ret { Value::Number(n) if n > 0.0 => Ordering::Greater, diff --git a/core/src/avm1/globals/boolean.rs b/core/src/avm1/globals/boolean.rs index 33fd064de..2a06bd67a 100644 --- a/core/src/avm1/globals/boolean.rs +++ b/core/src/avm1/globals/boolean.rs @@ -1,23 +1,23 @@ //! `Boolean` class impl +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::{Executable, FunctionObject}; -use crate::avm1::return_value::ReturnValue; use crate::avm1::value_object::ValueObject; -use crate::avm1::{Avm1, Object, TObject, Value}; +use crate::avm1::{Object, TObject, Value}; use crate::context::UpdateContext; use enumset::EnumSet; use gc_arena::MutationContext; /// `Boolean` constructor/function pub fn boolean<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let (ret_value, cons_value) = if let Some(val) = args.get(0) { - let b = Value::Bool(val.as_bool(avm.current_swf_version())); + let b = Value::Bool(val.as_bool(activation.current_swf_version())); (b.clone(), b) } else { (Value::Undefined, Value::Bool(false)) @@ -30,7 +30,7 @@ pub fn boolean<'gc>( // If called as a function, return the value. // Boolean() with no argument returns undefined. - Ok(ret_value.into()) + Ok(ret_value) } pub fn create_boolean_object<'gc>( @@ -74,11 +74,11 @@ pub fn create_proto<'gc>( } pub fn to_string<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(vbox) = this.as_value_object() { // Must be a bool. // Boolean.prototype.toString.call(x) returns undefined for non-bools. @@ -87,15 +87,15 @@ pub fn to_string<'gc>( } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn value_of<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(vbox) = this.as_value_object() { // Must be a bool. // Boolean.prototype.valueOf.call(x) returns undefined for non-bools. @@ -104,5 +104,5 @@ pub fn value_of<'gc>( } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } diff --git a/core/src/avm1/globals/button.rs b/core/src/avm1/globals/button.rs index bbaef7c69..39befe0b4 100644 --- a/core/src/avm1/globals/button.rs +++ b/core/src/avm1/globals/button.rs @@ -1,9 +1,9 @@ //! Button/SimpleButton prototype +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::globals::display_object; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, UpdateContext, Value}; +use crate::avm1::{Object, ScriptObject, UpdateContext, Value}; use gc_arena::MutationContext; pub fn create_proto<'gc>( @@ -20,10 +20,10 @@ pub fn create_proto<'gc>( /// Implements `Button` constructor. pub fn constructor<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(Value::Undefined.into()) +) -> Result, Error<'gc>> { + Ok(Value::Undefined) } diff --git a/core/src/avm1/globals/color.rs b/core/src/avm1/globals/color.rs index 0f6aa7da0..8ebc2e228 100644 --- a/core/src/avm1/globals/color.rs +++ b/core/src/avm1/globals/color.rs @@ -3,24 +3,24 @@ //! TODO: This should change when `ColorTransform` changes to match Flash's representation //! (See GitHub #193) +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::property::Attribute::*; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, UpdateContext, Value}; +use crate::avm1::{Object, ScriptObject, TObject, UpdateContext, Value}; use crate::display_object::{DisplayObject, TDisplayObject}; use enumset::EnumSet; use gc_arena::MutationContext; pub fn constructor<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, mut this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { // The target display object that this color will modify. let target = args.get(0).cloned().unwrap_or(Value::Undefined); // Set undocumented `target` property - this.set("target", target, avm, context)?; + this.set("target", target, activation, context)?; this.set_attributes( context.gc_context, Some("target"), @@ -28,7 +28,7 @@ pub fn constructor<'gc>( EnumSet::empty(), ); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn create_proto<'gc>( @@ -75,75 +75,116 @@ pub fn create_proto<'gc>( /// Gets the target display object of this color transform. fn target<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, ) -> Result>, Error<'gc>> { // The target path resolves based on the active tellTarget clip of the stack frame. // This means calls on the same `Color` object could set the color of different clips // depending on which timeline its called from! - let target = this.get("target", avm, context)?; + let target = this.get("target", activation, context)?; // Undefined or empty target is no-op. if target != Value::Undefined && !matches!(&target, &Value::String(ref s) if s.is_empty()) { - let start_clip = avm.target_clip_or_root(); - avm.resolve_target_display_object(context, start_clip, target) + let start_clip = activation.target_clip_or_root(); + activation.resolve_target_display_object(context, start_clip, target) } else { Ok(None) } } fn get_rgb<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(target) = target(avm, context, this)? { +) -> Result, Error<'gc>> { + if let Some(target) = target(activation, context, this)? { let color_transform = target.color_transform(); let r = ((color_transform.r_add * 255.0) as i32) << 16; let g = ((color_transform.g_add * 255.0) as i32) << 8; let b = (color_transform.b_add * 255.0) as i32; Ok((r | g | b).into()) } else { - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } fn get_transform<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(target) = target(avm, context, this)? { +) -> Result, Error<'gc>> { + if let Some(target) = target(activation, context, this)? { let color_transform = target.color_transform(); - let out = ScriptObject::object(context.gc_context, Some(avm.prototypes.object)); - out.set("ra", (color_transform.r_mult * 100.0).into(), avm, context)?; - out.set("ga", (color_transform.g_mult * 100.0).into(), avm, context)?; - out.set("ba", (color_transform.b_mult * 100.0).into(), avm, context)?; - out.set("aa", (color_transform.a_mult * 100.0).into(), avm, context)?; - out.set("rb", (color_transform.r_add * 255.0).into(), avm, context)?; - out.set("gb", (color_transform.g_add * 255.0).into(), avm, context)?; - out.set("bb", (color_transform.b_add * 255.0).into(), avm, context)?; - out.set("ab", (color_transform.a_add * 255.0).into(), avm, context)?; + let out = + ScriptObject::object(context.gc_context, Some(activation.avm().prototypes.object)); + out.set( + "ra", + (color_transform.r_mult * 100.0).into(), + activation, + context, + )?; + out.set( + "ga", + (color_transform.g_mult * 100.0).into(), + activation, + context, + )?; + out.set( + "ba", + (color_transform.b_mult * 100.0).into(), + activation, + context, + )?; + out.set( + "aa", + (color_transform.a_mult * 100.0).into(), + activation, + context, + )?; + out.set( + "rb", + (color_transform.r_add * 255.0).into(), + activation, + context, + )?; + out.set( + "gb", + (color_transform.g_add * 255.0).into(), + activation, + context, + )?; + out.set( + "bb", + (color_transform.b_add * 255.0).into(), + activation, + context, + )?; + out.set( + "ab", + (color_transform.a_add * 255.0).into(), + activation, + context, + )?; Ok(out.into()) } else { - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } fn set_rgb<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Some(target) = target(avm, context, this)? { +) -> Result, Error<'gc>> { + if let Some(target) = target(activation, context, this)? { let mut color_transform = target.color_transform_mut(context.gc_context); let rgb = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)? as i32; + .coerce_to_f64(activation, context)? as i32; let r = (((rgb >> 16) & 0xff) as f32) / 255.0; let g = (((rgb >> 8) & 0xff) as f32) / 255.0; let b = ((rgb & 0xff) as f32) / 255.0; @@ -155,67 +196,115 @@ fn set_rgb<'gc>( color_transform.g_add = g; color_transform.b_add = b; } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn set_transform<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { // TODO: These map from the 0-100% range for mult and the -255-255 range for addition used by ActionScript // to the 16-bit range used by the internal representations of the Flash Player. // This will get slightly simpler when we change ColorTransform to the proper representation (see #193). fn set_color_mult<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, transform: Object<'gc>, property: &str, out: &mut f32, ) -> Result<(), Error<'gc>> { // The parameters are set only if the property exists on the object itself (prototype excluded). - if transform.has_own_property(avm, context, property) { + if transform.has_own_property(activation, context, property) { let n = transform - .get(property, avm, context)? - .coerce_to_f64(avm, context)?; + .get(property, activation, context)? + .coerce_to_f64(activation, context)?; *out = f32::from(crate::avm1::value::f64_to_wrapping_i16(n * 2.56)) / 256.0 } Ok(()) } fn set_color_add<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, transform: Object<'gc>, property: &str, out: &mut f32, ) -> Result<(), Error<'gc>> { // The parameters are set only if the property exists on the object itself (prototype excluded). - if transform.has_own_property(avm, context, property) { + if transform.has_own_property(activation, context, property) { let n = transform - .get(property, avm, context)? - .coerce_to_f64(avm, context)?; + .get(property, activation, context)? + .coerce_to_f64(activation, context)?; *out = f32::from(crate::avm1::value::f64_to_wrapping_i16(n)) / 255.0 } Ok(()) } - if let Some(target) = target(avm, context, this)? { + if let Some(target) = target(activation, context, this)? { let mut color_transform = target.color_transform_mut(context.gc_context); let transform = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_object(avm, context); - set_color_mult(avm, context, transform, "ra", &mut color_transform.r_mult)?; - set_color_mult(avm, context, transform, "ga", &mut color_transform.g_mult)?; - set_color_mult(avm, context, transform, "ba", &mut color_transform.b_mult)?; - set_color_mult(avm, context, transform, "aa", &mut color_transform.a_mult)?; - set_color_add(avm, context, transform, "rb", &mut color_transform.r_add)?; - set_color_add(avm, context, transform, "gb", &mut color_transform.g_add)?; - set_color_add(avm, context, transform, "bb", &mut color_transform.b_add)?; - set_color_add(avm, context, transform, "ab", &mut color_transform.a_add)?; + .coerce_to_object(activation, context); + set_color_mult( + activation, + context, + transform, + "ra", + &mut color_transform.r_mult, + )?; + set_color_mult( + activation, + context, + transform, + "ga", + &mut color_transform.g_mult, + )?; + set_color_mult( + activation, + context, + transform, + "ba", + &mut color_transform.b_mult, + )?; + set_color_mult( + activation, + context, + transform, + "aa", + &mut color_transform.a_mult, + )?; + set_color_add( + activation, + context, + transform, + "rb", + &mut color_transform.r_add, + )?; + set_color_add( + activation, + context, + transform, + "gb", + &mut color_transform.g_add, + )?; + set_color_add( + activation, + context, + transform, + "bb", + &mut color_transform.b_add, + )?; + set_color_add( + activation, + context, + transform, + "ab", + &mut color_transform.a_add, + )?; } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } diff --git a/core/src/avm1/globals/display_object.rs b/core/src/avm1/globals/display_object.rs index 413d32b22..6da216cb2 100644 --- a/core/src/avm1/globals/display_object.rs +++ b/core/src/avm1/globals/display_object.rs @@ -1,10 +1,10 @@ //! DisplayObject common methods +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::property::Attribute::*; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, UpdateContext, Value}; +use crate::avm1::{Object, ScriptObject, TObject, UpdateContext, Value}; use crate::display_object::{DisplayObject, TDisplayObject}; use enumset::EnumSet; use gc_arena::MutationContext; @@ -24,11 +24,11 @@ macro_rules! with_display_object { $( $object.force_set_function( $name, - |avm, context: &mut UpdateContext<'_, 'gc, '_>, this, args| -> Result, Error<'gc>> { + |activation, context: &mut UpdateContext<'_, 'gc, '_>, this, args| -> Result, Error<'gc>> { if let Some(display_object) = this.as_display_object() { - return $fn(display_object, avm, context, args); + return $fn(display_object, activation, context, args); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } as crate::avm1::function::NativeFunction<'gc>, $gc_context, DontDelete | ReadOnly | DontEnum, @@ -54,7 +54,9 @@ pub fn define_display_object_proto<'gc>( object.add_property( gc_context, "_global", - Executable::Native(|avm, context, _this, _args| Ok(avm.global_object(context).into())), + Executable::Native(|activation, context, _this, _args| { + Ok(activation.avm().global_object(context)) + }), Some(Executable::Native(overwrite_global)), DontDelete | ReadOnly | DontEnum, ); @@ -62,7 +64,7 @@ pub fn define_display_object_proto<'gc>( object.add_property( gc_context, "_root", - Executable::Native(|avm, context, _this, _args| Ok(avm.root_object(context).into())), + Executable::Native(|activation, context, _this, _args| Ok(activation.root_object(context))), Some(Executable::Native(overwrite_root)), DontDelete | ReadOnly | DontEnum, ); @@ -77,60 +79,59 @@ pub fn define_display_object_proto<'gc>( } pub fn get_parent<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(this .as_display_object() .and_then(|mc| mc.parent()) - .map(|dn| dn.object().coerce_to_object(avm, context)) + .map(|dn| dn.object().coerce_to_object(activation, context)) .map(Value::Object) - .unwrap_or(Value::Undefined) - .into()) + .unwrap_or(Value::Undefined)) } pub fn get_depth<'gc>( display_object: DisplayObject<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if avm.current_swf_version() >= 6 { +) -> Result, Error<'gc>> { + if activation.current_swf_version() >= 6 { let depth = display_object.depth().wrapping_sub(AVM_DEPTH_BIAS); Ok(depth.into()) } else { - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } pub fn overwrite_root<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let new_val = args .get(0) .map(|v| v.to_owned()) .unwrap_or(Value::Undefined); this.define_value(ac.gc_context, "_root", new_val, EnumSet::new()); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn overwrite_global<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let new_val = args .get(0) .map(|v| v.to_owned()) .unwrap_or(Value::Undefined); this.define_value(ac.gc_context, "_global", new_val, EnumSet::new()); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } diff --git a/core/src/avm1/globals/function.rs b/core/src/avm1/globals/function.rs index f6bf168cc..3edba9439 100644 --- a/core/src/avm1/globals/function.rs +++ b/core/src/avm1/globals/function.rs @@ -1,31 +1,31 @@ //! Function prototype +use crate::avm1::activation::Activation; use crate::avm1::error::Error; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, UpdateContext, Value}; +use crate::avm1::{Object, ScriptObject, TObject, UpdateContext, Value}; use enumset::EnumSet; use gc_arena::MutationContext; /// Implements `Function` pub fn constructor<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(Value::Undefined.into()) +) -> Result, Error<'gc>> { + Ok(Value::Undefined) } /// Implements `Function.prototype.call` pub fn call<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, func: Object<'gc>, myargs: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let this = match myargs.get(0) { Some(Value::Object(this)) => *this, - _ => avm.globals, + _ => activation.avm().globals, }; let empty = []; let args = match myargs.len() { @@ -35,52 +35,52 @@ pub fn call<'gc>( }; match func.as_executable() { - Some(exec) => exec.exec(avm, action_context, this, None, args), - _ => Ok(Value::Undefined.into()), + Some(exec) => exec.exec(activation, action_context, this, None, args), + _ => Ok(Value::Undefined), } } /// Implements `Function.prototype.apply` pub fn apply<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, func: Object<'gc>, myargs: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let this = match myargs.get(0) { Some(Value::Object(this)) => *this, - _ => avm.globals, + _ => activation.avm().globals, }; let mut child_args = Vec::new(); let args_object = myargs.get(1).cloned().unwrap_or(Value::Undefined); let length = match args_object { Value::Object(a) => a - .get("length", avm, action_context)? - .coerce_to_f64(avm, action_context)? as usize, + .get("length", activation, action_context)? + .coerce_to_f64(activation, action_context)? as usize, _ => 0, }; while child_args.len() < length { - let args = args_object.coerce_to_object(avm, action_context); - let next_arg = args.get(&format!("{}", child_args.len()), avm, action_context)?; + let args = args_object.coerce_to_object(activation, action_context); + let next_arg = args.get(&format!("{}", child_args.len()), activation, action_context)?; child_args.push(next_arg); } match func.as_executable() { - Some(exec) => exec.exec(avm, action_context, this, None, &child_args), - _ => Ok(Value::Undefined.into()), + Some(exec) => exec.exec(activation, action_context, this, None, &child_args), + _ => Ok(Value::Undefined), } } /// Implements `Function.prototype.toString` fn to_string<'gc>( - _: &mut Avm1<'gc>, + _: &mut Activation<'_, 'gc>, _: &mut UpdateContext<'_, 'gc, '_>, _: Object<'gc>, _: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(ReturnValue::Immediate("[type Function]".into())) +) -> Result, Error<'gc>> { + Ok("[type Function]".into()) } /// Partially construct `Function.prototype`. diff --git a/core/src/avm1/globals/key.rs b/core/src/avm1/globals/key.rs index b2e95f933..8617784a7 100644 --- a/core/src/avm1/globals/key.rs +++ b/core/src/avm1/globals/key.rs @@ -1,21 +1,20 @@ +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, UpdateContext, Value}; - +use crate::avm1::{Object, ScriptObject, TObject, UpdateContext, Value}; use crate::events::KeyCode; use gc_arena::MutationContext; use std::convert::TryFrom; pub fn is_down<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(key) = args .get(0) - .and_then(|v| v.coerce_to_f64(avm, context).ok()) + .and_then(|v| v.coerce_to_f64(activation, context).ok()) .and_then(|k| KeyCode::try_from(k as u8).ok()) { Ok(context.input.is_key_down(key).into()) @@ -25,11 +24,11 @@ pub fn is_down<'gc>( } pub fn get_code<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let code: u8 = context.input.get_last_key_code().into(); Ok(code.into()) } diff --git a/core/src/avm1/globals/math.rs b/core/src/avm1/globals/math.rs index 877639013..d3526a598 100644 --- a/core/src/avm1/globals/math.rs +++ b/core/src/avm1/globals/math.rs @@ -1,8 +1,8 @@ +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::object::Object; use crate::avm1::property::Attribute::*; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, ScriptObject, TObject, UpdateContext, Value}; +use crate::avm1::{ScriptObject, TObject, UpdateContext, Value}; use gc_arena::MutationContext; use rand::Rng; use std::f64::{INFINITY, NAN, NEG_INFINITY}; @@ -12,9 +12,9 @@ macro_rules! wrap_std { $( $object.force_set_function( $name, - |avm, context, _this, args| -> Result, Error<'gc>> { + |activation, context, _this, args| -> Result, Error<'gc>> { if let Some(input) = args.get(0) { - Ok($std(input.coerce_to_f64(avm, context)?).into()) + Ok($std(input.coerce_to_f64(activation, context)?).into()) } else { Ok(NAN.into()) } @@ -28,50 +28,50 @@ macro_rules! wrap_std { } fn atan2<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(y) = args.get(0) { if let Some(x) = args.get(1) { return Ok(y - .coerce_to_f64(avm, context)? - .atan2(x.coerce_to_f64(avm, context)?) + .coerce_to_f64(activation, context)? + .atan2(x.coerce_to_f64(activation, context)?) .into()); } else { - return Ok(y.coerce_to_f64(avm, context)?.atan2(0.0).into()); + return Ok(y.coerce_to_f64(activation, context)?.atan2(0.0).into()); } } Ok(NAN.into()) } fn pow<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(y) = args.get(0) { if let Some(x) = args.get(1) { - let x = x.coerce_to_f64(avm, context)?; + let x = x.coerce_to_f64(activation, context)?; if x.is_nan() { return Ok(NAN.into()); } - return Ok(y.coerce_to_f64(avm, context)?.powf(x).into()); + return Ok(y.coerce_to_f64(activation, context)?.powf(x).into()); } } Ok(NAN.into()) } fn round<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(x) = args.get(0) { - let x = x.coerce_to_f64(avm, context)?; + let x = x.coerce_to_f64(activation, context)?; // Note that Flash Math.round always rounds toward infinity, // unlike Rust f32::round which rounds away from zero. let ret = (x + 0.5).floor(); @@ -81,19 +81,19 @@ fn round<'gc>( } fn max<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(a) = args.get(0) { return if let Some(b) = args.get(1) { - match a.abstract_lt(b.to_owned(), avm, context)? { + match a.abstract_lt(b.to_owned(), activation, context)? { Value::Bool(value) => { if value { - Ok(b.coerce_to_f64(avm, context)?.into()) + Ok(b.coerce_to_f64(activation, context)?.into()) } else { - Ok(a.coerce_to_f64(avm, context)?.into()) + Ok(a.coerce_to_f64(activation, context)?.into()) } } _ => Ok(NAN.into()), @@ -106,19 +106,19 @@ fn max<'gc>( } fn min<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(a) = args.get(0) { return if let Some(b) = args.get(1) { - match a.abstract_lt(b.to_owned(), avm, context)? { + match a.abstract_lt(b.to_owned(), activation, context)? { Value::Bool(value) => { if value { - Ok(a.coerce_to_f64(avm, context)?.into()) + Ok(a.coerce_to_f64(activation, context)?.into()) } else { - Ok(b.coerce_to_f64(avm, context)?.into()) + Ok(b.coerce_to_f64(activation, context)?.into()) } } _ => Ok(NAN.into()), @@ -131,11 +131,11 @@ fn min<'gc>( } pub fn random<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(action_context.rng.gen_range(0.0f64, 1.0f64).into()) } @@ -261,11 +261,14 @@ mod tests { use super::*; use crate::avm1::test_utils::with_avm; - fn setup<'gc>(avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) -> Object<'gc> { + fn setup<'gc>( + activation: &mut Activation<'_, 'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + ) -> Object<'gc> { create( context.gc_context, - Some(avm.prototypes().object), - Some(avm.prototypes().function), + Some(activation.avm().prototypes().object), + Some(activation.avm().prototypes().function), ) } @@ -479,24 +482,24 @@ mod tests { #[test] fn test_atan2_nan() { - with_avm(19, |avm, context, _root| -> Result<(), Error> { + with_avm(19, |activation, context, _root| -> Result<(), Error> { let math = create( context.gc_context, - Some(avm.prototypes().object), - Some(avm.prototypes().function), + Some(activation.avm().prototypes().object), + Some(activation.avm().prototypes().function), ); - assert_eq!(atan2(avm, context, math, &[]).unwrap(), NAN.into()); + assert_eq!(atan2(activation, context, math, &[]).unwrap(), NAN.into()); assert_eq!( - atan2(avm, context, math, &[1.0.into(), Value::Null]).unwrap(), + atan2(activation, context, math, &[1.0.into(), Value::Null]).unwrap(), NAN.into() ); assert_eq!( - atan2(avm, context, math, &[1.0.into(), Value::Undefined]).unwrap(), + atan2(activation, context, math, &[1.0.into(), Value::Undefined]).unwrap(), NAN.into() ); assert_eq!( - atan2(avm, context, math, &[Value::Undefined, 1.0.into()]).unwrap(), + atan2(activation, context, math, &[Value::Undefined, 1.0.into()]).unwrap(), NAN.into() ); Ok(()) @@ -505,19 +508,19 @@ mod tests { #[test] fn test_atan2_valid() { - with_avm(19, |avm, context, _root| -> Result<(), Error> { + with_avm(19, |activation, context, _root| -> Result<(), Error> { let math = create( context.gc_context, - Some(avm.prototypes().object), - Some(avm.prototypes().function), + Some(activation.avm().prototypes().object), + Some(activation.avm().prototypes().function), ); assert_eq!( - atan2(avm, context, math, &[10.0.into()]).unwrap(), + atan2(activation, context, math, &[10.0.into()]).unwrap(), std::f64::consts::FRAC_PI_2.into() ); assert_eq!( - atan2(avm, context, math, &[1.0.into(), 2.0.into()]).unwrap(), + atan2(activation, context, math, &[1.0.into(), 2.0.into()]).unwrap(), f64::atan2(1.0, 2.0).into() ); Ok(()) diff --git a/core/src/avm1/globals/matrix.rs b/core/src/avm1/globals/matrix.rs index 083191a9f..815e3fbc0 100644 --- a/core/src/avm1/globals/matrix.rs +++ b/core/src/avm1/globals/matrix.rs @@ -1,9 +1,9 @@ //! flash.geom.Matrix +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::{Executable, FunctionObject}; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, Value}; +use crate::avm1::{Object, ScriptObject, TObject, Value}; use crate::context::UpdateContext; use enumset::EnumSet; use gc_arena::MutationContext; @@ -11,36 +11,36 @@ use swf::{Matrix, Twips}; pub fn value_to_matrix<'gc>( value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result> { let a = value - .coerce_to_object(avm, context) - .get("a", avm, context)? - .coerce_to_f64(avm, context)? as f32; + .coerce_to_object(activation, context) + .get("a", activation, context)? + .coerce_to_f64(activation, context)? as f32; let b = value - .coerce_to_object(avm, context) - .get("b", avm, context)? - .coerce_to_f64(avm, context)? as f32; + .coerce_to_object(activation, context) + .get("b", activation, context)? + .coerce_to_f64(activation, context)? as f32; let c = value - .coerce_to_object(avm, context) - .get("c", avm, context)? - .coerce_to_f64(avm, context)? as f32; + .coerce_to_object(activation, context) + .get("c", activation, context)? + .coerce_to_f64(activation, context)? as f32; let d = value - .coerce_to_object(avm, context) - .get("d", avm, context)? - .coerce_to_f64(avm, context)? as f32; + .coerce_to_object(activation, context) + .get("d", activation, context)? + .coerce_to_f64(activation, context)? as f32; let tx = Twips::from_pixels( value - .coerce_to_object(avm, context) - .get("tx", avm, context)? - .coerce_to_f64(avm, context)?, + .coerce_to_object(activation, context) + .get("tx", activation, context)? + .coerce_to_f64(activation, context)?, ); let ty = Twips::from_pixels( value - .coerce_to_object(avm, context) - .get("ty", avm, context)? - .coerce_to_f64(avm, context)?, + .coerce_to_object(activation, context) + .get("ty", activation, context)? + .coerce_to_f64(activation, context)?, ); Ok(Matrix { a, b, c, d, tx, ty }) @@ -48,19 +48,29 @@ pub fn value_to_matrix<'gc>( pub fn gradient_object_to_matrix<'gc>( object: Object<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result> { if object - .get("matrixType", avm, context)? - .coerce_to_string(avm, context)? + .get("matrixType", activation, context)? + .coerce_to_string(activation, context)? == "box" { - let width = object.get("w", avm, context)?.coerce_to_f64(avm, context)?; - let height = object.get("h", avm, context)?.coerce_to_f64(avm, context)?; - let rotation = object.get("r", avm, context)?.coerce_to_f64(avm, context)?; - let tx = object.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let ty = object.get("y", avm, context)?.coerce_to_f64(avm, context)?; + let width = object + .get("w", activation, context)? + .coerce_to_f64(activation, context)?; + let height = object + .get("h", activation, context)? + .coerce_to_f64(activation, context)?; + let rotation = object + .get("r", activation, context)? + .coerce_to_f64(activation, context)?; + let tx = object + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let ty = object + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; Ok(Matrix::create_gradient_box( width as f32, height as f32, @@ -70,28 +80,36 @@ pub fn gradient_object_to_matrix<'gc>( )) } else { // TODO: You can apparently pass a 3x3 matrix here. Did anybody actually? How does it work? - object_to_matrix(object, avm, context) + object_to_matrix(object, activation, context) } } pub fn object_to_matrix<'gc>( object: Object<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result> { - let a = object.get("a", avm, context)?.coerce_to_f64(avm, context)? as f32; - let b = object.get("b", avm, context)?.coerce_to_f64(avm, context)? as f32; - let c = object.get("c", avm, context)?.coerce_to_f64(avm, context)? as f32; - let d = object.get("d", avm, context)?.coerce_to_f64(avm, context)? as f32; + let a = object + .get("a", activation, context)? + .coerce_to_f64(activation, context)? as f32; + let b = object + .get("b", activation, context)? + .coerce_to_f64(activation, context)? as f32; + let c = object + .get("c", activation, context)? + .coerce_to_f64(activation, context)? as f32; + let d = object + .get("d", activation, context)? + .coerce_to_f64(activation, context)? as f32; let tx = Twips::from_pixels( object - .get("tx", avm, context)? - .coerce_to_f64(avm, context)?, + .get("tx", activation, context)? + .coerce_to_f64(activation, context)?, ); let ty = Twips::from_pixels( object - .get("ty", avm, context)? - .coerce_to_f64(avm, context)?, + .get("ty", activation, context)? + .coerce_to_f64(activation, context)?, ); Ok(Matrix { a, b, c, d, tx, ty }) @@ -101,7 +119,7 @@ pub fn object_to_matrix<'gc>( #[allow(dead_code)] pub fn matrix_to_object<'gc>( matrix: Matrix, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error<'gc>> { let proto = context.system_prototypes.matrix; @@ -113,207 +131,207 @@ pub fn matrix_to_object<'gc>( matrix.tx.to_pixels().into(), matrix.ty.to_pixels().into(), ]; - let object = proto.new(avm, context, proto, &args)?; - let _ = constructor(avm, context, object, &args)?; + let object = proto.new(activation, context, proto, &args)?; + let _ = constructor(activation, context, object, &args)?; Ok(object) } pub fn apply_matrix_to_object<'gc>( matrix: Matrix, object: Object<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error<'gc>> { - object.set("a", matrix.a.into(), avm, context)?; - object.set("b", matrix.b.into(), avm, context)?; - object.set("c", matrix.c.into(), avm, context)?; - object.set("d", matrix.d.into(), avm, context)?; - object.set("tx", matrix.tx.to_pixels().into(), avm, context)?; - object.set("ty", matrix.ty.to_pixels().into(), avm, context)?; + object.set("a", matrix.a.into(), activation, context)?; + object.set("b", matrix.b.into(), activation, context)?; + object.set("c", matrix.c.into(), activation, context)?; + object.set("d", matrix.d.into(), activation, context)?; + object.set("tx", matrix.tx.to_pixels().into(), activation, context)?; + object.set("ty", matrix.ty.to_pixels().into(), activation, context)?; Ok(()) } fn constructor<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if args.is_empty() { - apply_matrix_to_object(Matrix::identity(), this, avm, context)?; + apply_matrix_to_object(Matrix::identity(), this, activation, context)?; } else { if let Some(a) = args.get(0) { - this.set("a", a.clone(), avm, context)?; + this.set("a", a.clone(), activation, context)?; } if let Some(b) = args.get(1) { - this.set("b", b.clone(), avm, context)?; + this.set("b", b.clone(), activation, context)?; } if let Some(c) = args.get(2) { - this.set("c", c.clone(), avm, context)?; + this.set("c", c.clone(), activation, context)?; } if let Some(d) = args.get(3) { - this.set("d", d.clone(), avm, context)?; + this.set("d", d.clone(), activation, context)?; } if let Some(tx) = args.get(4) { - this.set("tx", tx.clone(), avm, context)?; + this.set("tx", tx.clone(), activation, context)?; } if let Some(ty) = args.get(5) { - this.set("ty", ty.clone(), avm, context)?; + this.set("ty", ty.clone(), activation, context)?; } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn identity<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - apply_matrix_to_object(Matrix::identity(), this, avm, context)?; - Ok(Value::Undefined.into()) +) -> Result, Error<'gc>> { + apply_matrix_to_object(Matrix::identity(), this, activation, context)?; + Ok(Value::Undefined) } fn clone<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let proto = context.system_prototypes.matrix; let args = [ - this.get("a", avm, context)?, - this.get("b", avm, context)?, - this.get("c", avm, context)?, - this.get("d", avm, context)?, - this.get("tx", avm, context)?, - this.get("ty", avm, context)?, + this.get("a", activation, context)?, + this.get("b", activation, context)?, + this.get("c", activation, context)?, + this.get("d", activation, context)?, + this.get("tx", activation, context)?, + this.get("ty", activation, context)?, ]; - let cloned = proto.new(avm, context, proto, &args)?; - let _ = constructor(avm, context, cloned, &args)?; + let cloned = proto.new(activation, context, proto, &args)?; + let _ = constructor(activation, context, cloned, &args)?; Ok(cloned.into()) } fn scale<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let scale_x = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let scale_y = args .get(1) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let mut matrix = Matrix::scale(scale_x as f32, scale_y as f32); - matrix *= object_to_matrix(this, avm, context)?; - apply_matrix_to_object(matrix, this, avm, context)?; + matrix *= object_to_matrix(this, activation, context)?; + apply_matrix_to_object(matrix, this, activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn rotate<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let angle = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let mut matrix = Matrix::rotate(angle as f32); - matrix *= object_to_matrix(this, avm, context)?; - apply_matrix_to_object(matrix, this, avm, context)?; + matrix *= object_to_matrix(this, activation, context)?; + apply_matrix_to_object(matrix, this, activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn translate<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let translate_x = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let translate_y = args .get(1) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let mut matrix = Matrix::translate( Twips::from_pixels(translate_x), Twips::from_pixels(translate_y), ); - matrix *= object_to_matrix(this, avm, context)?; - apply_matrix_to_object(matrix, this, avm, context)?; + matrix *= object_to_matrix(this, activation, context)?; + apply_matrix_to_object(matrix, this, activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn concat<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let mut matrix = object_to_matrix(this, avm, context)?; +) -> Result, Error<'gc>> { + let mut matrix = object_to_matrix(this, activation, context)?; let other = value_to_matrix( args.get(0).unwrap_or(&Value::Undefined).clone(), - avm, + activation, context, )?; matrix = other * matrix; - apply_matrix_to_object(matrix, this, avm, context)?; + apply_matrix_to_object(matrix, this, activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn invert<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let mut matrix = object_to_matrix(this, avm, context)?; +) -> Result, Error<'gc>> { + let mut matrix = object_to_matrix(this, activation, context)?; matrix.invert(); - apply_matrix_to_object(matrix, this, avm, context)?; + apply_matrix_to_object(matrix, this, activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn create_box<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let scale_x = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let scale_y = args .get(1) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; // [NA] Docs say rotation is optional and defaults to 0, but that's wrong? let rotation = args .get(2) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let translate_x = if let Some(value) = args.get(3) { - value.coerce_to_f64(avm, context)? + value.coerce_to_f64(activation, context)? } else { 0.0 }; let translate_y = if let Some(value) = args.get(4) { - value.coerce_to_f64(avm, context)? + value.coerce_to_f64(activation, context)? } else { 0.0 }; @@ -325,37 +343,37 @@ fn create_box<'gc>( Twips::from_pixels(translate_x), Twips::from_pixels(translate_y), ); - apply_matrix_to_object(matrix, this, avm, context)?; + apply_matrix_to_object(matrix, this, activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn create_gradient_box<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let width = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let height = args .get(1) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let rotation = if let Some(value) = args.get(2) { - value.coerce_to_f64(avm, context)? + value.coerce_to_f64(activation, context)? } else { 0.0 }; let translate_x = if let Some(value) = args.get(3) { - value.coerce_to_f64(avm, context)? + value.coerce_to_f64(activation, context)? } else { 0.0 }; let translate_y = if let Some(value) = args.get(4) { - value.coerce_to_f64(avm, context)? + value.coerce_to_f64(activation, context)? } else { 0.0 }; @@ -367,32 +385,32 @@ fn create_gradient_box<'gc>( Twips::from_pixels(translate_x), Twips::from_pixels(translate_y), ); - apply_matrix_to_object(matrix, this, avm, context)?; + apply_matrix_to_object(matrix, this, activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn to_string<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let a = this.get("a", avm, context)?; - let b = this.get("b", avm, context)?; - let c = this.get("c", avm, context)?; - let d = this.get("d", avm, context)?; - let tx = this.get("tx", avm, context)?; - let ty = this.get("ty", avm, context)?; +) -> Result, Error<'gc>> { + let a = this.get("a", activation, context)?; + let b = this.get("b", activation, context)?; + let c = this.get("c", activation, context)?; + let d = this.get("d", activation, context)?; + let tx = this.get("tx", activation, context)?; + let ty = this.get("ty", activation, context)?; Ok(format!( "(a={}, b={}, c={}, d={}, tx={}, ty={})", - a.coerce_to_string(avm, context)?, - b.coerce_to_string(avm, context)?, - c.coerce_to_string(avm, context)?, - d.coerce_to_string(avm, context)?, - tx.coerce_to_string(avm, context)?, - ty.coerce_to_string(avm, context)? + a.coerce_to_string(activation, context)?, + b.coerce_to_string(activation, context)?, + c.coerce_to_string(activation, context)?, + d.coerce_to_string(activation, context)?, + tx.coerce_to_string(activation, context)?, + ty.coerce_to_string(activation, context)? ) .into()) } diff --git a/core/src/avm1/globals/mouse.rs b/core/src/avm1/globals/mouse.rs index 701f338cd..108b998ec 100644 --- a/core/src/avm1/globals/mouse.rs +++ b/core/src/avm1/globals/mouse.rs @@ -1,17 +1,16 @@ +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::listeners::Listeners; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, UpdateContext, Value}; - +use crate::avm1::{Object, ScriptObject, TObject, UpdateContext, Value}; use gc_arena::MutationContext; pub fn show_mouse<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let was_visible = context.input.mouse_visible(); context.input.show_mouse(); if was_visible { @@ -22,11 +21,11 @@ pub fn show_mouse<'gc>( } pub fn hide_mouse<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let was_visible = context.input.mouse_visible(); context.input.hide_mouse(); if was_visible { diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index 695fe3312..d3653cfc1 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -1,11 +1,11 @@ //! MovieClip prototype +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::globals::display_object::{self, AVM_DEPTH_BIAS, AVM_MAX_DEPTH}; use crate::avm1::globals::matrix::gradient_object_to_matrix; use crate::avm1::property::Attribute::*; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, UpdateContext, Value}; +use crate::avm1::{Object, ScriptObject, TObject, UpdateContext, Value}; use crate::backend::navigator::NavigationMethod; use crate::display_object::{DisplayObject, EditText, MovieClip, TDisplayObject}; use crate::prelude::*; @@ -19,12 +19,12 @@ use swf::{ /// Implements `MovieClip` pub fn constructor<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(Value::Undefined.into()) +) -> Result, Error<'gc>> { + Ok(Value::Undefined) } macro_rules! with_movie_clip { @@ -32,13 +32,13 @@ macro_rules! with_movie_clip { $( $object.force_set_function( $name, - |avm, context: &mut UpdateContext<'_, 'gc, '_>, this, args| -> Result, Error<'gc>> { + |activation, context: &mut UpdateContext<'_, 'gc, '_>, this, args| -> Result, Error<'gc>> { if let Some(display_object) = this.as_display_object() { if let Some(movie_clip) = display_object.as_movie_clip() { - return $fn(movie_clip, avm, context, args); + return $fn(movie_clip, activation, context, args); } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } as crate::avm1::function::NativeFunction<'gc>, $gc_context, DontDelete | ReadOnly | DontEnum, @@ -51,16 +51,16 @@ macro_rules! with_movie_clip { #[allow(clippy::comparison_chain)] pub fn hit_test<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if args.len() > 1 { - let x = args.get(0).unwrap().coerce_to_f64(avm, context)?; - let y = args.get(1).unwrap().coerce_to_f64(avm, context)?; + let x = args.get(0).unwrap().coerce_to_f64(activation, context)?; + let y = args.get(1).unwrap().coerce_to_f64(activation, context)?; let shape = args .get(2) - .map(|v| v.as_bool(avm.current_swf_version())) + .map(|v| v.as_bool(activation.current_swf_version())) .unwrap_or(false); if shape { log::warn!("Ignoring shape hittest and using bounding box instead. Shape based hit detection is not yet implemented. See https://github.com/ruffle-rs/ruffle/issues/177"); @@ -77,7 +77,7 @@ pub fn hit_test<'gc>( let other = args .get(0) .unwrap() - .coerce_to_object(avm, context) + .coerce_to_object(activation, context) .as_display_object(); if let Some(other) = other { return Ok(other @@ -144,16 +144,24 @@ pub fn create_proto<'gc>( fn line_style<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(width) = args.get(0) { - let width = Twips::from_pixels(width.coerce_to_f64(avm, context)?.min(255.0).max(0.0)); + let width = Twips::from_pixels( + width + .coerce_to_f64(activation, context)? + .min(255.0) + .max(0.0), + ); let color = if let Some(rgb) = args.get(1) { - let rgb = rgb.coerce_to_u32(avm, context)?; + let rgb = rgb.coerce_to_u32(activation, context)?; let alpha = if let Some(alpha) = args.get(2) { - alpha.coerce_to_f64(avm, context)?.min(100.0).max(0.0) + alpha + .coerce_to_f64(activation, context)? + .min(100.0) + .max(0.0) } else { 100.0 } as f32 @@ -165,10 +173,10 @@ fn line_style<'gc>( }; let is_pixel_hinted = args .get(3) - .map_or(false, |v| v.as_bool(avm.current_swf_version())); + .map_or(false, |v| v.as_bool(activation.current_swf_version())); let (allow_scale_x, allow_scale_y) = match args .get(4) - .and_then(|v| v.coerce_to_string(avm, context).ok()) + .and_then(|v| v.coerce_to_string(activation, context).ok()) .as_deref() { Some("normal") => (true, true), @@ -178,7 +186,7 @@ fn line_style<'gc>( }; let cap_style = match args .get(5) - .and_then(|v| v.coerce_to_string(avm, context).ok()) + .and_then(|v| v.coerce_to_string(activation, context).ok()) .as_deref() { Some("square") => LineCapStyle::Square, @@ -187,13 +195,16 @@ fn line_style<'gc>( }; let join_style = match args .get(6) - .and_then(|v| v.coerce_to_string(avm, context).ok()) + .and_then(|v| v.coerce_to_string(activation, context).ok()) .as_deref() { Some("miter") => { if let Some(limit) = args.get(7) { LineJoinStyle::Miter( - limit.coerce_to_f64(avm, context)?.max(0.0).min(255.0) as f32 + limit + .coerce_to_f64(activation, context)? + .max(0.0) + .min(255.0) as f32, ) } else { LineJoinStyle::Miter(3.0) @@ -220,19 +231,22 @@ fn line_style<'gc>( } else { movie_clip.set_line_style(context, None); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn begin_fill<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(rgb) = args.get(0) { - let rgb = rgb.coerce_to_u32(avm, context)?; + let rgb = rgb.coerce_to_u32(activation, context)?; let alpha = if let Some(alpha) = args.get(1) { - alpha.coerce_to_f64(avm, context)?.min(100.0).max(0.0) + alpha + .coerce_to_f64(activation, context)? + .min(100.0) + .max(0.0) } else { 100.0 } as f32 @@ -245,15 +259,15 @@ fn begin_fill<'gc>( } else { movie_clip.set_fill_style(context, None); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn begin_gradient_fill<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let (Some(method), Some(colors), Some(alphas), Some(ratios), Some(matrix)) = ( args.get(0), args.get(1), @@ -261,31 +275,37 @@ fn begin_gradient_fill<'gc>( args.get(3), args.get(4), ) { - let method = method.coerce_to_string(avm, context)?; - let colors = colors.coerce_to_object(avm, context).array(); - let alphas = alphas.coerce_to_object(avm, context).array(); - let ratios = ratios.coerce_to_object(avm, context).array(); - let matrix_object = matrix.coerce_to_object(avm, context); + let method = method.coerce_to_string(activation, context)?; + let colors = colors.coerce_to_object(activation, context).array(); + let alphas = alphas.coerce_to_object(activation, context).array(); + let ratios = ratios.coerce_to_object(activation, context).array(); + let matrix_object = matrix.coerce_to_object(activation, context); if colors.len() != alphas.len() || colors.len() != ratios.len() { log::warn!( "beginGradientFill() received different sized arrays for colors, alphas and ratios" ); - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } let mut records = Vec::with_capacity(colors.len()); for i in 0..colors.len() { - let ratio = ratios[i].coerce_to_f64(avm, context)?.min(255.0).max(0.0); - let rgb = colors[i].coerce_to_u32(avm, context)?; - let alpha = alphas[i].coerce_to_f64(avm, context)?.min(100.0).max(0.0); + let ratio = ratios[i] + .coerce_to_f64(activation, context)? + .min(255.0) + .max(0.0); + let rgb = colors[i].coerce_to_u32(activation, context)?; + let alpha = alphas[i] + .coerce_to_f64(activation, context)? + .min(100.0) + .max(0.0); records.push(GradientRecord { ratio: ratio as u8, color: Color::from_rgb(rgb, (alpha / 100.0 * 255.0) as u8), }); } - let matrix = gradient_object_to_matrix(matrix_object, avm, context)?; + let matrix = gradient_object_to_matrix(matrix_object, activation, context)?; let spread = match args .get(5) - .and_then(|v| v.coerce_to_string(avm, context).ok()) + .and_then(|v| v.coerce_to_string(activation, context).ok()) .as_deref() { Some("reflect") => GradientSpread::Reflect, @@ -294,7 +314,7 @@ fn begin_gradient_fill<'gc>( }; let interpolation = match args .get(6) - .and_then(|v| v.coerce_to_string(avm, context).ok()) + .and_then(|v| v.coerce_to_string(activation, context).ok()) .as_deref() { Some("linearRGB") => GradientInterpolation::LinearRGB, @@ -313,7 +333,7 @@ fn begin_gradient_fill<'gc>( if let Some(focal_point) = args.get(7) { FillStyle::FocalGradient { gradient, - focal_point: focal_point.coerce_to_f64(avm, context)? as f32, + focal_point: focal_point.coerce_to_f64(activation, context)? as f32, } } else { FillStyle::RadialGradient(gradient) @@ -321,25 +341,25 @@ fn begin_gradient_fill<'gc>( } other => { log::warn!("beginGradientFill() received invalid fill type {:?}", other); - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } }; movie_clip.set_fill_style(context, Some(style)); } else { movie_clip.set_fill_style(context, None); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn move_to<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let (Some(x), Some(y)) = (args.get(0), args.get(1)) { - let x = x.coerce_to_f64(avm, context)?; - let y = y.coerce_to_f64(avm, context)?; + let x = x.coerce_to_f64(activation, context)?; + let y = y.coerce_to_f64(activation, context)?; movie_clip.draw_command( context, DrawCommand::MoveTo { @@ -348,18 +368,18 @@ fn move_to<'gc>( }, ); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn line_to<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let (Some(x), Some(y)) = (args.get(0), args.get(1)) { - let x = x.coerce_to_f64(avm, context)?; - let y = y.coerce_to_f64(avm, context)?; + let x = x.coerce_to_f64(activation, context)?; + let y = y.coerce_to_f64(activation, context)?; movie_clip.draw_command( context, DrawCommand::LineTo { @@ -368,22 +388,22 @@ fn line_to<'gc>( }, ); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn curve_to<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let (Some(x1), Some(y1), Some(x2), Some(y2)) = (args.get(0), args.get(1), args.get(2), args.get(3)) { - let x1 = x1.coerce_to_f64(avm, context)?; - let y1 = y1.coerce_to_f64(avm, context)?; - let x2 = x2.coerce_to_f64(avm, context)?; - let y2 = y2.coerce_to_f64(avm, context)?; + let x1 = x1.coerce_to_f64(activation, context)?; + let y1 = y1.coerce_to_f64(activation, context)?; + let x2 = x2.coerce_to_f64(activation, context)?; + let y2 = y2.coerce_to_f64(activation, context)?; movie_clip.draw_command( context, DrawCommand::CurveTo { @@ -394,46 +414,46 @@ fn curve_to<'gc>( }, ); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn end_fill<'gc>( movie_clip: MovieClip<'gc>, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { movie_clip.set_fill_style(context, None); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn clear<'gc>( movie_clip: MovieClip<'gc>, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { movie_clip.clear(context); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn attach_movie<'gc>( mut movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let (export_name, new_instance_name, depth) = match &args[0..3] { [export_name, new_instance_name, depth] => ( - export_name.coerce_to_string(avm, context)?, - new_instance_name.coerce_to_string(avm, context)?, + export_name.coerce_to_string(activation, context)?, + new_instance_name.coerce_to_string(activation, context)?, depth - .coerce_to_i32(avm, context)? + .coerce_to_i32(activation, context)? .wrapping_add(AVM_DEPTH_BIAS), ), _ => { log::error!("MovieClip.attachMovie: Too few parameters"); - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } }; let init_object = args.get(3); @@ -441,7 +461,7 @@ fn attach_movie<'gc>( // TODO: What is the derivation of this max value? It shows up a few times in the AVM... // 2^31 - 16777220 if depth < 0 || depth > AVM_MAX_DEPTH { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } if let Ok(mut new_clip) = context @@ -453,132 +473,141 @@ fn attach_movie<'gc>( // Set name and attach to parent. new_clip.set_name(context.gc_context, &new_instance_name); movie_clip.add_child_from_avm(context, new_clip, depth); - let init_object = init_object.map(|v| v.coerce_to_object(avm, context)); - new_clip.post_instantiation(avm, context, new_clip, init_object, true); - new_clip.run_frame(avm, context); + let init_object = if let Some(Value::Object(init_object)) = init_object { + Some(init_object.to_owned()) + } else { + None + }; + new_clip.post_instantiation(activation.avm(), context, new_clip, init_object, true); + new_clip.run_frame(activation.avm(), context); - Ok(new_clip.object().coerce_to_object(avm, context).into()) + Ok(new_clip + .object() + .coerce_to_object(activation, context) + .into()) } else { log::warn!("Unable to attach '{}'", export_name); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } fn create_empty_movie_clip<'gc>( mut movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let (new_instance_name, depth) = match &args[0..2] { [new_instance_name, depth] => ( - new_instance_name.coerce_to_string(avm, context)?, + new_instance_name.coerce_to_string(activation, context)?, depth - .coerce_to_i32(avm, context)? + .coerce_to_i32(activation, context)? .wrapping_add(AVM_DEPTH_BIAS), ), _ => { log::error!("MovieClip.attachMovie: Too few parameters"); - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } }; // Create empty movie clip. let swf_movie = movie_clip .movie() - .or_else(|| avm.base_clip().movie()) + .or_else(|| activation.base_clip().movie()) .unwrap(); let mut new_clip = MovieClip::new(SwfSlice::empty(swf_movie), context.gc_context); // Set name and attach to parent. new_clip.set_name(context.gc_context, &new_instance_name); movie_clip.add_child_from_avm(context, new_clip.into(), depth); - new_clip.post_instantiation(avm, context, new_clip.into(), None, true); - new_clip.run_frame(avm, context); + new_clip.post_instantiation(activation.avm(), context, new_clip.into(), None, true); + new_clip.run_frame(activation.avm(), context); - Ok(new_clip.object().into()) + Ok(new_clip.object()) } fn create_text_field<'gc>( mut movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let movie = avm.base_clip().movie().unwrap(); +) -> Result, Error<'gc>> { + let movie = activation.base_clip().movie().unwrap(); let instance_name = args.get(0).cloned().unwrap_or(Value::Undefined); let depth = args .get(1) .cloned() .unwrap_or(Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let x = args .get(2) .cloned() .unwrap_or(Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let y = args .get(3) .cloned() .unwrap_or(Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let width = args .get(4) .cloned() .unwrap_or(Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let height = args .get(5) .cloned() .unwrap_or(Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let mut text_field: DisplayObject<'gc> = EditText::new(context, movie, x, y, width, height).into(); text_field.set_name( context.gc_context, - &instance_name.coerce_to_string(avm, context)?, + &instance_name.coerce_to_string(activation, context)?, ); movie_clip.add_child_from_avm( context, text_field, (depth as Depth).wrapping_add(AVM_DEPTH_BIAS), ); - text_field.post_instantiation(avm, context, text_field, None, true); + text_field.post_instantiation(activation.avm(), context, text_field, None, true); - if avm.current_swf_version() >= 8 { + if activation.current_swf_version() >= 8 { //SWF8+ returns the `TextField` instance here - Ok(text_field.object().into()) + Ok(text_field.object()) } else { - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } fn duplicate_movie_clip<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { // duplicateMovieClip method uses biased depth compared to CloneSprite - duplicate_movie_clip_with_bias(movie_clip, avm, context, args, AVM_DEPTH_BIAS) + duplicate_movie_clip_with_bias(movie_clip, activation, context, args, AVM_DEPTH_BIAS) } pub fn duplicate_movie_clip_with_bias<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], depth_bias: i32, -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let (new_instance_name, depth) = match &args[0..2] { [new_instance_name, depth] => ( - new_instance_name.coerce_to_string(avm, context)?, - depth.coerce_to_i32(avm, context)?.wrapping_add(depth_bias), + new_instance_name.coerce_to_string(activation, context)?, + depth + .coerce_to_i32(activation, context)? + .wrapping_add(depth_bias), ), _ => { log::error!("MovieClip.attachMovie: Too few parameters"); - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } }; let init_object = args.get(2); @@ -587,13 +616,13 @@ pub fn duplicate_movie_clip_with_bias<'gc>( let mut parent = if let Some(parent) = movie_clip.parent().and_then(|o| o.as_movie_clip()) { parent } else { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); }; // TODO: What is the derivation of this max value? It shows up a few times in the AVM... // 2^31 - 16777220 if depth < 0 || depth > AVM_MAX_DEPTH { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } if let Ok(mut new_clip) = context @@ -612,44 +641,47 @@ pub fn duplicate_movie_clip_with_bias<'gc>( // TODO: Any other properties we should copy...? // Definitely not ScriptObject properties. - let init_object = init_object.map(|v| v.coerce_to_object(avm, context)); - new_clip.post_instantiation(avm, context, new_clip, init_object, true); - new_clip.run_frame(avm, context); + let init_object = init_object.map(|v| v.coerce_to_object(activation, context)); + new_clip.post_instantiation(activation.avm(), context, new_clip, init_object, true); + new_clip.run_frame(activation.avm(), context); - Ok(new_clip.object().coerce_to_object(avm, context).into()) + Ok(new_clip + .object() + .coerce_to_object(activation, context) + .into()) } else { log::warn!("Unable to duplicate clip '{}'", movie_clip.name()); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } fn get_bytes_loaded<'gc>( _movie_clip: MovieClip<'gc>, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { // TODO find a correct value Ok(1.0.into()) } fn get_bytes_total<'gc>( _movie_clip: MovieClip<'gc>, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { // TODO find a correct value Ok(1.0.into()) } fn get_next_highest_depth<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if avm.current_swf_version() >= 7 { +) -> Result, Error<'gc>> { + if activation.current_swf_version() >= 7 { let depth = std::cmp::max( movie_clip .highest_depth() @@ -659,36 +691,36 @@ fn get_next_highest_depth<'gc>( ); Ok(depth.into()) } else { - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } fn goto_and_play<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - goto_frame(movie_clip, avm, context, args, false, 0) +) -> Result, Error<'gc>> { + goto_frame(movie_clip, activation, context, args, false, 0) } fn goto_and_stop<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - goto_frame(movie_clip, avm, context, args, true, 0) +) -> Result, Error<'gc>> { + goto_frame(movie_clip, activation, context, args, true, 0) } pub fn goto_frame<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], stop: bool, scene_offset: u16, -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { match args.get(0).cloned().unwrap_or(Value::Undefined) { // Goto only runs if n is an integer Value::Number(n) if n.fract() == 0.0 => { @@ -703,57 +735,62 @@ pub fn goto_frame<'gc>( frame = frame.wrapping_sub(1); frame = frame.wrapping_add(i32::from(scene_offset)); if frame >= 0 { - movie_clip.goto_frame(avm, context, frame.saturating_add(1) as u16, stop); + movie_clip.goto_frame( + activation.avm(), + context, + frame.saturating_add(1) as u16, + stop, + ); } } val => { // Coerce to string and search for a frame label. - let frame_label = val.coerce_to_string(avm, context)?; + let frame_label = val.coerce_to_string(activation, context)?; if let Some(mut frame) = movie_clip.frame_label_to_number(&frame_label) { frame = frame.wrapping_add(scene_offset); - movie_clip.goto_frame(avm, context, frame, stop); + movie_clip.goto_frame(activation.avm(), context, frame, stop); } } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn next_frame<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - movie_clip.next_frame(avm, context); - Ok(Value::Undefined.into()) +) -> Result, Error<'gc>> { + movie_clip.next_frame(activation.avm(), context); + Ok(Value::Undefined) } fn play<'gc>( movie_clip: MovieClip<'gc>, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { movie_clip.play(context); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn prev_frame<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - movie_clip.prev_frame(avm, context); - Ok(Value::Undefined.into()) +) -> Result, Error<'gc>> { + movie_clip.prev_frame(activation.avm(), context); + Ok(Value::Undefined) } fn remove_movie_clip<'gc>( movie_clip: MovieClip<'gc>, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { // removeMovieClip method uses biased depth compared to RemoveSprite remove_movie_clip_with_bias(movie_clip, context, AVM_DEPTH_BIAS) } @@ -762,7 +799,7 @@ pub fn remove_movie_clip_with_bias<'gc>( movie_clip: MovieClip<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, depth_bias: i32, -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let depth = movie_clip.depth().wrapping_add(depth_bias); // Can only remove positive depths (when offset by the AVM depth bias). // Generally this prevents you from removing non-dynamically created clips, @@ -773,64 +810,64 @@ pub fn remove_movie_clip_with_bias<'gc>( let mut parent = if let Some(parent) = movie_clip.parent().and_then(|o| o.as_movie_clip()) { parent } else { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); }; parent.remove_child_from_avm(context, movie_clip.into()); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn start_drag<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - crate::avm1::start_drag(movie_clip.into(), avm, context, args); - Ok(Value::Undefined.into()) +) -> Result, Error<'gc>> { + crate::avm1::start_drag(movie_clip.into(), activation, context, args); + Ok(Value::Undefined) } fn stop<'gc>( movie_clip: MovieClip<'gc>, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { movie_clip.stop(context); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn stop_drag<'gc>( _movie_clip: MovieClip<'gc>, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { // It doesn't matter which clip we call this on; it simply stops any active drag. *context.drag_object = None; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn swap_depths<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let arg = args.get(0).cloned().unwrap_or(Value::Undefined); let parent = if let Some(parent) = movie_clip.parent().and_then(|o| o.as_movie_clip()) { parent } else { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); }; let mut depth = None; if let Value::Number(n) = arg { depth = Some(crate::avm1::value::f64_to_wrapping_i32(n).wrapping_add(AVM_DEPTH_BIAS)); } else if let Some(target) = - avm.resolve_target_display_object(context, movie_clip.into(), arg)? + activation.resolve_target_display_object(context, movie_clip.into(), arg)? { if let Some(target_parent) = target.parent() { if DisplayObject::ptr_eq(target_parent, parent.into()) { @@ -846,7 +883,7 @@ fn swap_depths<'gc>( if let Some(depth) = depth { if depth < 0 || depth > AVM_MAX_DEPTH { // Depth out of range; no action. - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } if depth != movie_clip.depth() { @@ -854,36 +891,36 @@ fn swap_depths<'gc>( } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn to_string<'gc>( movie_clip: MovieClip<'gc>, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(movie_clip.path().into()) } fn local_to_global<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Value::Object(point) = args.get(0).unwrap_or(&Value::Undefined) { // localToGlobal does no coercion; it fails if the properties are not numbers. // It does not search the prototype chain. if let (Value::Number(x), Value::Number(y)) = ( - point.get_local("x", avm, context, *point)?, - point.get_local("y", avm, context, *point)?, + point.get_local("x", activation, context, *point)?, + point.get_local("y", activation, context, *point)?, ) { let x = Twips::from_pixels(x); let y = Twips::from_pixels(y); let (out_x, out_y) = movie_clip.local_to_global((x, y)); - point.set("x", out_x.to_pixels().into(), avm, context)?; - point.set("y", out_y.to_pixels().into(), avm, context)?; + point.set("x", out_x.to_pixels().into(), activation, context)?; + point.set("y", out_y.to_pixels().into(), activation, context)?; } else { log::warn!("MovieClip.localToGlobal: Invalid x and y properties"); } @@ -891,21 +928,21 @@ fn local_to_global<'gc>( log::warn!("MovieClip.localToGlobal: Missing point parameter"); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn get_bounds<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let target = match args.get(0) { Some(Value::String(s)) if s.is_empty() => None, Some(Value::Object(o)) if o.as_display_object().is_some() => o.as_display_object(), Some(val) => { - let path = val.coerce_to_string(avm, context)?; - avm.resolve_target_display_object(context, movie_clip.into(), path.into())? + let path = val.coerce_to_string(activation, context)?; + activation.resolve_target_display_object(context, movie_clip.into(), path.into())? } None => Some(movie_clip.into()), }; @@ -926,46 +963,67 @@ fn get_bounds<'gc>( bounds.transform(&bounds_transform) }; - let out = ScriptObject::object(context.gc_context, Some(avm.prototypes.object)); - out.set("xMin", out_bounds.x_min.to_pixels().into(), avm, context)?; - out.set("yMin", out_bounds.y_min.to_pixels().into(), avm, context)?; - out.set("xMax", out_bounds.x_max.to_pixels().into(), avm, context)?; - out.set("yMax", out_bounds.y_max.to_pixels().into(), avm, context)?; + let out = + ScriptObject::object(context.gc_context, Some(activation.avm().prototypes.object)); + out.set( + "xMin", + out_bounds.x_min.to_pixels().into(), + activation, + context, + )?; + out.set( + "yMin", + out_bounds.y_min.to_pixels().into(), + activation, + context, + )?; + out.set( + "xMax", + out_bounds.x_max.to_pixels().into(), + activation, + context, + )?; + out.set( + "yMax", + out_bounds.y_max.to_pixels().into(), + activation, + context, + )?; Ok(out.into()) } else { - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } fn get_rect<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { // TODO: This should get the bounds ignoring strokes. Always equal to or smaller than getBounds. // Just defer to getBounds for now. Will have to store edge_bounds vs. shape_bounds in Graphic. - get_bounds(movie_clip, avm, context, args) + get_bounds(movie_clip, activation, context, args) } fn global_to_local<'gc>( movie_clip: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Value::Object(point) = args.get(0).unwrap_or(&Value::Undefined) { // globalToLocal does no coercion; it fails if the properties are not numbers. // It does not search the prototype chain. if let (Value::Number(x), Value::Number(y)) = ( - point.get_local("x", avm, context, *point)?, - point.get_local("y", avm, context, *point)?, + point.get_local("x", activation, context, *point)?, + point.get_local("y", activation, context, *point)?, ) { let x = Twips::from_pixels(x); let y = Twips::from_pixels(y); let (out_x, out_y) = movie_clip.global_to_local((x, y)); - point.set("x", out_x.to_pixels().into(), avm, context)?; - point.set("y", out_y.to_pixels().into(), avm, context)?; + point.set("x", out_x.to_pixels().into(), activation, context)?; + point.set("y", out_y.to_pixels().into(), activation, context)?; } else { log::warn!("MovieClip.globalToLocal: Invalid x and y properties"); } @@ -973,20 +1031,20 @@ fn global_to_local<'gc>( log::warn!("MovieClip.globalToLocal: Missing point parameter"); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn load_movie<'gc>( target: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let url_val = args.get(0).cloned().unwrap_or(Value::Undefined); - let url = url_val.coerce_to_string(avm, context)?; + let url = url_val.coerce_to_string(activation, context)?; let method = args.get(1).cloned().unwrap_or(Value::Undefined); - let method = NavigationMethod::from_method_str(&method.coerce_to_string(avm, context)?); - let (url, opts) = avm.locals_into_request_options(context, url, method); + let method = NavigationMethod::from_method_str(&method.coerce_to_string(activation, context)?); + let (url, opts) = activation.locals_into_request_options(context, url, method); let fetch = context.navigator.fetch(&url, opts); let process = context.load_manager.load_movie_into_clip( context.player.clone().unwrap(), @@ -997,22 +1055,22 @@ fn load_movie<'gc>( context.navigator.spawn_future(process); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn load_variables<'gc>( target: MovieClip<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let url_val = args.get(0).cloned().unwrap_or(Value::Undefined); - let url = url_val.coerce_to_string(avm, context)?; + let url = url_val.coerce_to_string(activation, context)?; let method = args.get(1).cloned().unwrap_or(Value::Undefined); - let method = NavigationMethod::from_method_str(&method.coerce_to_string(avm, context)?); - let (url, opts) = avm.locals_into_request_options(context, url, method); + let method = NavigationMethod::from_method_str(&method.coerce_to_string(activation, context)?); + let (url, opts) = activation.locals_into_request_options(context, url, method); let fetch = context.navigator.fetch(&url, opts); - let target = target.object().coerce_to_object(avm, context); + let target = target.object().coerce_to_object(activation, context); let process = context .load_manager @@ -1020,17 +1078,17 @@ fn load_variables<'gc>( context.navigator.spawn_future(process); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn unload_movie<'gc>( mut target: MovieClip<'gc>, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { target.unload(context); target.replace_with_movie(context.gc_context, None); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } diff --git a/core/src/avm1/globals/movie_clip_loader.rs b/core/src/avm1/globals/movie_clip_loader.rs index c2e278b11..fc79d8fcf 100644 --- a/core/src/avm1/globals/movie_clip_loader.rs +++ b/core/src/avm1/globals/movie_clip_loader.rs @@ -1,42 +1,45 @@ //! `MovieClipLoader` impl +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::object::TObject; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; use crate::avm1::script_object::ScriptObject; -use crate::avm1::{Avm1, Object, UpdateContext, Value}; +use crate::avm1::{Object, UpdateContext, Value}; use crate::backend::navigator::RequestOptions; use crate::display_object::{DisplayObject, TDisplayObject}; use enumset::EnumSet; use gc_arena::MutationContext; pub fn constructor<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let listeners = ScriptObject::array(context.gc_context, Some(avm.prototypes().array)); +) -> Result, Error<'gc>> { + let listeners = ScriptObject::array( + context.gc_context, + Some(activation.avm().prototypes().array), + ); this.define_value( context.gc_context, "_listeners", Value::Object(listeners.into()), Attribute::DontEnum.into(), ); - listeners.set("0", Value::Object(this), avm, context)?; + listeners.set("0", Value::Object(this), activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn add_listener<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let new_listener = args.get(0).cloned().unwrap_or(Value::Undefined); - let listeners = this.get("_listeners", avm, context)?; + let listeners = this.get("_listeners", activation, context)?; if let Value::Object(listeners) = listeners { let length = listeners.length(); @@ -48,20 +51,20 @@ pub fn add_listener<'gc>( } pub fn remove_listener<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let old_listener = args.get(0).cloned().unwrap_or(Value::Undefined); - let listeners = this.get("_listeners", avm, context)?; + let listeners = this.get("_listeners", activation, context)?; if let Value::Object(listeners) = listeners { let length = listeners.length(); let mut position = None; for i in 0..length { - let other_listener = listeners.get(&format!("{}", i), avm, context)?; + let other_listener = listeners.get(&format!("{}", i), activation, context)?; if old_listener == other_listener { position = Some(i); break; @@ -80,7 +83,7 @@ pub fn remove_listener<'gc>( } listeners.delete_array_element(new_length, context.gc_context); - listeners.delete(avm, context.gc_context, &new_length.to_string()); + listeners.delete(activation, context.gc_context, &new_length.to_string()); listeners.set_length(context.gc_context, new_length); } @@ -91,37 +94,37 @@ pub fn remove_listener<'gc>( } pub fn broadcast_message<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let event_name_val = args.get(0).cloned().unwrap_or(Value::Undefined); - let event_name = event_name_val.coerce_to_string(avm, context)?; + let event_name = event_name_val.coerce_to_string(activation, context)?; let call_args = &args[0..]; - let listeners = this.get("_listeners", avm, context)?; + let listeners = this.get("_listeners", activation, context)?; if let Value::Object(listeners) = listeners { for i in 0..listeners.length() { - let listener = listeners.get(&format!("{}", i), avm, context)?; + let listener = listeners.get(&format!("{}", i), activation, context)?; if let Value::Object(listener) = listener { - listener.call_method(&event_name, call_args, avm, context)?; + listener.call_method(&event_name, call_args, activation, context)?; } } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn load_clip<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let url_val = args.get(0).cloned().unwrap_or(Value::Undefined); - let url = url_val.coerce_to_string(avm, context)?; + let url = url_val.coerce_to_string(activation, context)?; let target = args.get(1).cloned().unwrap_or(Value::Undefined); if let Value::Object(target) = target { @@ -147,11 +150,11 @@ pub fn load_clip<'gc>( } pub fn unload_clip<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let target = args.get(0).cloned().unwrap_or(Value::Undefined); if let Value::Object(target) = target { @@ -170,11 +173,11 @@ pub fn unload_clip<'gc>( } pub fn get_progress<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let target = args.get(0).cloned().unwrap_or(Value::Undefined); if let Value::Object(target) = target { @@ -206,7 +209,7 @@ pub fn get_progress<'gc>( } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn create_proto<'gc>( diff --git a/core/src/avm1/globals/number.rs b/core/src/avm1/globals/number.rs index f84dd9b50..67e3caf20 100644 --- a/core/src/avm1/globals/number.rs +++ b/core/src/avm1/globals/number.rs @@ -1,24 +1,24 @@ //! `Number` class impl +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::{Executable, FunctionObject}; use crate::avm1::property::Attribute::*; -use crate::avm1::return_value::ReturnValue; use crate::avm1::value_object::ValueObject; -use crate::avm1::{Avm1, Object, TObject, Value}; +use crate::avm1::{Object, TObject, Value}; use crate::context::UpdateContext; use enumset::EnumSet; use gc_arena::MutationContext; /// `Number` constructor/function pub fn number<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let value = if let Some(val) = args.get(0) { - val.coerce_to_f64(avm, context)? + val.coerce_to_f64(activation, context)? } else { 0.0 }; @@ -113,27 +113,27 @@ pub fn create_proto<'gc>( } fn to_string<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { // Boxed value must be a number. No coercion. let this = if let Some(vbox) = this.as_value_object() { if let Value::Number(n) = vbox.unbox() { n } else { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } } else { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); }; let radix = { let radix = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; if radix >= 2.0 && radix <= 36.0 { radix as u32 } else { @@ -143,7 +143,9 @@ fn to_string<'gc>( if radix == 10 { // Output number as floating-point decimal. - Ok(Value::from(this).coerce_to_string(avm, context)?.into()) + Ok(Value::from(this) + .coerce_to_string(activation, context)? + .into()) } else if this > -2_147_483_648.0 && this < 2_147_483_648.0 { // Output truncated integer in specified base. let n = this as i32; @@ -184,18 +186,18 @@ fn to_string<'gc>( } fn value_of<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(vbox) = this.as_value_object() { if let Value::Number(n) = vbox.unbox() { return Ok(n.into()); } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } // The values returned by `NaN.toString(radix)` in Flash Player v7+ diff --git a/core/src/avm1/globals/object.rs b/core/src/avm1/globals/object.rs index 24728c27b..c8d0a8462 100644 --- a/core/src/avm1/globals/object.rs +++ b/core/src/avm1/globals/object.rs @@ -1,9 +1,9 @@ //! Object prototype +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::{Executable, FunctionObject}; use crate::avm1::property::Attribute::{self, *}; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, TObject, UpdateContext, Value}; +use crate::avm1::{Object, TObject, UpdateContext, Value}; use crate::character::Character; use enumset::EnumSet; use gc_arena::MutationContext; @@ -11,24 +11,24 @@ use std::borrow::Cow; /// Implements `Object` pub fn constructor<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(Value::Undefined.into()) +) -> Result, Error<'gc>> { + Ok(Value::Undefined) } /// Implements `Object.prototype.addProperty` pub fn add_property<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let name = args .get(0) - .and_then(|v| v.coerce_to_string(avm, context).ok()) + .and_then(|v| v.coerce_to_string(activation, context).ok()) .unwrap_or_else(|| Cow::Borrowed("undefined")); let getter = args.get(1).unwrap_or(&Value::Undefined); let setter = args.get(2).unwrap_or(&Value::Undefined); @@ -39,7 +39,7 @@ pub fn add_property<'gc>( if let Value::Object(set) = setter { if let Some(set_func) = set.as_executable() { this.add_property_with_case( - avm, + activation, context.gc_context, &name, get_func.clone(), @@ -51,7 +51,7 @@ pub fn add_property<'gc>( } } else if let Value::Null = setter { this.add_property_with_case( - avm, + activation, context.gc_context, &name, get_func.clone(), @@ -71,14 +71,16 @@ pub fn add_property<'gc>( /// Implements `Object.prototype.hasOwnProperty` pub fn has_own_property<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(value) = args.get(0) { - let name = value.coerce_to_string(avm, context)?; - Ok(Value::Bool(this.has_own_property(avm, context, &name)).into()) + let name = value.coerce_to_string(activation, context)?; + Ok(Value::Bool( + this.has_own_property(activation, context, &name), + )) } else { Ok(false.into()) } @@ -86,62 +88,62 @@ pub fn has_own_property<'gc>( /// Implements `Object.prototype.toString` fn to_string<'gc>( - _: &mut Avm1<'gc>, + _: &mut Activation<'_, 'gc>, _: &mut UpdateContext<'_, 'gc, '_>, _: Object<'gc>, _: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(ReturnValue::Immediate("[object Object]".into())) +) -> Result, Error<'gc>> { + Ok("[object Object]".into()) } /// Implements `Object.prototype.isPropertyEnumerable` fn is_property_enumerable<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, _: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { match args.get(0) { - Some(Value::String(name)) => Ok(Value::Bool(this.is_property_enumerable(avm, name)).into()), - _ => Ok(Value::Bool(false).into()), + Some(Value::String(name)) => Ok(Value::Bool(this.is_property_enumerable(activation, name))), + _ => Ok(Value::Bool(false)), } } /// Implements `Object.prototype.isPrototypeOf` fn is_prototype_of<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { match args.get(0) { Some(val) => { - let ob = val.coerce_to_object(avm, context); - Ok(Value::Bool(this.is_prototype_of(ob)).into()) + let ob = val.coerce_to_object(activation, context); + Ok(Value::Bool(this.is_prototype_of(ob))) } - _ => Ok(Value::Bool(false).into()), + _ => Ok(Value::Bool(false)), } } /// Implements `Object.prototype.valueOf` fn value_of<'gc>( - _: &mut Avm1<'gc>, + _: &mut Activation<'_, 'gc>, _: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(ReturnValue::Immediate(this.into())) +) -> Result, Error<'gc>> { + Ok(this.into()) } /// Implements `Object.registerClass` pub fn register_class<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(class_name) = args.get(0).cloned() { - let class_name = class_name.coerce_to_string(avm, context)?; + let class_name = class_name.coerce_to_string(activation, context)?; if let Some(Character::MovieClip(movie_clip)) = context .library .library_for_movie_mut(context.swf.clone()) @@ -150,14 +152,14 @@ pub fn register_class<'gc>( if let Some(constructor) = args.get(1) { movie_clip.set_avm1_constructor( context.gc_context, - Some(constructor.coerce_to_object(avm, context)), + Some(constructor.coerce_to_object(activation, context)), ); } else { movie_clip.set_avm1_constructor(context.gc_context, None); } } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } /// Partially construct `Object.prototype`. @@ -224,16 +226,16 @@ pub fn fill_proto<'gc>( /// declare the property flags of a given property. It's not part of /// `Object.prototype`, and I suspect that's a deliberate omission. pub fn as_set_prop_flags<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, _: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let mut object = if let Some(object) = args.get(0).map(|v| v.coerce_to_object(avm, ac)) { +) -> Result, Error<'gc>> { + let mut object = if let Some(object) = args.get(0).map(|v| v.coerce_to_object(activation, ac)) { object } else { log::warn!("ASSetPropFlags called without object to apply to!"); - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); }; let properties = match args.get(1) { @@ -241,11 +243,13 @@ pub fn as_set_prop_flags<'gc>( //Convert to native array. //TODO: Can we make this an iterator? let mut array = vec![]; - let length = ob.get("length", avm, ac)?.coerce_to_f64(avm, ac)? as usize; + let length = ob + .get("length", activation, ac)? + .coerce_to_f64(activation, ac)? as usize; for i in 0..length { array.push( - ob.get(&format!("{}", i), avm, ac)? - .coerce_to_string(avm, ac)? + ob.get(&format!("{}", i), activation, ac)? + .coerce_to_string(activation, ac)? .to_string(), ) } @@ -256,20 +260,20 @@ pub fn as_set_prop_flags<'gc>( Some(_) => None, None => { log::warn!("ASSetPropFlags called without object list!"); - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } }; let set_attributes = EnumSet::::from_u128( args.get(2) .unwrap_or(&Value::Number(0.0)) - .coerce_to_f64(avm, ac)? as u128, + .coerce_to_f64(activation, ac)? as u128, ); let clear_attributes = EnumSet::::from_u128( args.get(3) .unwrap_or(&Value::Number(0.0)) - .coerce_to_f64(avm, ac)? as u128, + .coerce_to_f64(activation, ac)? as u128, ); match properties { @@ -286,7 +290,7 @@ pub fn as_set_prop_flags<'gc>( None => object.set_attributes(ac.gc_context, None, set_attributes, clear_attributes), } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn create_object_object<'gc>( diff --git a/core/src/avm1/globals/point.rs b/core/src/avm1/globals/point.rs index 964cd90c0..63142c527 100644 --- a/core/src/avm1/globals/point.rs +++ b/core/src/avm1/globals/point.rs @@ -1,10 +1,10 @@ //! flash.geom.Point +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::{Executable, FunctionObject}; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, Value}; +use crate::avm1::{Object, ScriptObject, TObject, Value}; use crate::context::UpdateContext; use enumset::EnumSet; use gc_arena::MutationContext; @@ -12,103 +12,110 @@ use std::f64::NAN; pub fn point_to_object<'gc>( point: (f64, f64), - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error<'gc>> { let args = [point.0.into(), point.1.into()]; - construct_new_point(&args, avm, context) + construct_new_point(&args, activation, context) } pub fn construct_new_point<'gc>( args: &[Value<'gc>], - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error<'gc>> { let proto = context.system_prototypes.point; - let object = proto.new(avm, context, proto, &args)?; - let _ = constructor(avm, context, object, &args)?; + let object = proto.new(activation, context, proto, &args)?; + let _ = constructor(activation, context, object, &args)?; Ok(object) } pub fn value_to_point<'gc>( value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(f64, f64), Error<'gc>> { let x = value - .coerce_to_object(avm, context) - .get("x", avm, context)? - .coerce_to_f64(avm, context)?; + .coerce_to_object(activation, context) + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; let y = value - .coerce_to_object(avm, context) - .get("y", avm, context)? - .coerce_to_f64(avm, context)?; + .coerce_to_object(activation, context) + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; Ok((x, y)) } pub fn object_to_point<'gc>( object: Object<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(f64, f64), Error<'gc>> { - let x = object.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let y = object.get("y", avm, context)?.coerce_to_f64(avm, context)?; + let x = object + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let y = object + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; Ok((x, y)) } fn constructor<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if args.is_empty() { - this.set("x", 0.into(), avm, context)?; - this.set("y", 0.into(), avm, context)?; + this.set("x", 0.into(), activation, context)?; + this.set("y", 0.into(), activation, context)?; } else { this.set( "x", args.get(0).unwrap_or(&Value::Undefined).to_owned(), - avm, + activation, context, )?; this.set( "y", args.get(1).unwrap_or(&Value::Undefined).to_owned(), - avm, + activation, context, )?; } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn clone<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let proto = context.system_prototypes.point; - let args = [this.get("x", avm, context)?, this.get("y", avm, context)?]; - let cloned = proto.new(avm, context, proto, &args)?; - let _ = constructor(avm, context, cloned, &args)?; + let args = [ + this.get("x", activation, context)?, + this.get("y", activation, context)?, + ]; + let cloned = proto.new(activation, context, proto, &args)?; + let _ = constructor(activation, context, cloned, &args)?; Ok(cloned.into()) } fn equals<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(other) = args.get(0) { - let this_x = this.get("x", avm, context)?; - let this_y = this.get("y", avm, context)?; - let other = other.coerce_to_object(avm, context); - let other_x = other.get("x", avm, context)?; - let other_y = other.get("y", avm, context)?; + let this_x = this.get("x", activation, context)?; + let this_y = this.get("y", activation, context)?; + let other = other.coerce_to_object(activation, context); + let other_x = other.get("x", activation, context)?; + let other_y = other.get("y", activation, context)?; return Ok((this_x == other_x && this_y == other_y).into()); } @@ -116,45 +123,53 @@ fn equals<'gc>( } fn add<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let this_x = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let this_y = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; +) -> Result, Error<'gc>> { + let this_x = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let this_y = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let other = value_to_point( args.get(0).unwrap_or(&Value::Undefined).to_owned(), - avm, + activation, context, )?; - let object = point_to_object((this_x + other.0, this_y + other.1), avm, context)?; + let object = point_to_object((this_x + other.0, this_y + other.1), activation, context)?; Ok(object.into()) } fn subtract<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let this_x = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let this_y = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; +) -> Result, Error<'gc>> { + let this_x = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let this_y = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let other = value_to_point( args.get(0).unwrap_or(&Value::Undefined).to_owned(), - avm, + activation, context, )?; - let object = point_to_object((this_x - other.0, this_y - other.1), avm, context)?; + let object = point_to_object((this_x - other.0, this_y - other.1), activation, context)?; Ok(object.into()) } fn distance<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if args.len() < 2 { return Ok(NAN.into()); } @@ -162,93 +177,96 @@ fn distance<'gc>( let a = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_object(avm, context); + .coerce_to_object(activation, context); let b = args.get(1).unwrap_or(&Value::Undefined); - let delta = a.call_method("subtract", &[b.to_owned()], avm, context)?; + let delta = a.call_method("subtract", &[b.to_owned()], activation, context)?; Ok(delta - .coerce_to_object(avm, context) - .get("length", avm, context)? - .into()) + .coerce_to_object(activation, context) + .get("length", activation, context)?) } fn polar<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let length = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let angle = args .get(1) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; - let point = point_to_object((length * angle.cos(), length * angle.sin()), avm, context)?; + .coerce_to_f64(activation, context)?; + let point = point_to_object( + (length * angle.cos(), length * angle.sin()), + activation, + context, + )?; Ok(point.into()) } fn interpolate<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if args.len() < 3 { - return Ok(point_to_object((NAN, NAN), avm, context)?.into()); + return Ok(point_to_object((NAN, NAN), activation, context)?.into()); } - let a = value_to_point(args.get(0).unwrap().to_owned(), avm, context)?; - let b = value_to_point(args.get(1).unwrap().to_owned(), avm, context)?; - let f = args.get(2).unwrap().coerce_to_f64(avm, context)?; + let a = value_to_point(args.get(0).unwrap().to_owned(), activation, context)?; + let b = value_to_point(args.get(1).unwrap().to_owned(), activation, context)?; + let f = args.get(2).unwrap().coerce_to_f64(activation, context)?; let result = (b.0 - (b.0 - a.0) * f, b.1 - (b.1 - a.1) * f); - Ok(point_to_object(result, avm, context)?.into()) + Ok(point_to_object(result, activation, context)?.into()) } fn to_string<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let x = this.get("x", avm, context)?; - let y = this.get("y", avm, context)?; +) -> Result, Error<'gc>> { + let x = this.get("x", activation, context)?; + let y = this.get("y", activation, context)?; Ok(format!( "(x={}, y={})", - x.coerce_to_string(avm, context)?, - y.coerce_to_string(avm, context)? + x.coerce_to_string(activation, context)?, + y.coerce_to_string(activation, context)? ) .into()) } fn length<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let point = value_to_point(this.into(), avm, context)?; +) -> Result, Error<'gc>> { + let point = value_to_point(this.into(), activation, context)?; let length = (point.0 * point.0 + point.1 * point.1).sqrt(); Ok(length.into()) } fn normalize<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let current_length = this - .get("length", avm, context)? - .coerce_to_f64(avm, context)?; + .get("length", activation, context)? + .coerce_to_f64(activation, context)?; if current_length.is_finite() { - let point = object_to_point(this, avm, context)?; + let point = object_to_point(this, activation, context)?; let new_length = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let (x, y) = if current_length == 0.0 { (point.0 * new_length, point.1 * new_length) } else { @@ -258,33 +276,33 @@ fn normalize<'gc>( ) }; - this.set("x", x.into(), avm, context)?; - this.set("y", y.into(), avm, context)?; + this.set("x", x.into(), activation, context)?; + this.set("y", y.into(), activation, context)?; } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn offset<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let point = value_to_point(this.into(), avm, context)?; +) -> Result, Error<'gc>> { + let point = value_to_point(this.into(), activation, context)?; let dx = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let dy = args .get(1) .unwrap_or(&Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; - this.set("x", (point.0 + dx).into(), avm, context)?; - this.set("y", (point.1 + dy).into(), avm, context)?; + this.set("x", (point.0 + dx).into(), activation, context)?; + this.set("y", (point.1 + dy).into(), activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn create_point_object<'gc>( diff --git a/core/src/avm1/globals/rectangle.rs b/core/src/avm1/globals/rectangle.rs index 75f20f53f..568e7cc2e 100644 --- a/core/src/avm1/globals/rectangle.rs +++ b/core/src/avm1/globals/rectangle.rs @@ -1,74 +1,74 @@ //! flash.geom.Rectangle +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::{Executable, FunctionObject}; use crate::avm1::globals::point::{construct_new_point, point_to_object, value_to_point}; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, Value}; +use crate::avm1::{Object, ScriptObject, TObject, Value}; use crate::context::UpdateContext; use enumset::EnumSet; use gc_arena::MutationContext; use std::f64::NAN; fn constructor<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if args.is_empty() { - this.set("x", 0.into(), avm, context)?; - this.set("y", 0.into(), avm, context)?; - this.set("width", 0.into(), avm, context)?; - this.set("height", 0.into(), avm, context)?; + this.set("x", 0.into(), activation, context)?; + this.set("y", 0.into(), activation, context)?; + this.set("width", 0.into(), activation, context)?; + this.set("height", 0.into(), activation, context)?; } else { this.set( "x", args.get(0).unwrap_or(&Value::Undefined).to_owned(), - avm, + activation, context, )?; this.set( "y", args.get(1).unwrap_or(&Value::Undefined).to_owned(), - avm, + activation, context, )?; this.set( "width", args.get(2).unwrap_or(&Value::Undefined).to_owned(), - avm, + activation, context, )?; this.set( "height", args.get(3).unwrap_or(&Value::Undefined).to_owned(), - avm, + activation, context, )?; } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn to_string<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let x = this.get("x", avm, context)?; - let y = this.get("y", avm, context)?; - let width = this.get("width", avm, context)?; - let height = this.get("height", avm, context)?; +) -> Result, Error<'gc>> { + let x = this.get("x", activation, context)?; + let y = this.get("y", activation, context)?; + let width = this.get("width", activation, context)?; + let height = this.get("height", activation, context)?; Ok(format!( "(x={}, y={}, w={}, h={})", - x.coerce_to_string(avm, context)?, - y.coerce_to_string(avm, context)?, - width.coerce_to_string(avm, context)?, - height.coerce_to_string(avm, context)? + x.coerce_to_string(activation, context)?, + y.coerce_to_string(activation, context)?, + width.coerce_to_string(activation, context)?, + height.coerce_to_string(activation, context)? ) .into()) } @@ -87,152 +87,168 @@ pub fn create_rectangle_object<'gc>( } fn is_empty<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let width = this - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; let height = this - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; Ok((width <= 0.0 || height <= 0.0 || width.is_nan() || height.is_nan()).into()) } fn set_empty<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - this.set("x", 0.into(), avm, context)?; - this.set("y", 0.into(), avm, context)?; - this.set("width", 0.into(), avm, context)?; - this.set("height", 0.into(), avm, context)?; - Ok(Value::Undefined.into()) +) -> Result, Error<'gc>> { + this.set("x", 0.into(), activation, context)?; + this.set("y", 0.into(), activation, context)?; + this.set("width", 0.into(), activation, context)?; + this.set("height", 0.into(), activation, context)?; + Ok(Value::Undefined) } fn clone<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let proto = context.system_prototypes.rectangle; let args = [ - this.get("x", avm, context)?, - this.get("y", avm, context)?, - this.get("width", avm, context)?, - this.get("height", avm, context)?, + this.get("x", activation, context)?, + this.get("y", activation, context)?, + this.get("width", activation, context)?, + this.get("height", activation, context)?, ]; - let cloned = proto.new(avm, context, proto, &args)?; - let _ = constructor(avm, context, cloned, &args)?; + let cloned = proto.new(activation, context, proto, &args)?; + let _ = constructor(activation, context, cloned, &args)?; Ok(cloned.into()) } fn contains<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { // TODO: This arbitrarily should return `false` or `undefined` for different invalid-values. // I can't find any rhyme or reason for it. let x = args .get(0) .unwrap_or(&Value::Undefined) .to_owned() - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let y = args .get(1) .unwrap_or(&Value::Undefined) .to_owned() - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; if x.is_nan() || y.is_nan() { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } - let left = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; + let left = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; let right = left + this - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; - let top = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; + let top = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let bottom = top + this - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; Ok((x >= left && x < right && y >= top && y < bottom).into()) } fn contains_point<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let (x, y) = value_to_point( args.get(0).unwrap_or(&Value::Undefined).to_owned(), - avm, + activation, context, )?; if x.is_nan() || y.is_nan() { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } - let left = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; + let left = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; let right = left + this - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; - let top = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; + let top = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let bottom = top + this - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; Ok((x >= left && x < right && y >= top && y < bottom).into()) } fn contains_rectangle<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let other = if let Some(Value::Object(other)) = args.get(0) { other } else { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); }; - let this_left = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let this_top = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; + let this_left = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let this_top = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let this_right = this_left + this - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; let this_bottom = this_top + this - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; - let other_left = other.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let other_top = other.get("y", avm, context)?.coerce_to_f64(avm, context)?; + let other_left = other + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let other_top = other + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let other_right = other_left + other - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; let other_bottom = other_top + other - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; if other_left.is_nan() || other_top.is_nan() || other_right.is_nan() || other_bottom.is_nan() { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } Ok((other_left >= this_left @@ -243,38 +259,46 @@ fn contains_rectangle<'gc>( } fn intersects<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let other = if let Some(Value::Object(other)) = args.get(0) { other } else { return Ok(false.into()); }; - let this_left = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let this_top = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; + let this_left = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let this_top = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let this_right = this_left + this - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; let this_bottom = this_top + this - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; - let other_left = other.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let other_top = other.get("y", avm, context)?.coerce_to_f64(avm, context)?; + let other_left = other + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let other_top = other + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let other_right = other_left + other - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; let other_bottom = other_top + other - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; Ok((this_left < other_right && this_right > other_left @@ -284,33 +308,41 @@ fn intersects<'gc>( } fn union<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let this_left = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let this_top = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; +) -> Result, Error<'gc>> { + let this_left = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let this_top = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let this_right = this_left + this - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; let this_bottom = this_top + this - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; let (other_left, other_top, other_width, other_height) = if let Some(Value::Object(other)) = args.get(0) { ( - other.get("x", avm, context)?.coerce_to_f64(avm, context)?, - other.get("y", avm, context)?.coerce_to_f64(avm, context)?, other - .get("width", avm, context)? - .coerce_to_f64(avm, context)?, + .get("x", activation, context)? + .coerce_to_f64(activation, context)?, other - .get("height", avm, context)? - .coerce_to_f64(avm, context)?, + .get("y", activation, context)? + .coerce_to_f64(activation, context)?, + other + .get("width", activation, context)? + .coerce_to_f64(activation, context)?, + other + .get("height", activation, context)? + .coerce_to_f64(activation, context)?, ) } else { (NAN, NAN, NAN, NAN) @@ -354,165 +386,189 @@ fn union<'gc>( Value::Number(width), Value::Number(height), ]; - let result = proto.new(avm, context, proto, &args)?; - let _ = constructor(avm, context, result, &args)?; + let result = proto.new(activation, context, proto, &args)?; + let _ = constructor(activation, context, result, &args)?; Ok(result.into()) } fn inflate<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let x = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let y = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; +) -> Result, Error<'gc>> { + let x = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let y = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let width = this - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; let height = this - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; let horizontal = args .get(0) .unwrap_or(&Value::Undefined) .to_owned() - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let vertical = args .get(1) .unwrap_or(&Value::Undefined) .to_owned() - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; - this.set("x", Value::Number(x - horizontal), avm, context)?; - this.set("y", Value::Number(y - vertical), avm, context)?; + this.set("x", Value::Number(x - horizontal), activation, context)?; + this.set("y", Value::Number(y - vertical), activation, context)?; this.set( "width", Value::Number(width + horizontal * 2.0), - avm, + activation, context, )?; this.set( "height", Value::Number(height + vertical * 2.0), - avm, + activation, context, )?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn inflate_point<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let x = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let y = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; +) -> Result, Error<'gc>> { + let x = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let y = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let width = this - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; let height = this - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; let (horizontal, vertical) = value_to_point( args.get(0).unwrap_or(&Value::Undefined).to_owned(), - avm, + activation, context, )?; - this.set("x", Value::Number(x - horizontal), avm, context)?; - this.set("y", Value::Number(y - vertical), avm, context)?; + this.set("x", Value::Number(x - horizontal), activation, context)?; + this.set("y", Value::Number(y - vertical), activation, context)?; this.set( "width", Value::Number(width + horizontal * 2.0), - avm, + activation, context, )?; this.set( "height", Value::Number(height + vertical * 2.0), - avm, + activation, context, )?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn offset<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let x = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let y = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; +) -> Result, Error<'gc>> { + let x = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let y = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let horizontal = args .get(0) .unwrap_or(&Value::Undefined) .to_owned() - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let vertical = args .get(1) .unwrap_or(&Value::Undefined) .to_owned() - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; - this.set("x", Value::Number(x + horizontal), avm, context)?; - this.set("y", Value::Number(y + vertical), avm, context)?; + this.set("x", Value::Number(x + horizontal), activation, context)?; + this.set("y", Value::Number(y + vertical), activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn offset_point<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let x = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let y = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; +) -> Result, Error<'gc>> { + let x = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let y = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let (horizontal, vertical) = value_to_point( args.get(0).unwrap_or(&Value::Undefined).to_owned(), - avm, + activation, context, )?; - this.set("x", Value::Number(x + horizontal), avm, context)?; - this.set("y", Value::Number(y + vertical), avm, context)?; + this.set("x", Value::Number(x + horizontal), activation, context)?; + this.set("y", Value::Number(y + vertical), activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn intersection<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let this_left = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let this_top = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; +) -> Result, Error<'gc>> { + let this_left = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let this_top = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let this_right = this_left + this - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; let this_bottom = this_top + this - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; let (other_left, other_top, other_width, other_height) = if let Some(Value::Object(other)) = args.get(0) { ( - other.get("x", avm, context)?.coerce_to_f64(avm, context)?, - other.get("y", avm, context)?.coerce_to_f64(avm, context)?, other - .get("width", avm, context)? - .coerce_to_f64(avm, context)?, + .get("x", activation, context)? + .coerce_to_f64(activation, context)?, other - .get("height", avm, context)? - .coerce_to_f64(avm, context)?, + .get("y", activation, context)? + .coerce_to_f64(activation, context)?, + other + .get("width", activation, context)? + .coerce_to_f64(activation, context)?, + other + .get("height", activation, context)? + .coerce_to_f64(activation, context)?, ) } else { (NAN, NAN, NAN, NAN) @@ -553,33 +609,33 @@ fn intersection<'gc>( Value::Number(right - left), Value::Number(bottom - top), ]; - let result = proto.new(avm, context, proto, &args)?; - let _ = constructor(avm, context, result, &args)?; + let result = proto.new(activation, context, proto, &args)?; + let _ = constructor(activation, context, result, &args)?; Ok(result.into()) } fn equals<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(Value::Object(other)) = args.get(0) { - let this_x = this.get("x", avm, context)?; - let this_y = this.get("y", avm, context)?; - let this_width = this.get("width", avm, context)?; - let this_height = this.get("height", avm, context)?; - let other_x = other.get("x", avm, context)?; - let other_y = other.get("y", avm, context)?; - let other_width = other.get("width", avm, context)?; - let other_height = other.get("height", avm, context)?; + let this_x = this.get("x", activation, context)?; + let this_y = this.get("y", activation, context)?; + let this_width = this.get("width", activation, context)?; + let this_height = this.get("height", activation, context)?; + let other_x = other.get("x", activation, context)?; + let other_y = other.get("y", activation, context)?; + let other_width = other.get("width", activation, context)?; + let other_height = other.get("height", activation, context)?; let proto = context.system_prototypes.rectangle; let constructor = context.system_prototypes.rectangle_constructor; return Ok((this_x == other_x && this_y == other_y && this_width == other_width && this_height == other_height - && other.is_instance_of(avm, context, constructor, proto)?) + && other.is_instance_of(activation, context, constructor, proto)?) .into()); } @@ -587,249 +643,273 @@ fn equals<'gc>( } fn get_left<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(this.get("x", avm, context)?.into()) +) -> Result, Error<'gc>> { + Ok(this.get("x", activation, context)?) } fn set_left<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let new_left = args.get(0).unwrap_or(&Value::Undefined).to_owned(); - let old_left = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; + let old_left = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; let width = this - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; - this.set("x", new_left.clone(), avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; + this.set("x", new_left.clone(), activation, context)?; this.set( "width", - Value::Number(width + (old_left - new_left.coerce_to_f64(avm, context)?)), - avm, + Value::Number(width + (old_left - new_left.coerce_to_f64(activation, context)?)), + activation, context, )?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn get_top<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(this.get("y", avm, context)?.into()) +) -> Result, Error<'gc>> { + Ok(this.get("y", activation, context)?) } fn set_top<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let new_top = args.get(0).unwrap_or(&Value::Undefined).to_owned(); - let old_top = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; + let old_top = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let height = this - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; - this.set("y", new_top.clone(), avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; + this.set("y", new_top.clone(), activation, context)?; this.set( "height", - Value::Number(height + (old_top - new_top.coerce_to_f64(avm, context)?)), - avm, + Value::Number(height + (old_top - new_top.coerce_to_f64(activation, context)?)), + activation, context, )?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn get_right<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let x = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; +) -> Result, Error<'gc>> { + let x = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; let width = this - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; Ok((x + width).into()) } fn set_right<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let right = if let Some(arg) = args.get(0) { - arg.coerce_to_f64(avm, context)? + arg.coerce_to_f64(activation, context)? } else { NAN }; - let x = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; + let x = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; - this.set("width", Value::Number(right - x), avm, context)?; + this.set("width", Value::Number(right - x), activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn get_bottom<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let y = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; +) -> Result, Error<'gc>> { + let y = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let height = this - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; Ok((y + height).into()) } fn set_bottom<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let bottom = if let Some(arg) = args.get(0) { - arg.coerce_to_f64(avm, context)? + arg.coerce_to_f64(activation, context)? } else { NAN }; - let y = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; + let y = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; - this.set("height", Value::Number(bottom - y), avm, context)?; + this.set("height", Value::Number(bottom - y), activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn get_size<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let width = this.get("width", avm, context)?; - let height = this.get("height", avm, context)?; - let point = construct_new_point(&[width, height], avm, context)?; +) -> Result, Error<'gc>> { + let width = this.get("width", activation, context)?; + let height = this.get("height", activation, context)?; + let point = construct_new_point(&[width, height], activation, context)?; Ok(point.into()) } fn set_size<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let (width, height) = if let Some(Value::Object(object)) = args.get(0) { ( - object.get("x", avm, context)?, - object.get("y", avm, context)?, + object.get("x", activation, context)?, + object.get("y", activation, context)?, ) } else { (Value::Undefined, Value::Undefined) }; - this.set("width", width, avm, context)?; - this.set("height", height, avm, context)?; + this.set("width", width, activation, context)?; + this.set("height", height, activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn get_top_left<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let x = this.get("x", avm, context)?; - let y = this.get("y", avm, context)?; - let point = construct_new_point(&[x, y], avm, context)?; +) -> Result, Error<'gc>> { + let x = this.get("x", activation, context)?; + let y = this.get("y", activation, context)?; + let point = construct_new_point(&[x, y], activation, context)?; Ok(point.into()) } fn set_top_left<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let (new_left, new_top) = if let Some(Value::Object(object)) = args.get(0) { ( - object.get("x", avm, context)?, - object.get("y", avm, context)?, + object.get("x", activation, context)?, + object.get("y", activation, context)?, ) } else { (Value::Undefined, Value::Undefined) }; - let old_left = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; + let old_left = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; let width = this - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; - let old_top = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; + let old_top = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let height = this - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; - this.set("x", new_left.clone(), avm, context)?; - this.set("y", new_top.clone(), avm, context)?; + this.set("x", new_left.clone(), activation, context)?; + this.set("y", new_top.clone(), activation, context)?; this.set( "width", - Value::Number(width + (old_left - new_left.coerce_to_f64(avm, context)?)), - avm, + Value::Number(width + (old_left - new_left.coerce_to_f64(activation, context)?)), + activation, context, )?; this.set( "height", - Value::Number(height + (old_top - new_top.coerce_to_f64(avm, context)?)), - avm, + Value::Number(height + (old_top - new_top.coerce_to_f64(activation, context)?)), + activation, context, )?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn get_bottom_right<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let x = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let y = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; +) -> Result, Error<'gc>> { + let x = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let y = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; let width = this - .get("width", avm, context)? - .coerce_to_f64(avm, context)?; + .get("width", activation, context)? + .coerce_to_f64(activation, context)?; let height = this - .get("height", avm, context)? - .coerce_to_f64(avm, context)?; - let point = point_to_object((x + width, y + height), avm, context)?; + .get("height", activation, context)? + .coerce_to_f64(activation, context)?; + let point = point_to_object((x + width, y + height), activation, context)?; Ok(point.into()) } fn set_bottom_right<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let (bottom, right) = value_to_point( args.get(0).unwrap_or(&Value::Undefined).to_owned(), - avm, + activation, context, )?; - let top = this.get("x", avm, context)?.coerce_to_f64(avm, context)?; - let left = this.get("y", avm, context)?.coerce_to_f64(avm, context)?; + let top = this + .get("x", activation, context)? + .coerce_to_f64(activation, context)?; + let left = this + .get("y", activation, context)? + .coerce_to_f64(activation, context)?; - this.set("width", Value::Number(bottom - top), avm, context)?; - this.set("height", Value::Number(right - left), avm, context)?; + this.set("width", Value::Number(bottom - top), activation, context)?; + this.set("height", Value::Number(right - left), activation, context)?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn create_proto<'gc>( diff --git a/core/src/avm1/globals/shared_object.rs b/core/src/avm1/globals/shared_object.rs index 2dce20c8f..714c8d899 100644 --- a/core/src/avm1/globals/shared_object.rs +++ b/core/src/avm1/globals/shared_object.rs @@ -1,7 +1,7 @@ +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::{Executable, FunctionObject}; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, TObject, Value}; +use crate::avm1::{Object, TObject, Value}; use crate::context::UpdateContext; use enumset::EnumSet; use gc_arena::MutationContext; @@ -11,36 +11,36 @@ use crate::avm1::shared_object::SharedObject; use json::JsonValue; pub fn delete_all<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("SharedObject.deleteAll() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn get_disk_usage<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("SharedObject.getDiskUsage() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } /// Serialize an Object and any children to a JSON object /// It would be best if this was implemented via serde but due to avm and context it can't /// Undefined fields aren't serialized fn recursive_serialize<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, obj: Object<'gc>, json_obj: &mut JsonValue, ) { - for k in &obj.get_keys(avm) { - if let Ok(elem) = obj.get(k, avm, action_context) { + for k in &obj.get_keys(activation) { + if let Ok(elem) = obj.get(k, activation, action_context) { match elem { Value::Undefined => {} Value::Null => json_obj[k] = JsonValue::Null, @@ -49,12 +49,13 @@ fn recursive_serialize<'gc>( Value::String(s) => json_obj[k] = s.into(), Value::Object(o) => { // Don't attempt to serialize functions + let function = activation.avm().prototypes.function; if !o - .is_instance_of(avm, action_context, o, avm.prototypes.function) + .is_instance_of(activation, action_context, o, function) .unwrap_or_default() { let mut sub_data_json = JsonValue::new_object(); - recursive_serialize(avm, action_context, o, &mut sub_data_json); + recursive_serialize(activation, action_context, o, &mut sub_data_json); json_obj[k] = sub_data_json; } } @@ -68,7 +69,7 @@ fn recursive_serialize<'gc>( /// Undefined fields aren't deserialized fn recursive_deserialize<'gc>( json_obj: JsonValue, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, object: Object<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) { @@ -112,10 +113,11 @@ fn recursive_deserialize<'gc>( ); } JsonValue::Object(o) => { - let so = avm.prototypes.object; - let obj = so.new(avm, context, so, &[]).unwrap(); - let _ = crate::avm1::globals::object::constructor(avm, context, obj, &[]).unwrap(); - recursive_deserialize(JsonValue::Object(o.clone()), avm, obj, context); + let so = activation.avm().prototypes.object; + let obj = so.new(activation, context, so, &[]).unwrap(); + let _ = crate::avm1::globals::object::constructor(activation, context, obj, &[]) + .unwrap(); + recursive_deserialize(JsonValue::Object(o.clone()), activation, obj, context); object.define_value( context.gc_context, @@ -130,21 +132,21 @@ fn recursive_deserialize<'gc>( } pub fn get_local<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let name = args .get(0) .unwrap_or(&Value::Undefined) .to_owned() - .coerce_to_string(avm, action_context)? + .coerce_to_string(activation, action_context)? .to_string(); //Check if this is referencing an existing shared object if let Some(so) = action_context.shared_objects.get(&name) { - return Ok(Value::Object(*so).into()); + return Ok(Value::Object(*so)); } if args.len() > 1 { @@ -152,23 +154,23 @@ pub fn get_local<'gc>( } // Data property only should exist when created with getLocal/Remote - let so = avm.prototypes.shared_object; - let this = so.new(avm, action_context, so, &[])?; - let _ = constructor(avm, action_context, this, &[])?; + let so = activation.avm().prototypes.shared_object; + let this = so.new(activation, action_context, so, &[])?; + let _ = constructor(activation, action_context, this, &[])?; // Set the internal name let obj_so = this.as_shared_object().unwrap(); obj_so.set_name(action_context.gc_context, name.to_string()); // Create the data object - let data_proto = avm.prototypes.object; - let data = data_proto.new(avm, action_context, so, &[])?; - let _ = crate::avm1::globals::object::constructor(avm, action_context, data, &[])?; + let data_proto = activation.avm().prototypes.object; + let data = data_proto.new(activation, action_context, so, &[])?; + let _ = crate::avm1::globals::object::constructor(activation, action_context, data, &[])?; // Load the data object from storage if it existed prior if let Some(saved) = action_context.storage.get_string(&name) { if let Ok(json_data) = json::parse(&saved) { - recursive_deserialize(json_data, avm, data, action_context); + recursive_deserialize(json_data, activation, data, action_context); } } @@ -185,43 +187,43 @@ pub fn get_local<'gc>( } pub fn get_remote<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("SharedObject.getRemote() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn get_max_size<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("SharedObject.getMaxSize() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn add_listener<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("SharedObject.addListener() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn remove_listener<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("SharedObject.removeListener() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn create_shared_object_object<'gc>( @@ -297,17 +299,17 @@ pub fn create_shared_object_object<'gc>( } pub fn clear<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let data = this - .get("data", avm, action_context)? - .coerce_to_object(avm, action_context); + .get("data", activation, action_context)? + .coerce_to_object(activation, action_context); - for k in &data.get_keys(avm) { - data.delete(avm, action_context.gc_context, k); + for k in &data.get_keys(activation) { + data.delete(activation, action_context.gc_context, k); } let so = this.as_shared_object().unwrap(); @@ -315,41 +317,41 @@ pub fn clear<'gc>( action_context.storage.remove_key(&name); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn close<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("SharedObject.close() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn connect<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("SharedObject.connect() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn flush<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let data = this - .get("data", avm, action_context)? - .coerce_to_object(avm, action_context); + .get("data", activation, action_context)? + .coerce_to_object(activation, action_context); let mut data_json = JsonValue::new_object(); - recursive_serialize(avm, action_context, data, &mut data_json); + recursive_serialize(activation, action_context, data, &mut data_json); let this_obj = this.as_shared_object().unwrap(); let name = this_obj.get_name(); @@ -361,53 +363,53 @@ pub fn flush<'gc>( } pub fn get_size<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("SharedObject.getSize() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn send<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("SharedObject.send() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn set_fps<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("SharedObject.setFps() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn on_status<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("SharedObject.onStatus() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn on_sync<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("SharedObject.onSync() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn create_proto<'gc>( @@ -470,10 +472,10 @@ pub fn create_proto<'gc>( } pub fn constructor<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(Value::Undefined.into()) +) -> Result, Error<'gc>> { + Ok(Value::Undefined) } diff --git a/core/src/avm1/globals/sound.rs b/core/src/avm1/globals/sound.rs index b4c4e1808..b2fbbdf86 100644 --- a/core/src/avm1/globals/sound.rs +++ b/core/src/avm1/globals/sound.rs @@ -1,27 +1,27 @@ //! AVM1 Sound object //! TODO: Sound position, transform, loadSound +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::property::Attribute::*; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, SoundObject, TObject, UpdateContext, Value}; +use crate::avm1::{Object, SoundObject, TObject, UpdateContext, Value}; use crate::character::Character; use crate::display_object::TDisplayObject; use gc_arena::MutationContext; /// Implements `Sound` pub fn constructor<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'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 = args .get(0) - .map(|o| o.coerce_to_object(avm, context)) + .map(|o| o.coerce_to_object(activation, context)) .and_then(|o| o.as_display_object()); let sound = this.as_sound_object().unwrap(); @@ -161,14 +161,14 @@ pub fn create_proto<'gc>( } fn attach_sound<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let name = args.get(0).unwrap_or(&Value::Undefined); if let Some(sound_object) = this.as_sound_object() { - let name = name.coerce_to_string(avm, context)?; + let name = name.coerce_to_string(activation, context)?; let movie = sound_object .owner() .or_else(|| context.levels.get(&0).copied()) @@ -197,16 +197,16 @@ fn attach_sound<'gc>( } else { log::warn!("Sound.attachSound: this is not a Sound"); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn duration<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if avm.current_swf_version() >= 6 { +) -> Result, Error<'gc>> { + if activation.current_swf_version() >= 6 { if let Some(sound_object) = this.as_sound_object() { return Ok(sound_object.duration().into()); } else { @@ -214,98 +214,98 @@ fn duration<'gc>( } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn get_bytes_loaded<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if avm.current_swf_version() >= 6 { +) -> Result, Error<'gc>> { + if activation.current_swf_version() >= 6 { log::warn!("Sound.getBytesLoaded: Unimplemented"); Ok(1.into()) } else { - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } fn get_bytes_total<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if avm.current_swf_version() >= 6 { +) -> Result, Error<'gc>> { + if activation.current_swf_version() >= 6 { log::warn!("Sound.getBytesTotal: Unimplemented"); Ok(1.into()) } else { - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } fn get_pan<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("Sound.getPan: Unimplemented"); Ok(0.into()) } fn get_transform<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("Sound.getTransform: Unimplemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn get_volume<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("Sound.getVolume: Unimplemented"); Ok(100.into()) } fn id3<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if avm.current_swf_version() >= 6 { +) -> Result, Error<'gc>> { + if activation.current_swf_version() >= 6 { log::warn!("Sound.id3: Unimplemented"); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn load_sound<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if avm.current_swf_version() >= 6 { +) -> Result, Error<'gc>> { + if activation.current_swf_version() >= 6 { log::warn!("Sound.loadSound: Unimplemented"); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn position<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if avm.current_swf_version() >= 6 { +) -> Result, Error<'gc>> { + if activation.current_swf_version() >= 6 { if let Some(sound_object) = this.as_sound_object() { // TODO: The position is "sticky"; even if the sound is no longer playing, it should return // the previous valid position. @@ -320,53 +320,53 @@ fn position<'gc>( log::warn!("Sound.position: this is not a Sound"); } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn set_pan<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("Sound.setPan: Unimplemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn set_transform<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("Sound.setTransform: Unimplemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn set_volume<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("Sound.setVolume: Unimplemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn start<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let start_offset = args .get(0) .unwrap_or(&Value::Number(0.0)) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let loops = args .get(1) .unwrap_or(&Value::Number(1.0)) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let loops = if loops >= 1.0 && loops <= f64::from(std::i16::MAX) { loops as u16 @@ -401,19 +401,19 @@ fn start<'gc>( log::warn!("Sound.start: Invalid sound"); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn stop<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(sound) = this.as_sound_object() { if let Some(name) = args.get(0) { // Usage 1: Stop all instances of a particular sound, using the name parameter. - let name = name.coerce_to_string(avm, context)?; + let name = name.coerce_to_string(activation, context)?; let movie = sound .owner() .or_else(|| context.levels.get(&0).copied()) @@ -449,5 +449,5 @@ fn stop<'gc>( log::warn!("Sound.stop: this is not a Sound"); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } diff --git a/core/src/avm1/globals/stage.rs b/core/src/avm1/globals/stage.rs index ead23ee69..aed2ca929 100644 --- a/core/src/avm1/globals/stage.rs +++ b/core/src/avm1/globals/stage.rs @@ -1,12 +1,11 @@ //! Stage object //! //! TODO: This is a very rough stub with not much implementation. +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, UpdateContext, Value}; - +use crate::avm1::{Object, ScriptObject, TObject, UpdateContext, Value}; use gc_arena::MutationContext; pub fn create_stage_object<'gc>( @@ -77,99 +76,99 @@ pub fn create_stage_object<'gc>( } fn add_listener<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("Stage.addListener: unimplemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn align<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("Stage.align: unimplemented"); Ok("".into()) } fn set_align<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("Stage.align: unimplemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn height<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(context.stage_size.1.to_pixels().into()) } fn remove_listener<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("Stage.removeListener: unimplemented"); Ok("".into()) } fn scale_mode<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("Stage.scaleMode: unimplemented"); Ok("noScale".into()) } fn set_scale_mode<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("Stage.scaleMode: unimplemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn show_menu<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("Stage.showMenu: unimplemented"); Ok(true.into()) } fn set_show_menu<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("Stage.showMenu: unimplemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn width<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(context.stage_size.0.to_pixels().into()) } diff --git a/core/src/avm1/globals/string.rs b/core/src/avm1/globals/string.rs index 2a8916c65..e27455600 100644 --- a/core/src/avm1/globals/string.rs +++ b/core/src/avm1/globals/string.rs @@ -1,11 +1,11 @@ //! `String` class impl +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::{Executable, FunctionObject}; use crate::avm1::property::Attribute::*; -use crate::avm1::return_value::ReturnValue; use crate::avm1::value_object::ValueObject; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, Value}; +use crate::avm1::{Object, ScriptObject, TObject, Value}; use crate::context::UpdateContext; use crate::string_utils; use enumset::EnumSet; @@ -13,14 +13,14 @@ use gc_arena::MutationContext; /// `String` constructor pub fn string<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let value = match args.get(0).cloned() { Some(Value::String(s)) => s, - Some(v) => v.coerce_to_string(avm, ac)?.to_string(), + Some(v) => v.coerce_to_string(activation, ac)?.to_string(), _ => String::new(), }; @@ -171,19 +171,19 @@ pub fn create_proto<'gc>( } fn char_at<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { // TODO: Will return REPLACEMENT_CHAR if this indexes a character outside the BMP, losing info about the surrogate. // When we improve our string representation, the unpaired surrogate should be returned. let this_val = Value::from(this); - let string = this_val.coerce_to_string(avm, context)?; + let string = this_val.coerce_to_string(activation, context)?; let i = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_i32(avm, context)?; + .coerce_to_i32(activation, context)?; let ret = if i >= 0 { string .encode_utf16() @@ -197,17 +197,17 @@ fn char_at<'gc>( } fn char_code_at<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let this_val = Value::from(this); - let this = this_val.coerce_to_string(avm, context)?; + let this = this_val.coerce_to_string(activation, context)?; let i = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_i32(avm, context)?; + .coerce_to_i32(activation, context)?; let ret = if i >= 0 { this.encode_utf16() .nth(i as usize) @@ -220,31 +220,31 @@ fn char_code_at<'gc>( } fn concat<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let mut ret = Value::from(this) - .coerce_to_string(avm, context)? + .coerce_to_string(activation, context)? .to_string(); for arg in args { - let s = arg.coerce_to_string(avm, context)?; + let s = arg.coerce_to_string(activation, context)?; ret.push_str(&s) } Ok(ret.into()) } fn from_char_code<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { // TODO: Unpaired surrogates will be replace with Unicode replacement char. let mut out = String::with_capacity(args.len()); for arg in args { - let i = arg.coerce_to_u16(avm, context)?; + let i = arg.coerce_to_u16(activation, context)?; if i == 0 { // Stop at a null-terminator. break; @@ -255,20 +255,20 @@ fn from_char_code<'gc>( } fn index_of<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let this = Value::from(this) - .coerce_to_string(avm, context)? + .coerce_to_string(activation, context)? .encode_utf16() .collect::>(); let pattern = match args.get(0) { - None | Some(Value::Undefined) => return Ok(Value::Undefined.into()), + None | Some(Value::Undefined) => return Ok(Value::Undefined), Some(s) => s .clone() - .coerce_to_string(avm, context)? + .coerce_to_string(activation, context)? .encode_utf16() .collect::>(), }; @@ -276,7 +276,7 @@ fn index_of<'gc>( let n = args .get(1) .unwrap_or(&Value::Undefined) - .coerce_to_i32(avm, context)?; + .coerce_to_i32(activation, context)?; if n >= 0 { n as usize } else { @@ -303,27 +303,27 @@ fn index_of<'gc>( } fn last_index_of<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let this = Value::from(this) - .coerce_to_string(avm, context)? + .coerce_to_string(activation, context)? .encode_utf16() .collect::>(); let pattern = match args.get(0) { - None | Some(Value::Undefined) => return Ok(Value::Undefined.into()), + None | Some(Value::Undefined) => return Ok(Value::Undefined), Some(s) => s .clone() - .coerce_to_string(avm, context)? + .coerce_to_string(activation, context)? .encode_utf16() .collect::>(), }; let start_index = match args.get(1) { None | Some(Value::Undefined) => this.len(), Some(n) => { - let n = n.coerce_to_i32(avm, context)?; + let n = n.coerce_to_i32(activation, context)?; if n >= 0 { let n = n as usize; if n <= this.len() { @@ -355,28 +355,28 @@ fn last_index_of<'gc>( } fn slice<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if args.is_empty() { // No args returns undefined immediately. - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } let this_val = Value::from(this); - let this = this_val.coerce_to_string(avm, context)?; + let this = this_val.coerce_to_string(activation, context)?; let this_len = this.encode_utf16().count(); let start_index = string_wrapping_index( args.get(0) .unwrap_or(&Value::Undefined) - .coerce_to_i32(avm, context)?, + .coerce_to_i32(activation, context)?, this_len, ); let end_index = match args.get(1) { None | Some(Value::Undefined) => this_len, - Some(n) => string_wrapping_index(n.coerce_to_i32(avm, context)?, this_len), + Some(n) => string_wrapping_index(n.coerce_to_i32(activation, context)?, this_len), }; if start_index < end_index { let ret = utf16_iter_to_string( @@ -391,20 +391,20 @@ fn slice<'gc>( } fn split<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let this_val = Value::from(this); - let this = this_val.coerce_to_string(avm, context)?; + let this = this_val.coerce_to_string(activation, context)?; let delimiter_val = args.get(0).unwrap_or(&Value::Undefined); - let delimiter = delimiter_val.coerce_to_string(avm, context)?; + let delimiter = delimiter_val.coerce_to_string(activation, context)?; let limit = match args.get(1) { None | Some(Value::Undefined) => std::usize::MAX, - Some(n) => std::cmp::max(0, n.coerce_to_i32(avm, context)?) as usize, + Some(n) => std::cmp::max(0, n.coerce_to_i32(activation, context)?) as usize, }; - let array = ScriptObject::array(context.gc_context, Some(avm.prototypes.array)); + let array = ScriptObject::array(context.gc_context, Some(activation.avm().prototypes.array)); if !delimiter.is_empty() { for (i, token) in this.split(delimiter.as_ref()).take(limit).enumerate() { array.set_array_element(i, token.to_string().into(), context.gc_context); @@ -421,24 +421,26 @@ fn split<'gc>( } fn substr<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if args.is_empty() { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } let this_val = Value::from(this); - let this = this_val.coerce_to_string(avm, context)?; + let this = this_val.coerce_to_string(activation, context)?; let this_len = this.encode_utf16().count(); - let start_index = - string_wrapping_index(args.get(0).unwrap().coerce_to_i32(avm, context)?, this_len); + let start_index = string_wrapping_index( + args.get(0).unwrap().coerce_to_i32(activation, context)?, + this_len, + ); let len = match args.get(1) { None | Some(Value::Undefined) => this_len, - Some(n) => string_index(n.coerce_to_i32(avm, context)?, this_len), + Some(n) => string_index(n.coerce_to_i32(activation, context)?, this_len), }; let ret = utf16_iter_to_string(this.encode_utf16().skip(start_index).take(len)); @@ -446,23 +448,26 @@ fn substr<'gc>( } fn substring<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if args.is_empty() { - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } let this_val = Value::from(this); - let this = this_val.coerce_to_string(avm, context)?; + let this = this_val.coerce_to_string(activation, context)?; let this_len = this.encode_utf16().count(); - let mut start_index = string_index(args.get(0).unwrap().coerce_to_i32(avm, context)?, this_len); + let mut start_index = string_index( + args.get(0).unwrap().coerce_to_i32(activation, context)?, + this_len, + ); let mut end_index = match args.get(1) { None | Some(Value::Undefined) => this_len, - Some(n) => string_index(n.coerce_to_i32(avm, context)?, this_len), + Some(n) => string_index(n.coerce_to_i32(activation, context)?, this_len), }; // substring automatically swaps the start/end if they are flipped. @@ -478,13 +483,13 @@ fn substring<'gc>( } fn to_lower_case<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let this_val = Value::from(this); - let this = this_val.coerce_to_string(avm, context)?; + let this = this_val.coerce_to_string(activation, context)?; Ok(this .chars() .map(string_utils::swf_char_to_lowercase) @@ -494,11 +499,11 @@ fn to_lower_case<'gc>( /// `String.toString` / `String.valueOf` impl pub fn to_string_value_of<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(vbox) = this.as_value_object() { if let Value::String(s) = vbox.unbox() { return Ok(s.into()); @@ -508,17 +513,17 @@ pub fn to_string_value_of<'gc>( //TODO: This normally falls back to `[object Object]` or `[type Function]`, //implying that `toString` and `valueOf` are inherent object properties and //not just methods. - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn to_upper_case<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let this_val = Value::from(this); - let this = this_val.coerce_to_string(avm, context)?; + let this = this_val.coerce_to_string(activation, context)?; Ok(this .chars() .map(string_utils::swf_char_to_uppercase) diff --git a/core/src/avm1/globals/system.rs b/core/src/avm1/globals/system.rs index ebcae1cb6..8c8290985 100644 --- a/core/src/avm1/globals/system.rs +++ b/core/src/avm1/globals/system.rs @@ -1,8 +1,8 @@ +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::object::Object; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, ScriptObject, TObject, Value}; +use crate::avm1::{ScriptObject, TObject, Value}; use crate::context::UpdateContext; use core::fmt; use enumset::{EnumSet, EnumSetType}; @@ -273,11 +273,11 @@ pub struct SystemProperties { } impl SystemProperties { - pub fn get_version_string(&self, avm: &Avm1) -> String { + pub fn get_version_string(&self, activation: &mut Activation) -> String { format!( "{} {},0,0,0", self.manufacturer.get_platform_name(), - avm.player_version + activation.avm().player_version ) } @@ -305,7 +305,7 @@ impl SystemProperties { percent_encoding::utf8_percent_encode(s, percent_encoding::NON_ALPHANUMERIC).to_string() } - pub fn get_server_string(&self, avm: &Avm1) -> String { + pub fn get_server_string(&self, activation: &mut Activation) -> String { url::form_urlencoded::Serializer::new(String::new()) .append_pair("A", self.encode_capability(SystemCapabilities::Audio)) .append_pair( @@ -347,7 +347,7 @@ impl SystemProperties { "M", &self.encode_string( self.manufacturer - .get_manufacturer_string(avm.player_version) + .get_manufacturer_string(activation.avm().player_version) .as_str(), ), ) @@ -358,7 +358,11 @@ impl SystemProperties { .append_pair("COL", &self.screen_color.to_string()) .append_pair("AR", &self.aspect_ratio.to_string()) .append_pair("OS", &self.encode_string(&self.os.to_string())) - .append_pair("L", self.language.get_language_code(avm.player_version)) + .append_pair( + "L", + self.language + .get_language_code(activation.avm().player_version), + ) .append_pair("IME", self.encode_capability(SystemCapabilities::IME)) .append_pair("PT", &self.player_type.to_string()) .append_pair( @@ -399,102 +403,102 @@ impl Default for SystemProperties { } pub fn set_clipboard<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let new_content = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_string(avm, action_context)? + .coerce_to_string(activation, action_context)? .to_string(); action_context.input.set_clipboard_content(new_content); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn show_settings<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { //TODO: should default to the last panel displayed let last_panel_pos = 0; let panel_pos = args .get(0) .unwrap_or(&Value::Number(last_panel_pos as f64)) - .coerce_to_i32(avm, action_context)?; + .coerce_to_i32(activation, action_context)?; let panel = SettingsPanel::try_from(panel_pos as u8).unwrap_or(SettingsPanel::Privacy); log::warn!("System.showSettings({:?}) not not implemented", panel); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn set_use_code_page<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let value = args .get(0) .unwrap_or(&Value::Undefined) .to_owned() - .as_bool(avm.current_swf_version()); + .as_bool(activation.current_swf_version()); action_context.system.use_codepage = value; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn get_use_code_page<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(action_context.system.use_codepage.into()) } pub fn set_exact_settings<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let value = args .get(0) .unwrap_or(&Value::Undefined) .to_owned() - .as_bool(avm.current_swf_version()); + .as_bool(activation.current_swf_version()); action_context.system.exact_settings = value; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn get_exact_settings<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(action_context.system.exact_settings.into()) } pub fn on_status<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _action_context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("System.onStatus() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn create<'gc>( diff --git a/core/src/avm1/globals/system_capabilities.rs b/core/src/avm1/globals/system_capabilities.rs index f1e15870f..73c090105 100644 --- a/core/src/avm1/globals/system_capabilities.rs +++ b/core/src/avm1/globals/system_capabilities.rs @@ -1,9 +1,9 @@ +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::globals::system::SystemCapabilities; use crate::avm1::object::Object; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, ScriptObject, TObject, Value}; +use crate::avm1::{ScriptObject, TObject, Value}; use crate::context::UpdateContext; use enumset::EnumSet; use gc_arena::MutationContext; @@ -11,11 +11,11 @@ use gc_arena::MutationContext; macro_rules! capabilities_func { ($func_name: ident, $capability: expr) => { pub fn $func_name<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], - ) -> Result, Error<'gc>> { + ) -> Result, Error<'gc>> { Ok(context.system.has_capability($capability).into()) } }; @@ -24,11 +24,11 @@ macro_rules! capabilities_func { macro_rules! inverse_capabilities_func { ($func_name: ident, $capability: expr) => { pub fn $func_name<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], - ) -> Result, Error<'gc>> { + ) -> Result, Error<'gc>> { Ok((!context.system.has_capability($capability)).into()) } }; @@ -77,127 +77,127 @@ inverse_capabilities_func!(get_is_av_hardware_disabled, SystemCapabilities::AvHa inverse_capabilities_func!(get_is_windowless_disabled, SystemCapabilities::WindowLess); pub fn get_player_type<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(context.system.player_type.to_string().into()) } pub fn get_screen_color<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(context.system.screen_color.to_string().into()) } pub fn get_language<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(context .system .language - .get_language_code(avm.player_version) + .get_language_code(activation.avm().player_version) .into()) } pub fn get_screen_resolution_x<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(context.system.screen_resolution.0.into()) } pub fn get_screen_resolution_y<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(context.system.screen_resolution.1.into()) } pub fn get_pixel_aspect_ratio<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(context.system.aspect_ratio.into()) } pub fn get_screen_dpi<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(context.system.dpi.into()) } pub fn get_manufacturer<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(context .system .manufacturer - .get_manufacturer_string(avm.player_version) + .get_manufacturer_string(activation.avm().player_version) .into()) } pub fn get_os_name<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(context.system.os.to_string().into()) } pub fn get_version<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(context.system.get_version_string(avm).into()) +) -> Result, Error<'gc>> { + Ok(context.system.get_version_string(activation).into()) } pub fn get_server_string<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(context.system.get_server_string(avm).into()) +) -> Result, Error<'gc>> { + Ok(context.system.get_server_string(activation).into()) } pub fn get_cpu_architecture<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(context.system.cpu_architecture.to_string().into()) } pub fn get_max_idc_level<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(context.system.idc_level.clone().into()) } diff --git a/core/src/avm1/globals/system_ime.rs b/core/src/avm1/globals/system_ime.rs index aac2f6d18..f7d26072b 100644 --- a/core/src/avm1/globals/system_ime.rs +++ b/core/src/avm1/globals/system_ime.rs @@ -1,74 +1,74 @@ +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::listeners::Listeners; use crate::avm1::object::Object; use crate::avm1::property::Attribute; use crate::avm1::property::Attribute::{DontDelete, DontEnum, ReadOnly}; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, ScriptObject, TObject, Value}; +use crate::avm1::{ScriptObject, TObject, Value}; use crate::context::UpdateContext; use gc_arena::MutationContext; use std::convert::Into; fn on_ime_composition<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(false.into()) } fn do_conversion<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(true.into()) } fn get_conversion_mode<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok("KOREAN".into()) } fn get_enabled<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(false.into()) } fn set_composition_string<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(false.into()) } fn set_conversion_mode<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(false.into()) } fn set_enabled<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(false.into()) } diff --git a/core/src/avm1/globals/system_security.rs b/core/src/avm1/globals/system_security.rs index 80b8d5e9f..601e25824 100644 --- a/core/src/avm1/globals/system_security.rs +++ b/core/src/avm1/globals/system_security.rs @@ -1,80 +1,80 @@ +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::object::Object; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, ScriptObject, TObject, Value}; +use crate::avm1::{ScriptObject, TObject, Value}; use crate::context::UpdateContext; use enumset::EnumSet; use gc_arena::MutationContext; use std::convert::Into; fn allow_domain<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("System.security.allowDomain() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn allow_insecure_domain<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("System.security.allowInsecureDomain() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn load_policy_file<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("System.security.allowInsecureDomain() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn escape_domain<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("System.security.escapeDomain() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn get_sandbox_type<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(context.system.sandbox_type.to_string().into()) } fn get_choose_local_swf_path<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("System.security.chooseLocalSwfPath() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn policy_file_resolver<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { log::warn!("System.security.chooseLocalSwfPath() not implemented"); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn create<'gc>( diff --git a/core/src/avm1/globals/text_field.rs b/core/src/avm1/globals/text_field.rs index 308f001e2..5f8d9da77 100644 --- a/core/src/avm1/globals/text_field.rs +++ b/core/src/avm1/globals/text_field.rs @@ -1,94 +1,95 @@ +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::globals::display_object; use crate::avm1::property::Attribute::*; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, UpdateContext, Value}; +use crate::avm1::{Object, ScriptObject, TObject, UpdateContext, Value}; use crate::display_object::{AutoSizeMode, EditText, TDisplayObject}; use crate::html::TextFormat; use gc_arena::MutationContext; /// Implements `TextField` pub fn constructor<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(Value::Undefined.into()) +) -> Result, Error<'gc>> { + Ok(Value::Undefined) } pub fn get_text<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(display_object) = this.as_display_object() { if let Some(text_field) = display_object.as_edit_text() { return Ok(text_field.text().into()); } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn set_text<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(display_object) = this.as_display_object() { if let Some(text_field) = display_object.as_edit_text() { if let Some(value) = args.get(0) { - if let Err(err) = - text_field.set_text(value.coerce_to_string(avm, context)?.to_string(), context) - { + if let Err(err) = text_field.set_text( + value.coerce_to_string(activation, context)?.to_string(), + context, + ) { log::error!("Error when setting TextField.text: {}", err); } - text_field.propagate_text_binding(avm, context); + text_field.propagate_text_binding(activation, context); } } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn get_html<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(display_object) = this.as_display_object() { if let Some(text_field) = display_object.as_edit_text() { return Ok(text_field.is_html().into()); } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn set_html<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(display_object) = this.as_display_object() { if let Some(text_field) = display_object.as_edit_text() { if let Some(value) = args.get(0) { - text_field.set_is_html(context, value.as_bool(avm.current_swf_version())); + text_field.set_is_html(context, value.as_bool(activation.current_swf_version())); } } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn get_html_text<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(display_object) = this.as_display_object() { if let Some(text_field) = display_object.as_edit_text() { if let Ok(text) = text_field.html_text(context) { @@ -96,104 +97,104 @@ pub fn get_html_text<'gc>( } } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn set_html_text<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(display_object) = this.as_display_object() { if let Some(text_field) = display_object.as_edit_text() { let text = args .get(0) .unwrap_or(&Value::Undefined) - .coerce_to_string(avm, context)?; + .coerce_to_string(activation, context)?; let _ = text_field.set_html_text(text.into_owned(), context); // Changing the htmlText does NOT update variable bindings (does not call EditText::propagate_text_binding). } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn get_border<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(display_object) = this.as_display_object() { if let Some(text_field) = display_object.as_edit_text() { return Ok(text_field.has_border().into()); } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn set_border<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(display_object) = this.as_display_object() { if let Some(text_field) = display_object.as_edit_text() { if let Some(value) = args.get(0) { - let has_border = value.as_bool(avm.current_swf_version()); + let has_border = value.as_bool(activation.current_swf_version()); text_field.set_has_border(context.gc_context, has_border); } } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn get_embed_fonts<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(display_object) = this.as_display_object() { if let Some(text_field) = display_object.as_edit_text() { return Ok((!text_field.is_device_font()).into()); } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn set_embed_fonts<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(display_object) = this.as_display_object() { if let Some(text_field) = display_object.as_edit_text() { if let Some(value) = args.get(0) { - let embed_fonts = value.as_bool(avm.current_swf_version()); + let embed_fonts = value.as_bool(activation.current_swf_version()); text_field.set_is_device_font(context, !embed_fonts); } } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn get_length<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(display_object) = this.as_display_object() { if let Some(text_field) = display_object.as_edit_text() { return Ok((text_field.text_length() as f64).into()); } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } macro_rules! with_text_field { @@ -201,13 +202,13 @@ macro_rules! with_text_field { $( $object.force_set_function( $name, - |avm, context: &mut UpdateContext<'_, 'gc, '_>, this, args| -> Result, Error<'gc>> { + |activation, context: &mut UpdateContext<'_, 'gc, '_>, this, args| -> Result, Error<'gc>> { if let Some(display_object) = this.as_display_object() { if let Some(text_field) = display_object.as_edit_text() { - return $fn(text_field, avm, context, args); + return $fn(text_field, activation, context, args); } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } as crate::avm1::function::NativeFunction<'gc>, $gc_context, DontDelete | ReadOnly | DontEnum, @@ -218,11 +219,11 @@ macro_rules! with_text_field { } pub fn text_width<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(etext) = this .as_display_object() .and_then(|dobj| dobj.as_edit_text()) @@ -232,15 +233,15 @@ pub fn text_width<'gc>( return Ok(metrics.0.to_pixels().into()); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn text_height<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(etext) = this .as_display_object() .and_then(|dobj| dobj.as_edit_text()) @@ -250,15 +251,15 @@ pub fn text_height<'gc>( return Ok(metrics.1.to_pixels().into()); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn multiline<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(etext) = this .as_display_object() .and_then(|dobj| dobj.as_edit_text()) @@ -266,20 +267,20 @@ pub fn multiline<'gc>( return Ok(etext.is_multiline().into()); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn set_multiline<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let is_multiline = args .get(0) .cloned() .unwrap_or(Value::Undefined) - .as_bool(avm.current_swf_version()); + .as_bool(activation.current_swf_version()); if let Some(etext) = this .as_display_object() @@ -288,15 +289,15 @@ pub fn set_multiline<'gc>( etext.set_multiline(is_multiline, context); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn variable<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(etext) = this .as_display_object() .and_then(|dobj| dobj.as_edit_text()) @@ -307,36 +308,36 @@ fn variable<'gc>( } // Unset `variable` retuns null, not undefined - Ok(Value::Null.into()) + Ok(Value::Null) } fn set_variable<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let variable = match args.get(0) { None | Some(Value::Undefined) | Some(Value::Null) => None, - Some(v) => Some(v.coerce_to_string(avm, context)?), + Some(v) => Some(v.coerce_to_string(activation, context)?), }; if let Some(etext) = this .as_display_object() .and_then(|dobj| dobj.as_edit_text()) { - etext.set_variable(variable.map(|v| v.into_owned()), avm, context); + etext.set_variable(variable.map(|v| v.into_owned()), activation, context); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn word_wrap<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(etext) = this .as_display_object() .and_then(|dobj| dobj.as_edit_text()) @@ -344,20 +345,20 @@ pub fn word_wrap<'gc>( return Ok(etext.is_word_wrap().into()); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn set_word_wrap<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let is_word_wrap = args .get(0) .cloned() .unwrap_or(Value::Undefined) - .as_bool(avm.current_swf_version()); + .as_bool(activation.current_swf_version()); if let Some(etext) = this .as_display_object() @@ -366,15 +367,15 @@ pub fn set_word_wrap<'gc>( etext.set_word_wrap(is_word_wrap, context); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn auto_size<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(etext) = this .as_display_object() .and_then(|dobj| dobj.as_edit_text()) @@ -387,15 +388,15 @@ pub fn auto_size<'gc>( }); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn set_auto_size<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(etext) = this .as_display_object() .and_then(|dobj| dobj.as_edit_text()) @@ -412,7 +413,7 @@ pub fn set_auto_size<'gc>( ); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn create_proto<'gc>( @@ -527,44 +528,44 @@ pub fn attach_virtual_properties<'gc>(gc_context: MutationContext<'gc, '_>, obje fn get_new_text_format<'gc>( text_field: EditText<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let tf = text_field.new_text_format(); - Ok(tf.as_avm1_object(avm, context)?.into()) + Ok(tf.as_avm1_object(activation, context)?.into()) } fn set_new_text_format<'gc>( text_field: EditText<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let tf = args.get(0).cloned().unwrap_or(Value::Undefined); if let Value::Object(tf) = tf { - let tf_parsed = TextFormat::from_avm1_object(tf, avm, context)?; + let tf_parsed = TextFormat::from_avm1_object(tf, activation, context)?; text_field.set_new_text_format(tf_parsed, context); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn get_text_format<'gc>( text_field: EditText<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let (from, to) = match (args.get(0), args.get(1)) { (Some(f), Some(t)) => ( - f.coerce_to_f64(avm, context)? as usize, - t.coerce_to_f64(avm, context)? as usize, + f.coerce_to_f64(activation, context)? as usize, + t.coerce_to_f64(activation, context)? as usize, ), (Some(f), None) => { - let v = f.coerce_to_f64(avm, context)? as usize; + let v = f.coerce_to_f64(activation, context)? as usize; (v, v.saturating_add(1)) } _ => (0, text_field.text_length()), @@ -572,28 +573,28 @@ fn get_text_format<'gc>( Ok(text_field .text_format(from, to) - .as_avm1_object(avm, context)? + .as_avm1_object(activation, context)? .into()) } fn set_text_format<'gc>( text_field: EditText<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let tf = args.last().cloned().unwrap_or(Value::Undefined); if let Value::Object(tf) = tf { - let tf_parsed = TextFormat::from_avm1_object(tf, avm, context)?; + let tf_parsed = TextFormat::from_avm1_object(tf, activation, context)?; let (from, to) = match (args.get(0), args.get(1)) { (Some(f), Some(t)) if args.len() > 2 => ( - f.coerce_to_f64(avm, context)? as usize, - t.coerce_to_f64(avm, context)? as usize, + f.coerce_to_f64(activation, context)? as usize, + t.coerce_to_f64(activation, context)? as usize, ), (Some(f), _) if args.len() > 1 => { - let v = f.coerce_to_f64(avm, context)? as usize; + let v = f.coerce_to_f64(activation, context)? as usize; (v, v.saturating_add(1)) } _ => (0, text_field.text_length()), @@ -602,33 +603,33 @@ fn set_text_format<'gc>( text_field.set_text_format(from, to, tf_parsed, context); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } fn replace_text<'gc>( text_field: EditText<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let from = args .get(0) .cloned() .unwrap_or(Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let to = args .get(1) .cloned() .unwrap_or(Value::Undefined) - .coerce_to_f64(avm, context)?; + .coerce_to_f64(activation, context)?; let text = args .get(2) .cloned() .unwrap_or(Value::Undefined) - .coerce_to_string(avm, context)? + .coerce_to_string(activation, context)? .into_owned(); text_field.replace_text(from as usize, to as usize, &text, context); - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } diff --git a/core/src/avm1/globals/text_format.rs b/core/src/avm1/globals/text_format.rs index 4e3371750..2ed5da0ea 100644 --- a/core/src/avm1/globals/text_format.rs +++ b/core/src/avm1/globals/text_format.rs @@ -1,14 +1,14 @@ //! `TextFormat` impl +use crate::avm1::activation::Activation; use crate::avm1::error::Error; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, UpdateContext, Value}; +use crate::avm1::{Object, ScriptObject, TObject, UpdateContext, Value}; use gc_arena::MutationContext; fn map_defined_to_string<'gc>( name: &str, this: Object<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, val: Option>, ) -> Result<(), Error<'gc>> { @@ -16,10 +16,10 @@ fn map_defined_to_string<'gc>( Some(Value::Undefined) => Value::Null, Some(Value::Null) => Value::Null, None => Value::Null, - Some(v) => v.coerce_to_string(avm, ac)?.into(), + Some(v) => v.coerce_to_string(activation, ac)?.into(), }; - this.set(name, val, avm, ac)?; + this.set(name, val, activation, ac)?; Ok(()) } @@ -27,7 +27,7 @@ fn map_defined_to_string<'gc>( fn map_defined_to_number<'gc>( name: &str, this: Object<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, val: Option>, ) -> Result<(), Error<'gc>> { @@ -35,10 +35,10 @@ fn map_defined_to_number<'gc>( Some(Value::Undefined) => Value::Null, Some(Value::Null) => Value::Null, None => Value::Null, - Some(v) => v.coerce_to_f64(avm, ac)?.into(), + Some(v) => v.coerce_to_f64(activation, ac)?.into(), }; - this.set(name, val, avm, ac)?; + this.set(name, val, activation, ac)?; Ok(()) } @@ -46,7 +46,7 @@ fn map_defined_to_number<'gc>( fn map_defined_to_bool<'gc>( name: &str, this: Object<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, val: Option>, ) -> Result<(), Error<'gc>> { @@ -54,36 +54,36 @@ fn map_defined_to_bool<'gc>( Some(Value::Undefined) => Value::Null, Some(Value::Null) => Value::Null, None => Value::Null, - Some(v) => v.as_bool(avm.current_swf_version()).into(), + Some(v) => v.as_bool(activation.current_swf_version()).into(), }; - this.set(name, val, avm, ac)?; + this.set(name, val, activation, ac)?; Ok(()) } /// `TextFormat` constructor pub fn constructor<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { - map_defined_to_string("font", this, avm, ac, args.get(0).cloned())?; - map_defined_to_number("size", this, avm, ac, args.get(1).cloned())?; - map_defined_to_number("color", this, avm, ac, args.get(2).cloned())?; - map_defined_to_bool("bold", this, avm, ac, args.get(3).cloned())?; - map_defined_to_bool("italic", this, avm, ac, args.get(4).cloned())?; - map_defined_to_bool("underline", this, avm, ac, args.get(5).cloned())?; - map_defined_to_string("url", this, avm, ac, args.get(6).cloned())?; - map_defined_to_string("target", this, avm, ac, args.get(7).cloned())?; - map_defined_to_string("align", this, avm, ac, args.get(8).cloned())?; - map_defined_to_number("leftMargin", this, avm, ac, args.get(9).cloned())?; - map_defined_to_number("rightMargin", this, avm, ac, args.get(10).cloned())?; - map_defined_to_number("indent", this, avm, ac, args.get(11).cloned())?; - map_defined_to_number("leading", this, avm, ac, args.get(12).cloned())?; +) -> Result, Error<'gc>> { + map_defined_to_string("font", this, activation, ac, args.get(0).cloned())?; + map_defined_to_number("size", this, activation, ac, args.get(1).cloned())?; + map_defined_to_number("color", this, activation, ac, args.get(2).cloned())?; + map_defined_to_bool("bold", this, activation, ac, args.get(3).cloned())?; + map_defined_to_bool("italic", this, activation, ac, args.get(4).cloned())?; + map_defined_to_bool("underline", this, activation, ac, args.get(5).cloned())?; + map_defined_to_string("url", this, activation, ac, args.get(6).cloned())?; + map_defined_to_string("target", this, activation, ac, args.get(7).cloned())?; + map_defined_to_string("align", this, activation, ac, args.get(8).cloned())?; + map_defined_to_number("leftMargin", this, activation, ac, args.get(9).cloned())?; + map_defined_to_number("rightMargin", this, activation, ac, args.get(10).cloned())?; + map_defined_to_number("indent", this, activation, ac, args.get(11).cloned())?; + map_defined_to_number("leading", this, activation, ac, args.get(12).cloned())?; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } /// `TextFormat.prototype` constructor diff --git a/core/src/avm1/globals/xml.rs b/core/src/avm1/globals/xml.rs index 21a75ca62..9fd8440bd 100644 --- a/core/src/avm1/globals/xml.rs +++ b/core/src/avm1/globals/xml.rs @@ -1,12 +1,12 @@ //! XML/XMLNode global classes +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::property::Attribute::*; -use crate::avm1::return_value::ReturnValue; use crate::avm1::script_object::ScriptObject; use crate::avm1::xml_object::XMLObject; -use crate::avm1::{Avm1, Object, TObject, UpdateContext, Value}; +use crate::avm1::{Object, TObject, UpdateContext, Value}; use crate::backend::navigator::RequestOptions; use crate::xml; use crate::xml::{XMLDocument, XMLNode}; @@ -42,17 +42,17 @@ fn is_as2_compatible(node: XMLNode<'_>) -> bool { /// XMLNode constructor pub fn xmlnode_constructor<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let blank_document = XMLDocument::new(ac.gc_context); match ( args.get(0) - .map(|v| v.coerce_to_f64(avm, ac).map(|v| v as u32)), - args.get(1).map(|v| v.coerce_to_string(avm, ac)), + .map(|v| v.coerce_to_f64(activation, ac).map(|v| v as u32)), + args.get(1).map(|v| v.coerce_to_string(activation, ac)), this.as_xml_node(), ) { (Some(Ok(1)), Some(Ok(ref strval)), Some(ref mut this_node)) => { @@ -69,19 +69,19 @@ pub fn xmlnode_constructor<'gc>( _ => {} }; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xmlnode_append_child<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let (Some(mut xmlnode), Some(child_xmlnode)) = ( this.as_xml_node(), args.get(0) - .and_then(|n| n.coerce_to_object(avm, ac).as_xml_node()), + .and_then(|n| n.coerce_to_object(activation, ac).as_xml_node()), ) { if let Ok(None) = child_xmlnode.parent() { let position = xmlnode.children_len(); @@ -91,21 +91,21 @@ pub fn xmlnode_append_child<'gc>( } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xmlnode_insert_before<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let (Some(mut xmlnode), Some(child_xmlnode), Some(insertpoint_xmlnode)) = ( this.as_xml_node(), args.get(0) - .and_then(|n| n.coerce_to_object(avm, ac).as_xml_node()), + .and_then(|n| n.coerce_to_object(activation, ac).as_xml_node()), args.get(1) - .and_then(|n| n.coerce_to_object(avm, ac).as_xml_node()), + .and_then(|n| n.coerce_to_object(activation, ac).as_xml_node()), ) { if let Ok(None) = child_xmlnode.parent() { if let Some(position) = xmlnode.child_position(insertpoint_xmlnode) { @@ -119,91 +119,91 @@ pub fn xmlnode_insert_before<'gc>( } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xmlnode_clone_node<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let (Some(xmlnode), deep) = ( this.as_xml_node(), args.get(0) - .map(|v| v.as_bool(avm.current_swf_version())) + .map(|v| v.as_bool(activation.current_swf_version())) .unwrap_or(false), ) { let mut clone_node = xmlnode.duplicate(ac.gc_context, deep); - return Ok(Value::Object( - clone_node.script_object(ac.gc_context, Some(avm.prototypes.xml_node)), - ) - .into()); + return Ok(Value::Object(clone_node.script_object( + ac.gc_context, + Some(activation.avm().prototypes.xml_node), + ))); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xmlnode_get_namespace_for_prefix<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let (Some(xmlnode), Some(prefix_string)) = ( this.as_xml_node(), - args.get(0).map(|v| v.coerce_to_string(avm, ac)), + args.get(0).map(|v| v.coerce_to_string(activation, ac)), ) { if let Some(uri) = xmlnode.lookup_uri_for_namespace(&prefix_string?) { Ok(uri.into()) } else { - Ok(Value::Null.into()) + Ok(Value::Null) } } else { - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } pub fn xmlnode_get_prefix_for_namespace<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let (Some(xmlnode), Some(uri_string)) = ( this.as_xml_node(), - args.get(0).map(|v| v.coerce_to_string(avm, ac)), + args.get(0).map(|v| v.coerce_to_string(activation, ac)), ) { if let Some(prefix) = xmlnode.lookup_namespace_for_uri(&uri_string?) { Ok(prefix.into()) } else { - Ok(Value::Null.into()) + Ok(Value::Null) } } else { - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } pub fn xmlnode_has_child_nodes<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(xmlnode) = this.as_xml_node() { Ok((xmlnode.children_len() > 0).into()) } else { - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } } pub fn xmlnode_remove_node<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(node) = this.as_xml_node() { if let Ok(Some(mut parent)) = node.parent() { if let Err(e) = parent.remove_child(ac.gc_context, node) { @@ -212,15 +212,15 @@ pub fn xmlnode_remove_node<'gc>( } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xmlnode_to_string<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(node) = this.as_xml_node() { let result = node.into_string(&mut is_as2_compatible); @@ -236,37 +236,37 @@ pub fn xmlnode_to_string<'gc>( } pub fn xmlnode_local_name<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(this .as_xml_node() .and_then(|n| n.tag_name()) .map(|n| n.local_name().to_string().into()) - .unwrap_or_else(|| Value::Null.into())) + .unwrap_or_else(|| Value::Null)) } pub fn xmlnode_node_name<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(this .as_xml_node() .and_then(|n| n.tag_name()) .map(|n| n.node_name().into()) - .unwrap_or_else(|| Value::Null.into())) + .unwrap_or_else(|| Value::Null)) } pub fn xmlnode_node_type<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(this .as_xml_node() .map(|n| { @@ -278,28 +278,28 @@ pub fn xmlnode_node_type<'gc>( } .into() }) - .unwrap_or_else(|| Value::Undefined.into())) + .unwrap_or_else(|| Value::Undefined)) } pub fn xmlnode_node_value<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(this .as_xml_node() .and_then(|n| n.node_value()) .map(|n| n.into()) - .unwrap_or_else(|| Value::Null.into())) + .unwrap_or_else(|| Value::Null)) } pub fn xmlnode_prefix<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { Ok(this .as_xml_node() .and_then(|n| n.tag_name()) @@ -308,17 +308,17 @@ pub fn xmlnode_prefix<'gc>( .map(|n| n.to_string().into()) .unwrap_or_else(|| "".to_string().into()) }) - .unwrap_or_else(|| Value::Null.into())) + .unwrap_or_else(|| Value::Null)) } pub fn xmlnode_child_nodes<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(node) = this.as_xml_node() { - let array = ScriptObject::array(ac.gc_context, Some(avm.prototypes.array)); + let array = ScriptObject::array(ac.gc_context, Some(activation.avm().prototypes.array)); if let Some(children) = node.children() { let mut compatible_nodes = 0; for mut child in children { @@ -329,7 +329,7 @@ pub fn xmlnode_child_nodes<'gc>( array.set_array_element( compatible_nodes as usize, child - .script_object(ac.gc_context, Some(avm.prototypes.xml_node)) + .script_object(ac.gc_context, Some(activation.avm().prototypes.xml_node)) .into(), ac.gc_context, ); @@ -341,80 +341,80 @@ pub fn xmlnode_child_nodes<'gc>( return Ok(array.into()); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xmlnode_first_child<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(node) = this.as_xml_node() { if let Some(mut children) = node.children() { return Ok(children .next() .map(|mut child| { child - .script_object(ac.gc_context, Some(avm.prototypes.xml_node)) + .script_object(ac.gc_context, Some(activation.avm().prototypes.xml_node)) .into() }) - .unwrap_or_else(|| Value::Null.into())); + .unwrap_or_else(|| Value::Null)); } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xmlnode_last_child<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(node) = this.as_xml_node() { if let Some(mut children) = node.children() { return Ok(children .next_back() .map(|mut child| { child - .script_object(ac.gc_context, Some(avm.prototypes.xml_node)) + .script_object(ac.gc_context, Some(activation.avm().prototypes.xml_node)) .into() }) - .unwrap_or_else(|| Value::Null.into())); + .unwrap_or_else(|| Value::Null)); } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xmlnode_parent_node<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(node) = this.as_xml_node() { return Ok(node .parent() .unwrap_or(None) .map(|mut parent| { parent - .script_object(ac.gc_context, Some(avm.prototypes.xml_node)) + .script_object(ac.gc_context, Some(activation.avm().prototypes.xml_node)) .into() }) - .unwrap_or_else(|| Value::Null.into())); + .unwrap_or_else(|| Value::Null)); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xmlnode_previous_sibling<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(node) = this.as_xml_node() { let mut prev = node.prev_sibling().unwrap_or(None); while let Some(my_prev) = prev { @@ -427,21 +427,21 @@ pub fn xmlnode_previous_sibling<'gc>( return Ok(prev .map(|mut prev| { - prev.script_object(ac.gc_context, Some(avm.prototypes.xml_node)) + prev.script_object(ac.gc_context, Some(activation.avm().prototypes.xml_node)) .into() }) - .unwrap_or_else(|| Value::Null.into())); + .unwrap_or_else(|| Value::Null)); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xmlnode_next_sibling<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(node) = this.as_xml_node() { let mut next = node.next_sibling().unwrap_or(None); while let Some(my_next) = next { @@ -454,37 +454,37 @@ pub fn xmlnode_next_sibling<'gc>( return Ok(next .map(|mut next| { - next.script_object(ac.gc_context, Some(avm.prototypes.xml_node)) + next.script_object(ac.gc_context, Some(activation.avm().prototypes.xml_node)) .into() }) - .unwrap_or_else(|| Value::Null.into())); + .unwrap_or_else(|| Value::Null)); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xmlnode_attributes<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(mut node) = this.as_xml_node() { return Ok(node .attribute_script_object(ac.gc_context) .map(|o| o.into()) - .unwrap_or_else(|| Value::Undefined.into())); + .unwrap_or_else(|| Value::Undefined)); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xmlnode_namespace_uri<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(node) = this.as_xml_node() { if let Some(name) = node.tag_name() { return Ok(node @@ -493,10 +493,10 @@ pub fn xmlnode_namespace_uri<'gc>( .unwrap_or_else(|| "".into())); } - return Ok(Value::Null.into()); + return Ok(Value::Null); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } /// Construct the prototype for `XMLNode`. @@ -684,13 +684,13 @@ pub fn create_xmlnode_proto<'gc>( /// XML (document) constructor pub fn xml_constructor<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { match ( - args.get(0).map(|v| v.coerce_to_string(avm, ac)), + args.get(0).map(|v| v.coerce_to_string(activation, ac)), this.as_xml_node(), ) { (Some(Ok(ref string)), Some(ref mut this_node)) => { @@ -713,15 +713,15 @@ pub fn xml_constructor<'gc>( _ => {} }; - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xml_create_element<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let document = if let Some(node) = this.as_xml_node() { node.document() } else { @@ -730,10 +730,14 @@ pub fn xml_create_element<'gc>( let nodename = args .get(0) - .map(|v| v.coerce_to_string(avm, ac).unwrap_or_default()) + .map(|v| v.coerce_to_string(activation, ac).unwrap_or_default()) .unwrap_or_default(); let mut xml_node = XMLNode::new_element(ac.gc_context, &nodename, document); - let object = XMLObject::from_xml_node(ac.gc_context, xml_node, Some(avm.prototypes().xml_node)); + let object = XMLObject::from_xml_node( + ac.gc_context, + xml_node, + Some(activation.avm().prototypes().xml_node), + ); xml_node.introduce_script_object(ac.gc_context, object); @@ -741,11 +745,11 @@ pub fn xml_create_element<'gc>( } pub fn xml_create_text_node<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let document = if let Some(node) = this.as_xml_node() { node.document() } else { @@ -754,10 +758,14 @@ pub fn xml_create_text_node<'gc>( let text_node = args .get(0) - .map(|v| v.coerce_to_string(avm, ac).unwrap_or_default()) + .map(|v| v.coerce_to_string(activation, ac).unwrap_or_default()) .unwrap_or_default(); let mut xml_node = XMLNode::new_text(ac.gc_context, &text_node, document); - let object = XMLObject::from_xml_node(ac.gc_context, xml_node, Some(avm.prototypes().xml_node)); + let object = XMLObject::from_xml_node( + ac.gc_context, + xml_node, + Some(activation.avm().prototypes().xml_node), + ); xml_node.introduce_script_object(ac.gc_context, object); @@ -765,14 +773,14 @@ pub fn xml_create_text_node<'gc>( } pub fn xml_parse_xml<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(mut node) = this.as_xml_node() { let xmlstring = - if let Some(Ok(xmlstring)) = args.get(0).map(|s| s.coerce_to_string(avm, ac)) { + if let Some(Ok(xmlstring)) = args.get(0).map(|s| s.coerce_to_string(activation, ac)) { xmlstring } else { Cow::Borrowed("") @@ -783,7 +791,7 @@ pub fn xml_parse_xml<'gc>( let result = node.remove_child(ac.gc_context, child); if let Err(e) = result { log::warn!("XML.parseXML: Error removing node contents: {}", e); - return Ok(Value::Undefined.into()); + return Ok(Value::Undefined); } } } @@ -794,15 +802,15 @@ pub fn xml_parse_xml<'gc>( } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xml_load<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let url = args.get(0).cloned().unwrap_or(Value::Undefined); if let Value::Null = url { @@ -810,12 +818,12 @@ pub fn xml_load<'gc>( } if let Some(node) = this.as_xml_node() { - let url = url.coerce_to_string(avm, ac)?; + let url = url.coerce_to_string(activation, ac)?; - this.set("loaded", false.into(), avm, ac)?; + this.set("loaded", false.into(), activation, ac)?; let fetch = ac.navigator.fetch(&url, RequestOptions::get()); - let target_clip = avm.target_clip_or_root(); + let target_clip = activation.target_clip_or_root(); let process = ac.load_manager.load_xml_into_node( ac.player.clone().unwrap(), node, @@ -832,33 +840,33 @@ pub fn xml_load<'gc>( } pub fn xml_on_data<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { let src = args.get(0).cloned().unwrap_or(Value::Undefined); if let Value::Undefined = src { - this.call_method("onLoad", &[false.into()], avm, ac)?; + this.call_method("onLoad", &[false.into()], activation, ac)?; } else { - let src = src.coerce_to_string(avm, ac)?; - this.call_method("parseXML", &[src.into()], avm, ac)?; + let src = src.coerce_to_string(activation, ac)?; + this.call_method("parseXML", &[src.into()], activation, ac)?; - this.set("loaded", true.into(), avm, ac)?; + this.set("loaded", true.into(), activation, ac)?; - this.call_method("onLoad", &[true.into()], avm, ac)?; + this.call_method("onLoad", &[true.into()], activation, ac)?; } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xml_doc_type_decl<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(node) = this.as_xml_node() { if let Some(doctype) = node.document().doctype() { let result = doctype.into_string(&mut |_| true); @@ -872,15 +880,15 @@ pub fn xml_doc_type_decl<'gc>( } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xml_xml_decl<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(node) = this.as_xml_node() { let result = node.document().xmldecl_string(); @@ -891,55 +899,49 @@ pub fn xml_xml_decl<'gc>( } } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xml_id_map<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(node) = this.as_xml_node() { return Ok(node.document().idmap_script_object(ac.gc_context).into()); } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } pub fn xml_status<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _ac: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], -) -> Result, Error<'gc>> { +) -> Result, Error<'gc>> { if let Some(node) = this.as_xml_node() { return match node.document().last_parse_error() { None => Ok(XML_NO_ERROR.into()), Some(err) => match err.ref_error() { - ParseError::UnexpectedEof(_) => Ok(Value::Number(XML_ELEMENT_MALFORMED).into()), - ParseError::EndEventMismatch { .. } => Ok(Value::Number(XML_MISMATCHED_END).into()), - ParseError::XmlDeclWithoutVersion(_) => { - Ok(Value::Number(XML_DECL_NOT_TERMINATED).into()) - } - ParseError::NameWithQuote(_) => Ok(Value::Number(XML_ELEMENT_MALFORMED).into()), - ParseError::NoEqAfterName(_) => Ok(Value::Number(XML_ELEMENT_MALFORMED).into()), - ParseError::UnquotedValue(_) => { - Ok(Value::Number(XML_ATTRIBUTE_NOT_TERMINATED).into()) - } - ParseError::DuplicatedAttribute(_, _) => { - Ok(Value::Number(XML_ELEMENT_MALFORMED).into()) - } - _ => Ok(Value::Number(XML_OUT_OF_MEMORY).into()), //Not accounted for: - //ParseError::UnexpectedToken(_) - //ParseError::UnexpectedBang - //ParseError::TextNotFound - //ParseError::EscapeError(_) + ParseError::UnexpectedEof(_) => Ok(Value::Number(XML_ELEMENT_MALFORMED)), + ParseError::EndEventMismatch { .. } => Ok(Value::Number(XML_MISMATCHED_END)), + ParseError::XmlDeclWithoutVersion(_) => Ok(Value::Number(XML_DECL_NOT_TERMINATED)), + ParseError::NameWithQuote(_) => Ok(Value::Number(XML_ELEMENT_MALFORMED)), + ParseError::NoEqAfterName(_) => Ok(Value::Number(XML_ELEMENT_MALFORMED)), + ParseError::UnquotedValue(_) => Ok(Value::Number(XML_ATTRIBUTE_NOT_TERMINATED)), + ParseError::DuplicatedAttribute(_, _) => Ok(Value::Number(XML_ELEMENT_MALFORMED)), + _ => Ok(Value::Number(XML_OUT_OF_MEMORY)), //Not accounted for: + //ParseError::UnexpectedToken(_) + //ParseError::UnexpectedBang + //ParseError::TextNotFound + //ParseError::EscapeError(_) }, }; } - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } /// Construct the prototype for `XML`. diff --git a/core/src/avm1/listeners.rs b/core/src/avm1/listeners.rs index 874de5940..8b391716e 100644 --- a/core/src/avm1/listeners.rs +++ b/core/src/avm1/listeners.rs @@ -1,7 +1,6 @@ +use crate::avm1::activation::Activation; use crate::avm1::error::Error; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, UpdateContext, Value}; - +use crate::avm1::{Object, ScriptObject, TObject, UpdateContext, Value}; use gc_arena::{Collect, MutationContext}; #[derive(Clone, Collect, Debug, Copy)] @@ -11,24 +10,26 @@ pub struct Listeners<'gc>(Object<'gc>); macro_rules! register_listener { ( $gc_context: ident, $object:ident, $listener: ident, $fn_proto: ident, $system_listeners_key: ident ) => {{ pub fn add_listener<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], - ) -> Result, Error<'gc>> { - avm.system_listeners + ) -> Result, Error<'gc>> { + activation + .avm() + .system_listeners .$system_listeners_key .add_listener(context, args) } pub fn remove_listener<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, args: &[Value<'gc>], - ) -> Result, Error<'gc>> { - let listener = avm.system_listeners.$system_listeners_key; - listener.remove_listener(avm, context, args) + ) -> Result, Error<'gc>> { + let listener = activation.avm().system_listeners.$system_listeners_key; + listener.remove_listener(activation, context, args) } $object.define_value( @@ -65,7 +66,7 @@ impl<'gc> Listeners<'gc> { &self, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], - ) -> Result, Error<'gc>> { + ) -> Result, Error<'gc>> { let listeners = self.0; let listener = args.get(0).unwrap_or(&Value::Undefined).to_owned(); for i in 0..listeners.length() { @@ -80,10 +81,10 @@ impl<'gc> Listeners<'gc> { pub fn remove_listener( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, args: &[Value<'gc>], - ) -> Result, Error<'gc>> { + ) -> Result, Error<'gc>> { let listeners = self.0; let listener = args.get(0).unwrap_or(&Value::Undefined).to_owned(); for index in 0..listeners.length() { @@ -99,7 +100,7 @@ impl<'gc> Listeners<'gc> { } listeners.delete_array_element(new_length, context.gc_context); - listeners.delete(avm, context.gc_context, &new_length.to_string()); + listeners.delete(activation, context.gc_context, &new_length.to_string()); listeners.set_length(context.gc_context, new_length); return Ok(true.into()); @@ -111,7 +112,7 @@ impl<'gc> Listeners<'gc> { pub fn prepare_handlers( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, method: &str, ) -> Vec<(Object<'gc>, Value<'gc>)> { @@ -119,8 +120,10 @@ impl<'gc> Listeners<'gc> { let mut handlers = Vec::with_capacity(listeners.length()); for i in 0..listeners.length() { - let listener = listeners.array_element(i).coerce_to_object(avm, context); - if let Ok(handler) = listener.get(method, avm, context) { + let listener = listeners + .array_element(i) + .coerce_to_object(activation, context); + if let Ok(handler) = listener.get(method, activation, context) { handlers.push((listener, handler)); } } diff --git a/core/src/avm1/object.rs b/core/src/avm1/object.rs index c04196f73..0cde26b9e 100644 --- a/core/src/avm1/object.rs +++ b/core/src/avm1/object.rs @@ -3,15 +3,15 @@ use crate::avm1::error::Error; use crate::avm1::function::{Executable, FunctionObject}; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; use crate::avm1::shared_object::SharedObject; use crate::avm1::super_object::SuperObject; use crate::avm1::value_object::ValueObject; +use crate::avm1::activation::Activation; use crate::avm1::xml_attributes_object::XMLAttributesObject; use crate::avm1::xml_idmap_object::XMLIDMapObject; use crate::avm1::xml_object::XMLObject; -use crate::avm1::{Avm1, ScriptObject, SoundObject, StageObject, UpdateContext, Value}; +use crate::avm1::{ScriptObject, SoundObject, StageObject, UpdateContext, Value}; use crate::display_object::DisplayObject; use crate::xml::XMLNode; use enumset::EnumSet; @@ -50,7 +50,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn get_local( &self, name: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, ) -> Result, Error<'gc>>; @@ -59,15 +59,13 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn get( &self, name: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error<'gc>> { - if self.has_own_property(avm, context, name) { - self.get_local(name, avm, context, (*self).into()) + if self.has_own_property(activation, context, name) { + self.get_local(name, activation, context, (*self).into()) } else { - search_prototype(self.proto(), name, avm, context, (*self).into())? - .0 - .resolve(avm, context) + Ok(search_prototype(self.proto(), name, activation, context, (*self).into())?.0) } } @@ -76,7 +74,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error<'gc>>; @@ -87,7 +85,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// it can be changed by `Function.apply`/`Function.call`. fn call( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, @@ -105,24 +103,29 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &self, name: &str, args: &[Value<'gc>], - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error<'gc>> { - let (method, base_proto) = - search_prototype(Some((*self).into()), name, avm, context, (*self).into())?; - let method = method.resolve(avm, context)?; + let (method, base_proto) = search_prototype( + Some((*self).into()), + name, + activation, + context, + (*self).into(), + )?; + let method = method; if let Value::Object(_) = method { } else { log::warn!("Object method {} is not callable", name); } - method.call(avm, context, (*self).into(), base_proto, args) + method.call(activation, context, (*self).into(), base_proto, args) } /// Call a setter defined in this object. /// - /// This function returns the `ReturnValue` of the called function; it + /// This function may return a `Executable` of the function to call; it /// should be resolved and discarded. Attempts to call a non-virtual setter /// or non-existent setter fail silently. /// @@ -133,10 +136,9 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, - ) -> Result, Error<'gc>>; + ) -> Option>; /// Construct a host object of some kind and return it's cell. /// @@ -151,7 +153,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// purely so that host objects can be constructed by the VM. fn new( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], @@ -160,8 +162,12 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Delete a named property from the object. /// /// Returns false if the property cannot be deleted. - fn delete(&self, avm: &mut Avm1<'gc>, gc_context: MutationContext<'gc, '_>, name: &str) - -> bool; + fn delete( + &self, + activation: &mut Activation<'_, 'gc>, + gc_context: MutationContext<'gc, '_>, + name: &str, + ) -> bool; /// Retrieve the `__proto__` of a given object. /// @@ -242,7 +248,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// as `__proto__`. fn add_property_with_case( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, get: Executable<'gc>, @@ -253,7 +259,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Checks if the object has a given named property. fn has_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool; @@ -262,7 +268,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// say, the object's prototype or superclass) fn has_own_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool; @@ -271,19 +277,19 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// virtual. fn has_own_virtual( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool; /// Checks if a named property can be overwritten. - fn is_property_overwritable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool; + fn is_property_overwritable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool; /// Checks if a named property appears when enumerating the object. - fn is_property_enumerable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool; + fn is_property_enumerable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool; /// Enumerate the object. - fn get_keys(&self, avm: &mut Avm1<'gc>) -> Vec; + fn get_keys(&self, activation: &mut Activation<'_, 'gc>) -> Vec; /// Coerce the object into a string. fn as_string(&self) -> Cow; @@ -314,7 +320,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// somehow could this would support that, too. fn is_instance_of( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, constructor: Object<'gc>, prototype: Object<'gc>, @@ -333,13 +339,13 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy proto_stack.push(p); } - if avm.current_swf_version() >= 7 { + if activation.current_swf_version() >= 7 { for interface in this_proto.interfaces() { if Object::ptr_eq(interface, constructor) { return Ok(true); } - if let Value::Object(o) = interface.get("prototype", avm, context)? { + if let Value::Object(o) = interface.get("prototype", activation, context)? { proto_stack.push(o); } } @@ -464,10 +470,10 @@ impl<'gc> Object<'gc> { pub fn search_prototype<'gc>( mut proto: Option>, name: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, -) -> Result<(ReturnValue<'gc>, Option>), Error<'gc>> { +) -> Result<(Value<'gc>, Option>), Error<'gc>> { let mut depth = 0; while proto.is_some() { @@ -475,9 +481,9 @@ pub fn search_prototype<'gc>( return Err(Error::PrototypeRecursionLimit); } - if proto.unwrap().has_own_property(avm, context, name) { + if proto.unwrap().has_own_property(activation, context, name) { return Ok(( - proto.unwrap().get_local(name, avm, context, this)?.into(), + proto.unwrap().get_local(name, activation, context, this)?, proto, )); } @@ -486,5 +492,5 @@ pub fn search_prototype<'gc>( depth += 1; } - Ok((Value::Undefined.into(), None)) + Ok((Value::Undefined, None)) } diff --git a/core/src/avm1/property.rs b/core/src/avm1/property.rs index 781074d8f..edccb7bea 100644 --- a/core/src/avm1/property.rs +++ b/core/src/avm1/property.rs @@ -1,10 +1,8 @@ //! User-defined properties use self::Attribute::*; -use crate::avm1::error::Error; use crate::avm1::function::Executable; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, UpdateContext, Value}; +use crate::avm1::Value; use core::fmt; use enumset::{EnumSet, EnumSetType}; @@ -32,42 +30,18 @@ pub enum Property<'gc> { } impl<'gc> Property<'gc> { - /// Get the value of a property slot. - /// - /// This function yields `ReturnValue` because some properties may be - /// user-defined. - pub fn get( - &self, - avm: &mut Avm1<'gc>, - context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, - base_proto: Option>, - ) -> Result, Error<'gc>> { - match self { - Property::Virtual { get, .. } => get.exec(avm, context, this, base_proto, &[]), - Property::Stored { value, .. } => Ok(value.to_owned().into()), - } - } - /// Set a property slot. /// - /// This function returns the `ReturnValue` of the property's virtual + /// This function may return an `Executable` of the property's virtual /// function, if any happen to exist. It should be resolved, and it's value /// discarded. - pub fn set( - &mut self, - avm: &mut Avm1<'gc>, - context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, - base_proto: Option>, - new_value: impl Into>, - ) -> Result, Error<'gc>> { + pub fn set(&mut self, new_value: impl Into>) -> Option> { match self { Property::Virtual { set, .. } => { if let Some(function) = set { - function.exec(avm, context, this, base_proto, &[new_value.into()]) + Some(function.to_owned()) } else { - Ok(Value::Undefined.into()) + None } } Property::Stored { @@ -77,7 +51,7 @@ impl<'gc> Property<'gc> { *value = new_value.into(); } - Ok(Value::Undefined.into()) + None } } } diff --git a/core/src/avm1/return_value.rs b/core/src/avm1/return_value.rs deleted file mode 100644 index 3e0b75faf..000000000 --- a/core/src/avm1/return_value.rs +++ /dev/null @@ -1,203 +0,0 @@ -//! Return value enum - -use crate::avm1::activation::Activation; -use crate::avm1::error::Error; -use crate::avm1::{Avm1, Object, Value}; -use crate::context::UpdateContext; -use gc_arena::{Collect, GcCell}; -use std::borrow::Cow; -use std::fmt; - -/// Represents the return value of a function call. -/// -/// Since function calls can result in invoking native code or adding a new -/// activation onto the AVM stack, you need another type to represent how the -/// return value will be delivered to you. -/// -/// This function contains a handful of utility methods for deciding what to do -/// with a given value regardless of how it is delivered to the calling -/// function. -/// -/// It is `must_use` - failing to use a return value is a compiler warning. We -/// provide a helper function specifically to indicate that you aren't -/// interested in the result of a call. -#[must_use = "Return values must be used"] -#[derive(Clone)] -pub enum ReturnValue<'gc> { - /// Indicates that the return value is available immediately. - Immediate(Value<'gc>), - - /// Indicates that the return value is the result of a given user-defined - /// function call. The activation record returned is the frame that needs - /// to return to get your value. - ResultOf(GcCell<'gc, Activation<'gc>>), -} - -unsafe impl<'gc> Collect for ReturnValue<'gc> { - #[inline] - fn trace(&self, cc: gc_arena::CollectionContext) { - use ReturnValue::*; - - match self { - Immediate(value) => value.trace(cc), - ResultOf(frame) => frame.trace(cc), - } - } -} - -impl PartialEq for ReturnValue<'_> { - fn eq(&self, other: &Self) -> bool { - use ReturnValue::*; - - match (self, other) { - (Immediate(val1), Immediate(val2)) => val1 == val2, - (ResultOf(frame1), ResultOf(frame2)) => GcCell::ptr_eq(*frame1, *frame2), - _ => false, - } - } -} - -impl fmt::Debug for ReturnValue<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use ReturnValue::*; - - match self { - Immediate(val) => write!(f, "Immediate({:?})", val), - ResultOf(_frame) => write!(f, "ResultOf()"), - } - } -} - -impl<'gc> ReturnValue<'gc> { - /// Mark a given return value as intended to be pushed onto the stack. - /// - /// The natural result of a stack frame retiring is to be pushed, so this - /// only ensures that Immediate values are pushed. - pub fn push(self, avm: &mut Avm1<'gc>) { - use ReturnValue::*; - - match self { - Immediate(val) => avm.push(val), - ResultOf(_frame) => {} - }; - } - - /// Force a return value to resolve on the Rust stack by recursing back - /// into the AVM. - pub fn resolve( - self, - avm: &mut Avm1<'gc>, - context: &mut UpdateContext<'_, 'gc, '_>, - ) -> Result, Error<'gc>> { - use ReturnValue::*; - - match self { - Immediate(val) => Ok(val), - ResultOf(frame) => match avm.run_current_frame(context, frame) { - Ok(_) => Ok(avm.pop()), - Err(e) => { - avm.retire_stack_frame(context, Value::Undefined); - Err(e) - } - }, - } - } - - pub fn is_immediate(&self) -> bool { - use ReturnValue::*; - - if let Immediate(_v) = self { - true - } else { - false - } - } - - /// Panic if a value is not immediate. - /// - /// This should only be used in test assertions. - #[cfg(test)] - pub fn unwrap_immediate(self) -> Value<'gc> { - use ReturnValue::*; - - match self { - Immediate(val) => val, - _ => panic!("Unwrapped a non-immediate return value"), - } - } -} - -impl<'gc> From> for ReturnValue<'gc> { - fn from(val: Value<'gc>) -> Self { - ReturnValue::Immediate(val) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(string: String) -> Self { - ReturnValue::Immediate(Value::String(string)) - } -} - -impl<'gc> From<&str> for ReturnValue<'gc> { - fn from(string: &str) -> Self { - ReturnValue::Immediate(Value::String(string.to_owned())) - } -} - -impl<'gc> From> for ReturnValue<'gc> { - fn from(string: Cow) -> Self { - ReturnValue::Immediate(Value::String(string.to_string())) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(value: bool) -> Self { - ReturnValue::Immediate(Value::Bool(value)) - } -} - -impl<'gc, T> From for ReturnValue<'gc> -where - Object<'gc>: From, -{ - fn from(value: T) -> Self { - ReturnValue::Immediate(Value::Object(Object::from(value))) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(value: f64) -> Self { - ReturnValue::Immediate(Value::Number(value)) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(value: f32) -> Self { - ReturnValue::Immediate(Value::Number(f64::from(value))) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(value: u8) -> Self { - ReturnValue::Immediate(Value::Number(f64::from(value))) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(value: i32) -> Self { - ReturnValue::Immediate(Value::Number(f64::from(value))) - } -} - -impl<'gc> From for ReturnValue<'gc> { - fn from(value: u32) -> Self { - ReturnValue::Immediate(Value::Number(f64::from(value))) - } -} - -impl<'gc> From>> for ReturnValue<'gc> { - fn from(frame: GcCell<'gc, Activation<'gc>>) -> Self { - ReturnValue::ResultOf(frame) - } -} diff --git a/core/src/avm1/scope.rs b/core/src/avm1/scope.rs index e21f11877..d9c47a426 100644 --- a/core/src/avm1/scope.rs +++ b/core/src/avm1/scope.rs @@ -1,8 +1,8 @@ //! Represents AVM1 scope chain resolution. +use crate::avm1::activation::Activation; use crate::avm1::error::Error; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, TObject, UpdateContext, Value}; +use crate::avm1::{Object, ScriptObject, TObject, UpdateContext, Value}; use enumset::EnumSet; use gc_arena::{GcCell, MutationContext}; use std::cell::Ref; @@ -207,6 +207,11 @@ impl<'gc> Scope<'gc> { &self.values } + /// Returns a reference to the current local scope object. + pub fn locals_cell(&self) -> Object<'gc> { + self.values + } + /// Returns a reference to the current local scope object for mutation. #[allow(dead_code)] pub fn locals_mut(&mut self) -> &mut Object<'gc> { @@ -234,34 +239,34 @@ impl<'gc> Scope<'gc> { pub fn resolve( &self, name: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, - ) -> Result, Error<'gc>> { - if self.locals().has_property(avm, context, name) { - return Ok(self.locals().get(name, avm, context)?.into()); + ) -> Result, Error<'gc>> { + if self.locals().has_property(activation, context, name) { + return Ok(self.locals().get(name, activation, context)?); } if let Some(scope) = self.parent() { - return scope.resolve(name, avm, context, this); + return scope.resolve(name, activation, context, this); } //TODO: Should undefined variables halt execution? - Ok(Value::Undefined.into()) + Ok(Value::Undefined) } /// Check if a particular property in the scope chain is defined. pub fn is_defined( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - if self.locals().has_property(avm, context, name) { + if self.locals().has_property(activation, context, name) { return true; } if let Some(scope) = self.parent() { - return scope.is_defined(avm, context, name); + return scope.is_defined(activation, context, name); } false @@ -277,26 +282,26 @@ impl<'gc> Scope<'gc> { &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, ) -> Result<(), Error<'gc>> { if self.class == ScopeClass::Target - || (self.locals().has_property(avm, context, name) - && self.locals().is_property_overwritable(avm, name)) + || (self.locals().has_property(activation, context, name) + && self.locals().is_property_overwritable(activation, name)) { // Value found on this object, so overwrite it. // Or we've hit the executing movie clip, so create it here. - self.locals().set(name, value, avm, context) + self.locals().set(name, value, activation, context) } else if let Some(scope) = self.parent() { // Traverse the scope chain in search of the value. - scope.set(name, value, avm, context, this) + scope.set(name, value, activation, context, this) } else { // This probably shouldn't happen -- all AVM1 code runs in reference to some movieclip, // so we should always have a movieclip scope. // Define on the top-level scope. debug_assert!(false, "Scope::set: No top-level movie clip scope"); - self.locals().set(name, value, avm, context) + self.locals().set(name, value, activation, context) } } @@ -314,17 +319,17 @@ impl<'gc> Scope<'gc> { /// Delete a value from scope pub fn delete( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, mc: MutationContext<'gc, '_>, ) -> bool { - if self.locals().has_property(avm, context, name) { - return self.locals().delete(avm, mc, name); + if self.locals().has_property(activation, context, name) { + return self.locals().delete(activation, mc, name); } if let Some(scope) = self.parent() { - return scope.delete(avm, context, name, mc); + return scope.delete(activation, context, name, mc); } false diff --git a/core/src/avm1/script_object.rs b/core/src/avm1/script_object.rs index 6e43e899b..710b34653 100644 --- a/core/src/avm1/script_object.rs +++ b/core/src/avm1/script_object.rs @@ -1,8 +1,8 @@ +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::{Executable, FunctionObject, NativeFunction}; use crate::avm1::property::{Attribute, Property}; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ObjectPtr, TObject, UpdateContext, Value}; +use crate::avm1::{Object, ObjectPtr, TObject, UpdateContext, Value}; use crate::property_map::{Entry, PropertyMap}; use core::fmt; use enumset::EnumSet; @@ -197,19 +197,20 @@ impl<'gc> ScriptObject<'gc> { &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, ) -> Result<(), Error<'gc>> { if name == "__proto__" { - self.0.write(context.gc_context).prototype = Some(value.coerce_to_object(avm, context)); + self.0.write(context.gc_context).prototype = + Some(value.coerce_to_object(activation, context)); } else if let Ok(index) = name.parse::() { self.set_array_element(index, value.to_owned(), context.gc_context); } else if !name.is_empty() { if name == "length" { let length = value - .coerce_to_f64(avm, context) + .coerce_to_f64(activation, context) .map(|v| v.abs() as i32) .unwrap_or(0); if length > 0 { @@ -226,14 +227,14 @@ impl<'gc> ScriptObject<'gc> { .0 .read() .values - .contains_key(name, avm.is_case_sensitive()); - let mut rval = None; + .contains_key(name, activation.is_case_sensitive()); + let mut worked = false; if is_vacant { let mut proto: Option> = Some((*self).into()); while let Some(this_proto) = proto { - if this_proto.has_own_virtual(avm, context, name) - && this_proto.is_property_overwritable(avm, name) + if this_proto.has_own_virtual(activation, context, name) + && this_proto.is_property_overwritable(activation, name) { break; } @@ -242,42 +243,45 @@ impl<'gc> ScriptObject<'gc> { } if let Some(this_proto) = proto { - rval = Some(this_proto.call_setter( - name, - value.clone(), - avm, - context, - (*self).into(), - )?); + if let Some(rval) = + this_proto.call_setter(name, value.clone(), activation, context) + { + let _ = rval.exec( + activation, + context, + this, + Some(this_proto), + &[value.clone()], + )?; + worked = true; + } } } - //No `rval` signals we didn't call a virtual setter above. Normally, + //This signals we didn't call a virtual setter above. Normally, //we'd resolve and return up there, but we have borrows that need //to end before we can do so. - if rval.is_none() { - rval = match self + if !worked { + let rval = match self .0 .write(context.gc_context) .values - .entry(name.to_owned(), avm.is_case_sensitive()) + .entry(name.to_owned(), activation.is_case_sensitive()) { - Entry::Occupied(mut entry) => { - Some(entry.get_mut().set(avm, context, this, base_proto, value)?) - } + Entry::Occupied(mut entry) => entry.get_mut().set(value.clone()), Entry::Vacant(entry) => { entry.insert(Property::Stored { - value, + value: value.clone(), attributes: Default::default(), }); None } }; - } - if let Some(rval) = rval { - rval.resolve(avm, context)?; + if let Some(rval) = rval { + let _ = rval.exec(activation, context, this, base_proto, &[value])?; + } } } @@ -297,7 +301,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn get_local( &self, name: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, ) -> Result, Error<'gc>> { @@ -305,13 +309,25 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { return Ok(self.proto().map_or(Value::Undefined, Value::Object)); } - if let Some(value) = self.0.read().values.get(name, avm.is_case_sensitive()) { - return value - .get(avm, context, this, Some((*self).into()))? - .resolve(avm, context); + let mut exec = None; + + if let Some(value) = self + .0 + .read() + .values + .get(name, activation.is_case_sensitive()) + { + match value { + Property::Virtual { get, .. } => exec = Some(get.to_owned()), + Property::Stored { value, .. } => return Ok(value.to_owned()), + } } - Ok(Value::Undefined) + if let Some(get) = exec { + get.exec(activation, context, this, Some((*self).into()), &[]) + } else { + Ok(Value::Undefined) + } } /// Set a named property on the object. @@ -323,13 +339,13 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error<'gc>> { self.internal_set( name, value, - avm, + activation, context, (*self).into(), Some((*self).into()), @@ -343,7 +359,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { /// overrides that may need to interact with the underlying object. fn call( &self, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _base_proto: Option>, @@ -356,27 +372,24 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, - ) -> Result, Error<'gc>> { + ) -> Option> { match self .0 .write(context.gc_context) .values - .get_mut(name, avm.is_case_sensitive()) + .get_mut(name, activation.is_case_sensitive()) { - Some(propref) if propref.is_virtual() => { - propref.set(avm, context, this, Some((*self).into()), value) - } - _ => Ok(Value::Undefined.into()), + Some(propref) if propref.is_virtual() => propref.set(value), + _ => None, } } #[allow(clippy::new_ret_no_self)] fn new( &self, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], @@ -396,14 +409,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { /// Returns false if the property cannot be deleted. fn delete( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, ) -> bool { let mut object = self.0.write(gc_context); - if let Some(prop) = object.values.get(name, avm.is_case_sensitive()) { + if let Some(prop) = object.values.get(name, activation.is_case_sensitive()) { if prop.can_delete() { - object.values.remove(name, avm.is_case_sensitive()); + object.values.remove(name, activation.is_case_sensitive()); return true; } } @@ -432,7 +445,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn add_property_with_case( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, get: Executable<'gc>, @@ -446,7 +459,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { set, attributes, }, - avm.is_case_sensitive(), + activation.is_case_sensitive(), ); } @@ -499,22 +512,22 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { /// Checks if the object has a given named property. fn has_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.has_own_property(avm, context, name) + self.has_own_property(activation, context, name) || self .proto() .as_ref() - .map_or(false, |p| p.has_property(avm, context, name)) + .map_or(false, |p| p.has_property(activation, context, name)) } /// Checks if the object has a given named property on itself (and not, /// say, the object's prototype or superclass) fn has_own_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { @@ -524,34 +537,44 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { self.0 .read() .values - .contains_key(name, avm.is_case_sensitive()) + .contains_key(name, activation.is_case_sensitive()) } fn has_own_virtual( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - if let Some(slot) = self.0.read().values.get(name, avm.is_case_sensitive()) { + if let Some(slot) = self + .0 + .read() + .values + .get(name, activation.is_case_sensitive()) + { slot.is_virtual() } else { false } } - fn is_property_overwritable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { + fn is_property_overwritable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { self.0 .read() .values - .get(name, avm.is_case_sensitive()) + .get(name, activation.is_case_sensitive()) .map(|p| p.is_overwritable()) .unwrap_or(false) } /// Checks if a named property appears when enumerating the object. - fn is_property_enumerable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - if let Some(prop) = self.0.read().values.get(name, avm.is_case_sensitive()) { + fn is_property_enumerable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + if let Some(prop) = self + .0 + .read() + .values + .get(name, activation.is_case_sensitive()) + { prop.is_enumerable() } else { false @@ -559,17 +582,19 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { } /// Enumerate the object. - fn get_keys(&self, avm: &mut Avm1<'gc>) -> Vec { - let proto_keys = self.proto().map_or_else(Vec::new, |p| p.get_keys(avm)); + fn get_keys(&self, activation: &mut Activation<'_, 'gc>) -> Vec { + let proto_keys = self + .proto() + .map_or_else(Vec::new, |p| p.get_keys(activation)); let mut out_keys = vec![]; let object = self.0.read(); // Prototype keys come first. - out_keys.extend( - proto_keys - .into_iter() - .filter(|k| !object.values.contains_key(k, avm.is_case_sensitive())), - ); + out_keys.extend(proto_keys.into_iter().filter(|k| { + !object + .values + .contains_key(k, activation.is_case_sensitive()) + })); // Then our own keys. out_keys.extend(self.0.read().values.iter().filter_map(move |(k, p)| { @@ -710,9 +735,9 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { mod tests { use super::*; - use crate::avm1::activation::Activation; use crate::avm1::globals::system::SystemProperties; use crate::avm1::property::Attribute::*; + use crate::avm1::Avm1; use crate::backend::audio::NullAudioBackend; use crate::backend::input::NullInputBackend; use crate::backend::navigator::NullNavigatorBackend; @@ -730,7 +755,11 @@ mod tests { fn with_object(swf_version: u8, test: F) -> R where - F: for<'a, 'gc> FnOnce(&mut Avm1<'gc>, &mut UpdateContext<'a, 'gc, '_>, Object<'gc>) -> R, + F: for<'a, 'gc> FnOnce( + &mut Activation<'_, 'gc>, + &mut UpdateContext<'a, 'gc, '_>, + Object<'gc>, + ) -> R, { rootless_arena(|gc_context| { let mut avm = Avm1::new(gc_context, swf_version); @@ -780,20 +809,23 @@ mod tests { let object = ScriptObject::object(gc_context, Some(avm.prototypes().object)).into(); let globals = avm.global_object_cell(); - avm.insert_stack_frame(GcCell::allocate( - gc_context, - Activation::from_nothing(swf_version, globals, gc_context, root), - )); + let mut activation = Activation::from_nothing( + &mut avm, + context.swf.version(), + globals, + context.gc_context, + *context.levels.get(&0).unwrap(), + ); - test(&mut avm, &mut context, object) + test(&mut activation, &mut context, object) }) } #[test] fn test_get_undefined() { - with_object(0, |avm, context, object| { + with_object(0, |activation, context, object| { assert_eq!( - object.get("not_defined", avm, context).unwrap(), + object.get("not_defined", activation, context).unwrap(), Value::Undefined ); }) @@ -801,7 +833,7 @@ mod tests { #[test] fn test_set_get() { - with_object(0, |avm, context, object| { + with_object(0, |activation, context, object| { object.as_script_object().unwrap().define_value( context.gc_context, "forced", @@ -809,12 +841,15 @@ mod tests { EnumSet::empty(), ); object - .set("natural", "natural".into(), avm, context) + .set("natural", "natural".into(), activation, context) .unwrap(); - assert_eq!(object.get("forced", avm, context).unwrap(), "forced".into()); assert_eq!( - object.get("natural", avm, context).unwrap(), + object.get("forced", activation, context).unwrap(), + "forced".into() + ); + assert_eq!( + object.get("natural", activation, context).unwrap(), "natural".into() ); }) @@ -822,7 +857,7 @@ mod tests { #[test] fn test_set_readonly() { - with_object(0, |avm, context, object| { + with_object(0, |activation, context, object| { object.as_script_object().unwrap().define_value( context.gc_context, "normal", @@ -837,18 +872,18 @@ mod tests { ); object - .set("normal", "replaced".into(), avm, context) + .set("normal", "replaced".into(), activation, context) .unwrap(); object - .set("readonly", "replaced".into(), avm, context) + .set("readonly", "replaced".into(), activation, context) .unwrap(); assert_eq!( - object.get("normal", avm, context).unwrap(), + object.get("normal", activation, context).unwrap(), "replaced".into() ); assert_eq!( - object.get("readonly", avm, context).unwrap(), + object.get("readonly", activation, context).unwrap(), "initial".into() ); }) @@ -856,7 +891,7 @@ mod tests { #[test] fn test_deletable_not_readonly() { - with_object(0, |avm, context, object| { + with_object(0, |activation, context, object| { object.as_script_object().unwrap().define_value( context.gc_context, "test", @@ -864,26 +899,30 @@ mod tests { DontDelete.into(), ); - assert_eq!(object.delete(avm, context.gc_context, "test"), false); - assert_eq!(object.get("test", avm, context).unwrap(), "initial".into()); + assert_eq!(object.delete(activation, context.gc_context, "test"), false); + assert_eq!( + object.get("test", activation, context).unwrap(), + "initial".into() + ); object .as_script_object() .unwrap() - .set("test", "replaced".into(), avm, context) + .set("test", "replaced".into(), activation, context) .unwrap(); - assert_eq!(object.delete(avm, context.gc_context, "test"), false); - assert_eq!(object.get("test", avm, context).unwrap(), "replaced".into()); + assert_eq!(object.delete(activation, context.gc_context, "test"), false); + assert_eq!( + object.get("test", activation, context).unwrap(), + "replaced".into() + ); }) } #[test] fn test_virtual_get() { - with_object(0, |avm, context, object| { - let getter = Executable::Native(|_avm, _context, _this, _args| { - Ok(ReturnValue::Immediate("Virtual!".into())) - }); + with_object(0, |activation, context, object| { + let getter = Executable::Native(|_avm, _context, _this, _args| Ok("Virtual!".into())); object.as_script_object().unwrap().add_property( context.gc_context, @@ -893,20 +932,26 @@ mod tests { EnumSet::empty(), ); - assert_eq!(object.get("test", avm, context).unwrap(), "Virtual!".into()); + assert_eq!( + object.get("test", activation, context).unwrap(), + "Virtual!".into() + ); // This set should do nothing - object.set("test", "Ignored!".into(), avm, context).unwrap(); - assert_eq!(object.get("test", avm, context).unwrap(), "Virtual!".into()); + object + .set("test", "Ignored!".into(), activation, context) + .unwrap(); + assert_eq!( + object.get("test", activation, context).unwrap(), + "Virtual!".into() + ); }) } #[test] fn test_delete() { - with_object(0, |avm, context, object| { - let getter = Executable::Native(|_avm, _context, _this, _args| { - Ok(ReturnValue::Immediate("Virtual!".into())) - }); + with_object(0, |activation, context, object| { + let getter = Executable::Native(|_avm, _context, _this, _args| Ok("Virtual!".into())); object.as_script_object().unwrap().add_property( context.gc_context, @@ -935,29 +980,41 @@ mod tests { DontDelete.into(), ); - assert_eq!(object.delete(avm, context.gc_context, "virtual"), true); - assert_eq!(object.delete(avm, context.gc_context, "virtual_un"), false); - assert_eq!(object.delete(avm, context.gc_context, "stored"), true); - assert_eq!(object.delete(avm, context.gc_context, "stored_un"), false); assert_eq!( - object.delete(avm, context.gc_context, "non_existent"), + object.delete(activation, context.gc_context, "virtual"), + true + ); + assert_eq!( + object.delete(activation, context.gc_context, "virtual_un"), + false + ); + assert_eq!( + object.delete(activation, context.gc_context, "stored"), + true + ); + assert_eq!( + object.delete(activation, context.gc_context, "stored_un"), + false + ); + assert_eq!( + object.delete(activation, context.gc_context, "non_existent"), false ); assert_eq!( - object.get("virtual", avm, context).unwrap(), + object.get("virtual", activation, context).unwrap(), Value::Undefined ); assert_eq!( - object.get("virtual_un", avm, context).unwrap(), + object.get("virtual_un", activation, context).unwrap(), "Virtual!".into() ); assert_eq!( - object.get("stored", avm, context).unwrap(), + object.get("stored", activation, context).unwrap(), Value::Undefined ); assert_eq!( - object.get("stored_un", avm, context).unwrap(), + object.get("stored_un", activation, context).unwrap(), "Stored!".into() ); }) @@ -965,10 +1022,8 @@ mod tests { #[test] fn test_iter_values() { - with_object(0, |avm, context, object| { - let getter = Executable::Native(|_avm, _context, _this, _args| { - Ok(ReturnValue::Immediate(Value::Null)) - }); + with_object(0, |activation, context, object| { + let getter = Executable::Native(|_avm, _context, _this, _args| Ok(Value::Null)); object.as_script_object().unwrap().define_value( context.gc_context, @@ -997,7 +1052,7 @@ mod tests { DontEnum.into(), ); - let keys: Vec<_> = object.get_keys(avm); + let keys: Vec<_> = object.get_keys(activation); assert_eq!(keys.len(), 2); assert_eq!(keys.contains(&"stored".to_string()), true); assert_eq!(keys.contains(&"stored_hidden".to_string()), false); diff --git a/core/src/avm1/shared_object.rs b/core/src/avm1/shared_object.rs index 7c83164be..25ccff2df 100644 --- a/core/src/avm1/shared_object.rs +++ b/core/src/avm1/shared_object.rs @@ -1,9 +1,9 @@ +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; use crate::avm1::sound_object::SoundObject; -use crate::avm1::{Avm1, Object, ObjectPtr, ScriptObject, TObject, Value}; +use crate::avm1::{Object, ObjectPtr, ScriptObject, TObject, Value}; use crate::context::UpdateContext; use crate::display_object::DisplayObject; use enumset::EnumSet; @@ -73,66 +73,67 @@ impl<'gc> TObject<'gc> for SharedObject<'gc> { fn get_local( &self, name: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, ) -> Result, Error<'gc>> { - self.base().get_local(name, avm, context, this) + self.base().get_local(name, activation, context, this) } fn set( &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error<'gc>> { - self.base().set(name, value, avm, context) + self.base().set(name, value, activation, context) } fn call( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - self.base().call(avm, context, this, base_proto, args) + self.base() + .call(activation, context, this, base_proto, args) } fn call_setter( &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, - ) -> Result, Error<'gc>> { - self.base().call_setter(name, value, avm, context, this) + ) -> Option> { + self.base().call_setter(name, value, activation, context) } #[allow(clippy::new_ret_no_self)] fn new( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - Ok( - SharedObject::empty_shared_obj(context.gc_context, Some(avm.prototypes.shared_object)) - .into(), + Ok(SharedObject::empty_shared_obj( + context.gc_context, + Some(activation.avm().prototypes.shared_object), ) + .into()) } fn delete( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, ) -> bool { - self.base().delete(avm, gc_context, name) + self.base().delete(activation, gc_context, name) } fn proto(&self) -> Option> { @@ -179,7 +180,7 @@ impl<'gc> TObject<'gc> for SharedObject<'gc> { fn add_property_with_case( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, get: Executable<'gc>, @@ -187,46 +188,46 @@ impl<'gc> TObject<'gc> for SharedObject<'gc> { attributes: EnumSet, ) { self.base() - .add_property_with_case(avm, gc_context, name, get, set, attributes) + .add_property_with_case(activation, gc_context, name, get, set, attributes) } fn has_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base().has_property(avm, context, name) + self.base().has_property(activation, context, name) } fn has_own_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base().has_own_property(avm, context, name) + self.base().has_own_property(activation, context, name) } fn has_own_virtual( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base().has_own_virtual(avm, context, name) + self.base().has_own_virtual(activation, context, name) } - fn is_property_overwritable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.base().is_property_overwritable(avm, name) + fn is_property_overwritable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.base().is_property_overwritable(activation, name) } - fn is_property_enumerable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.base().is_property_enumerable(avm, name) + fn is_property_enumerable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.base().is_property_enumerable(activation, name) } - fn get_keys(&self, avm: &mut Avm1<'gc>) -> Vec { - self.base().get_keys(avm) + fn get_keys(&self, activation: &mut Activation<'_, 'gc>) -> Vec { + self.base().get_keys(activation) } fn as_string(&self) -> Cow { diff --git a/core/src/avm1/sound_object.rs b/core/src/avm1/sound_object.rs index 3eca6ff61..ab5ce7083 100644 --- a/core/src/avm1/sound_object.rs +++ b/core/src/avm1/sound_object.rs @@ -1,10 +1,10 @@ //! AVM1 object type to represent Sound objects. +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ObjectPtr, ScriptObject, TObject, Value}; +use crate::avm1::{Object, ObjectPtr, ScriptObject, TObject, Value}; use crate::backend::audio::{SoundHandle, SoundInstanceHandle}; use crate::context::UpdateContext; use crate::display_object::DisplayObject; @@ -134,63 +134,66 @@ impl<'gc> TObject<'gc> for SoundObject<'gc> { fn get_local( &self, name: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, ) -> Result, Error<'gc>> { - self.base().get_local(name, avm, context, this) + self.base().get_local(name, activation, context, this) } fn set( &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error<'gc>> { - self.base().set(name, value, avm, context) + self.base().set(name, value, activation, context) } fn call( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - self.base().call(avm, context, this, base_proto, args) + self.base() + .call(activation, context, this, base_proto, args) } fn call_setter( &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, - ) -> Result, Error<'gc>> { - self.base().call_setter(name, value, avm, context, this) + ) -> Option> { + self.base().call_setter(name, value, activation, context) } #[allow(clippy::new_ret_no_self)] fn new( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - Ok(SoundObject::empty_sound(context.gc_context, Some(avm.prototypes.sound)).into()) + Ok( + SoundObject::empty_sound(context.gc_context, Some(activation.avm().prototypes.sound)) + .into(), + ) } fn delete( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, ) -> bool { - self.base().delete(avm, gc_context, name) + self.base().delete(activation, gc_context, name) } fn proto(&self) -> Option> { @@ -237,7 +240,7 @@ impl<'gc> TObject<'gc> for SoundObject<'gc> { fn add_property_with_case( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, get: Executable<'gc>, @@ -245,46 +248,46 @@ impl<'gc> TObject<'gc> for SoundObject<'gc> { attributes: EnumSet, ) { self.base() - .add_property_with_case(avm, gc_context, name, get, set, attributes) + .add_property_with_case(activation, gc_context, name, get, set, attributes) } fn has_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base().has_property(avm, context, name) + self.base().has_property(activation, context, name) } fn has_own_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base().has_own_property(avm, context, name) + self.base().has_own_property(activation, context, name) } fn has_own_virtual( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base().has_own_virtual(avm, context, name) + self.base().has_own_virtual(activation, context, name) } - fn is_property_overwritable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.base().is_property_overwritable(avm, name) + fn is_property_overwritable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.base().is_property_overwritable(activation, name) } - fn is_property_enumerable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.base().is_property_enumerable(avm, name) + fn is_property_enumerable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.base().is_property_enumerable(activation, name) } - fn get_keys(&self, avm: &mut Avm1<'gc>) -> Vec { - self.base().get_keys(avm) + fn get_keys(&self, activation: &mut Activation<'_, 'gc>) -> Vec { + self.base().get_keys(activation) } fn as_string(&self) -> Cow { diff --git a/core/src/avm1/stage_object.rs b/core/src/avm1/stage_object.rs index 20bef2688..ceaef458a 100644 --- a/core/src/avm1/stage_object.rs +++ b/core/src/avm1/stage_object.rs @@ -1,11 +1,11 @@ //! AVM1 object type to represent objects on the stage. +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::object::search_prototype; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ObjectPtr, ScriptObject, TDisplayObject, TObject, Value}; +use crate::avm1::{Object, ObjectPtr, ScriptObject, TDisplayObject, TObject, Value}; use crate::context::UpdateContext; use crate::display_object::{DisplayObject, EditText, MovieClip}; use crate::property_map::PropertyMap; @@ -127,19 +127,19 @@ impl<'gc> TObject<'gc> for StageObject<'gc> { fn get( &self, name: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error<'gc>> { let obj = self.0.read(); - let props = avm.display_properties; - let case_sensitive = avm.is_case_sensitive(); + let props = activation.avm().display_properties; + let case_sensitive = activation.is_case_sensitive(); // Property search order for DisplayObjects: - if self.has_own_property(avm, context, name) { + if self.has_own_property(activation, context, name) { // 1) Actual properties on the underlying object - self.get_local(name, avm, context, (*self).into()) + self.get_local(name, activation, context, (*self).into()) } else if let Some(property) = props.read().get_by_name(&name) { // 2) Display object properties such as _x, _y - let val = property.get(avm, context, obj.display_object)?; + let val = property.get(activation, context, obj.display_object)?; Ok(val) } else if let Some(child) = obj.display_object.get_child_by_name(name, case_sensitive) { // 3) Child display objects with the given instance name @@ -152,9 +152,7 @@ impl<'gc> TObject<'gc> for StageObject<'gc> { Ok(level.object()) } else { // 5) Prototype - search_prototype(self.proto(), name, avm, context, (*self).into())? - .0 - .resolve(avm, context) + Ok(search_prototype(self.proto(), name, activation, context, (*self).into())?.0) } // 6) TODO: __resolve? } @@ -162,22 +160,25 @@ impl<'gc> TObject<'gc> for StageObject<'gc> { fn get_local( &self, name: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, ) -> Result, Error<'gc>> { - self.0.read().base.get_local(name, avm, context, this) + self.0 + .read() + .base + .get_local(name, activation, context, this) } fn set( &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error<'gc>> { let obj = self.0.read(); - let props = avm.display_properties; + let props = activation.avm().display_properties; // Check if a text field is bound to this property and update the text if so. for binding in obj @@ -185,31 +186,32 @@ impl<'gc> TObject<'gc> for StageObject<'gc> { .iter() .filter(|binding| binding.variable_name == name) { - let _ = binding - .text_field - .set_html_text(value.coerce_to_string(avm, context)?.into_owned(), context); + let _ = binding.text_field.set_html_text( + value.coerce_to_string(activation, context)?.into_owned(), + context, + ); } - if obj.base.has_own_property(avm, context, name) { + if obj.base.has_own_property(activation, context, name) { // 1) Actual proeprties on the underlying object obj.base.internal_set( name, value, - avm, + activation, context, (*self).into(), Some((*self).into()), ) } else if let Some(property) = props.read().get_by_name(&name) { // 2) Display object properties such as _x, _y - property.set(avm, context, obj.display_object, value)?; + property.set(activation, context, obj.display_object, value)?; Ok(()) } else { // 3) TODO: Prototype obj.base.internal_set( name, value, - avm, + activation, context, (*self).into(), Some((*self).into()), @@ -219,7 +221,7 @@ impl<'gc> TObject<'gc> for StageObject<'gc> { fn call( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, @@ -228,42 +230,41 @@ impl<'gc> TObject<'gc> for StageObject<'gc> { self.0 .read() .base - .call(avm, context, this, base_proto, args) + .call(activation, context, this, base_proto, args) } fn call_setter( &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, - ) -> Result, Error<'gc>> { + ) -> Option> { self.0 .read() .base - .call_setter(name, value, avm, context, this) + .call_setter(name, value, activation, context) } #[allow(clippy::new_ret_no_self)] fn new( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { //TODO: Create a StageObject of some kind - self.0.read().base.new(avm, context, this, args) + self.0.read().base.new(activation, context, this, args) } fn delete( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, ) -> bool { - self.0.read().base.delete(avm, gc_context, name) + self.0.read().base.delete(activation, gc_context, name) } fn proto(&self) -> Option> { @@ -318,7 +319,7 @@ impl<'gc> TObject<'gc> for StageObject<'gc> { fn add_property_with_case( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, get: Executable<'gc>, @@ -328,21 +329,21 @@ impl<'gc> TObject<'gc> for StageObject<'gc> { self.0 .read() .base - .add_property_with_case(avm, gc_context, name, get, set, attributes) + .add_property_with_case(activation, gc_context, name, get, set, attributes) } fn has_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { let obj = self.0.read(); - if obj.base.has_property(avm, context, name) { + if obj.base.has_property(activation, context, name) { return true; } - let case_sensitive = avm.is_case_sensitive(); + let case_sensitive = activation.is_case_sensitive(); if obj .display_object .get_child_by_name(name, case_sensitive) @@ -364,36 +365,45 @@ impl<'gc> TObject<'gc> for StageObject<'gc> { fn has_own_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { // Note that `hasOwnProperty` does NOT return true for child display objects. - self.0.read().base.has_own_property(avm, context, name) + self.0 + .read() + .base + .has_own_property(activation, context, name) } fn has_own_virtual( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.0.read().base.has_own_virtual(avm, context, name) + self.0 + .read() + .base + .has_own_virtual(activation, context, name) } - fn is_property_enumerable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.0.read().base.is_property_enumerable(avm, name) + fn is_property_enumerable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.0.read().base.is_property_enumerable(activation, name) } - fn is_property_overwritable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.0.read().base.is_property_overwritable(avm, name) + fn is_property_overwritable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.0 + .read() + .base + .is_property_overwritable(activation, name) } - fn get_keys(&self, avm: &mut Avm1<'gc>) -> Vec { + fn get_keys(&self, activation: &mut Activation<'_, 'gc>) -> Vec { // Keys from the underlying object are listed first, followed by // child display objects in order from highest depth to lowest depth. let obj = self.0.read(); - let mut keys = obj.base.get_keys(avm); + let mut keys = obj.base.get_keys(activation); keys.extend( obj.display_object .children() @@ -486,13 +496,13 @@ pub struct DisplayProperty<'gc> { } pub type DisplayGetter<'gc> = fn( - &mut Avm1<'gc>, + &mut Activation<'_, 'gc>, &mut UpdateContext<'_, 'gc, '_>, DisplayObject<'gc>, ) -> Result, Error<'gc>>; pub type DisplaySetter<'gc> = fn( - &mut Avm1<'gc>, + &mut Activation<'_, 'gc>, &mut UpdateContext<'_, 'gc, '_>, DisplayObject<'gc>, Value<'gc>, @@ -501,22 +511,22 @@ pub type DisplaySetter<'gc> = fn( impl<'gc> DisplayProperty<'gc> { pub fn get( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { - (self.get)(avm, context, this) + (self.get)(activation, context, this) } pub fn set( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, value: Value<'gc>, ) -> Result<(), Error<'gc>> { self.set - .map(|f| f(avm, context, this, value)) + .map(|f| f(activation, context, this, value)) .unwrap_or(Ok(())) } } @@ -593,7 +603,7 @@ impl<'gc> DisplayPropertyMap<'gc> { } fn x<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -601,19 +611,19 @@ fn x<'gc>( } fn set_x<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, mut this: DisplayObject<'gc>, val: Value<'gc>, ) -> Result<(), Error<'gc>> { - if let Some(val) = property_coerce_to_number(avm, context, val)? { + if let Some(val) = property_coerce_to_number(activation, context, val)? { this.set_x(context.gc_context, val); } Ok(()) } fn y<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -621,19 +631,19 @@ fn y<'gc>( } fn set_y<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, mut this: DisplayObject<'gc>, val: Value<'gc>, ) -> Result<(), Error<'gc>> { - if let Some(val) = property_coerce_to_number(avm, context, val)? { + if let Some(val) = property_coerce_to_number(activation, context, val)? { this.set_y(context.gc_context, val); } Ok(()) } fn x_scale<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, mut this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -642,19 +652,19 @@ fn x_scale<'gc>( } fn set_x_scale<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, mut this: DisplayObject<'gc>, val: Value<'gc>, ) -> Result<(), Error<'gc>> { - if let Some(val) = property_coerce_to_number(avm, context, val)? { + if let Some(val) = property_coerce_to_number(activation, context, val)? { this.set_scale_x(context.gc_context, val / 100.0); } Ok(()) } fn y_scale<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, mut this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -663,19 +673,19 @@ fn y_scale<'gc>( } fn set_y_scale<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, mut this: DisplayObject<'gc>, val: Value<'gc>, ) -> Result<(), Error<'gc>> { - if let Some(val) = property_coerce_to_number(avm, context, val)? { + if let Some(val) = property_coerce_to_number(activation, context, val)? { this.set_scale_y(context.gc_context, val / 100.0); } Ok(()) } fn current_frame<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -687,7 +697,7 @@ fn current_frame<'gc>( } fn total_frames<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -699,7 +709,7 @@ fn total_frames<'gc>( } fn alpha<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -708,19 +718,19 @@ fn alpha<'gc>( } fn set_alpha<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, val: Value<'gc>, ) -> Result<(), Error<'gc>> { - if let Some(val) = property_coerce_to_number(avm, context, val)? { + if let Some(val) = property_coerce_to_number(activation, context, val)? { this.set_alpha(context.gc_context, val / 100.0); } Ok(()) } fn visible<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -729,21 +739,21 @@ fn visible<'gc>( } fn set_visible<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, mut this: DisplayObject<'gc>, val: Value<'gc>, ) -> Result<(), Error<'gc>> { // Because this property dates to the era of Flash 4, this is actually coerced to an integer. // `_visible = "false";` coerces to NaN and has no effect. - if let Some(n) = property_coerce_to_number(avm, context, val)? { + if let Some(n) = property_coerce_to_number(activation, context, val)? { this.set_visible(context.gc_context, n != 0.0); } Ok(()) } fn width<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -751,19 +761,19 @@ fn width<'gc>( } fn set_width<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, mut this: DisplayObject<'gc>, val: Value<'gc>, ) -> Result<(), Error<'gc>> { - if let Some(val) = property_coerce_to_number(avm, context, val)? { + if let Some(val) = property_coerce_to_number(activation, context, val)? { this.set_width(context.gc_context, val); } Ok(()) } fn height<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -771,19 +781,19 @@ fn height<'gc>( } fn set_height<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, mut this: DisplayObject<'gc>, val: Value<'gc>, ) -> Result<(), Error<'gc>> { - if let Some(val) = property_coerce_to_number(avm, context, val)? { + if let Some(val) = property_coerce_to_number(activation, context, val)? { this.set_height(context.gc_context, val); } Ok(()) } fn rotation<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, mut this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -791,12 +801,12 @@ fn rotation<'gc>( } fn set_rotation<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, mut this: DisplayObject<'gc>, degrees: Value<'gc>, ) -> Result<(), Error<'gc>> { - if let Some(mut degrees) = property_coerce_to_number(avm, context, degrees)? { + if let Some(mut degrees) = property_coerce_to_number(activation, context, degrees)? { // Normalize into the range of [-180, 180]. degrees %= 360.0; if degrees < -180.0 { @@ -810,7 +820,7 @@ fn set_rotation<'gc>( } fn target<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -818,7 +828,7 @@ fn target<'gc>( } fn frames_loaded<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -830,7 +840,7 @@ fn frames_loaded<'gc>( } fn name<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -838,18 +848,18 @@ fn name<'gc>( } fn set_name<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, mut this: DisplayObject<'gc>, val: Value<'gc>, ) -> Result<(), Error<'gc>> { - let name = val.coerce_to_string(avm, context)?; + let name = val.coerce_to_string(activation, context)?; this.set_name(context.gc_context, &name); Ok(()) } fn drop_target<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -858,7 +868,7 @@ fn drop_target<'gc>( } fn url<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -867,7 +877,7 @@ fn url<'gc>( } fn high_quality<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -876,7 +886,7 @@ fn high_quality<'gc>( } fn set_high_quality<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: DisplayObject<'gc>, _val: Value<'gc>, @@ -886,7 +896,7 @@ fn set_high_quality<'gc>( } fn focus_rect<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -895,7 +905,7 @@ fn focus_rect<'gc>( } fn set_focus_rect<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: DisplayObject<'gc>, _val: Value<'gc>, @@ -905,7 +915,7 @@ fn set_focus_rect<'gc>( } fn sound_buf_time<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -914,7 +924,7 @@ fn sound_buf_time<'gc>( } fn set_sound_buf_time<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: DisplayObject<'gc>, _val: Value<'gc>, @@ -924,7 +934,7 @@ fn set_sound_buf_time<'gc>( } fn quality<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -933,7 +943,7 @@ fn quality<'gc>( } fn set_quality<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: DisplayObject<'gc>, _val: Value<'gc>, @@ -943,7 +953,7 @@ fn set_quality<'gc>( } fn x_mouse<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -952,7 +962,7 @@ fn x_mouse<'gc>( } fn y_mouse<'gc>( - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { @@ -961,12 +971,12 @@ fn y_mouse<'gc>( } fn property_coerce_to_number<'gc>( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, value: Value<'gc>, ) -> Result, Error<'gc>> { if value != Value::Undefined && value != Value::Null { - let n = value.coerce_to_f64(avm, context)?; + let n = value.coerce_to_f64(activation, context)?; if n.is_finite() { return Ok(Some(n)); } diff --git a/core/src/avm1/super_object.rs b/core/src/avm1/super_object.rs index d111b0794..e839a547a 100644 --- a/core/src/avm1/super_object.rs +++ b/core/src/avm1/super_object.rs @@ -1,12 +1,12 @@ //! Special object that implements `super` +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::object::search_prototype; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; use crate::avm1::script_object::TYPE_OF_OBJECT; -use crate::avm1::{Avm1, Object, ObjectPtr, ScriptObject, TObject, Value}; +use crate::avm1::{Object, ObjectPtr, ScriptObject, TObject, Value}; use crate::context::UpdateContext; use crate::display_object::DisplayObject; use enumset::EnumSet; @@ -45,7 +45,7 @@ impl<'gc> SuperObject<'gc> { pub fn from_this_and_base_proto( this: Object<'gc>, base_proto: Object<'gc>, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result> { Ok(Self(GcCell::allocate( @@ -65,14 +65,14 @@ impl<'gc> SuperObject<'gc> { /// Retrieve the constructor associated with the super proto. fn super_constr( self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result>, Error<'gc>> { if let Some(super_proto) = self.super_proto() { Ok(Some( super_proto - .get("__constructor__", avm, context)? - .coerce_to_object(avm, context), + .get("__constructor__", activation, context)? + .coerce_to_object(activation, context), )) } else { Ok(None) @@ -84,7 +84,7 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> { fn get_local( &self, _name: &str, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, ) -> Result, Error<'gc>> { @@ -95,7 +95,7 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> { &self, _name: &str, _value: Value<'gc>, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error<'gc>> { //TODO: What happens if you set `super.__proto__`? @@ -104,14 +104,20 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> { fn call( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _base_proto: Option>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(constr) = self.super_constr(avm, context)? { - constr.call(avm, context, self.0.read().child, self.super_proto(), args) + if let Some(constr) = self.super_constr(activation, context)? { + constr.call( + activation, + context, + self.0.read().child, + self.super_proto(), + args, + ) } else { Ok(Value::Undefined) } @@ -121,56 +127,55 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> { &self, name: &str, args: &[Value<'gc>], - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error<'gc>> { let child = self.0.read().child; let super_proto = self.super_proto(); - let (method, base_proto) = search_prototype(super_proto, name, avm, context, child)?; - let method = method.resolve(avm, context)?; + let (method, base_proto) = search_prototype(super_proto, name, activation, context, child)?; + let method = method; if let Value::Object(_) = method { } else { log::warn!("Super method {} is not callable", name); } - method.call(avm, context, child, base_proto, args) + method.call(activation, context, child, base_proto, args) } fn call_setter( &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, - ) -> Result, Error<'gc>> { + ) -> Option> { self.0 .read() .child - .call_setter(name, value, avm, context, this) + .call_setter(name, value, activation, context) } #[allow(clippy::new_ret_no_self)] fn new( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(proto) = self.proto() { - proto.new(avm, context, this, args) + proto.new(activation, context, this, args) } else { // TODO: What happens when you `new super` but there's no // super? Is this code even reachable?! - self.0.read().child.new(avm, context, this, args) + self.0.read().child.new(activation, context, this, args) } } fn delete( &self, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _gc_context: MutationContext<'gc, '_>, _name: &str, ) -> bool { @@ -221,7 +226,7 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> { fn add_property_with_case( &self, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _gc_context: MutationContext<'gc, '_>, _name: &str, _get: Executable<'gc>, @@ -233,40 +238,49 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> { fn has_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.0.read().child.has_property(avm, context, name) + self.0.read().child.has_property(activation, context, name) } fn has_own_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.0.read().child.has_own_property(avm, context, name) + self.0 + .read() + .child + .has_own_property(activation, context, name) } fn has_own_virtual( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.0.read().child.has_own_virtual(avm, context, name) + self.0 + .read() + .child + .has_own_virtual(activation, context, name) } - fn is_property_enumerable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.0.read().child.is_property_enumerable(avm, name) + fn is_property_enumerable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.0.read().child.is_property_enumerable(activation, name) } - fn is_property_overwritable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.0.read().child.is_property_overwritable(avm, name) + fn is_property_overwritable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.0 + .read() + .child + .is_property_overwritable(activation, name) } - fn get_keys(&self, _avm: &mut Avm1<'gc>) -> Vec { + fn get_keys(&self, _activation: &mut Activation<'_, 'gc>) -> Vec { vec![] } diff --git a/core/src/avm1/test_utils.rs b/core/src/avm1/test_utils.rs index 1efd36121..2769db304 100644 --- a/core/src/avm1/test_utils.rs +++ b/core/src/avm1/test_utils.rs @@ -13,7 +13,7 @@ use crate::library::Library; use crate::loader::LoadManager; use crate::prelude::*; use crate::tag_utils::{SwfMovie, SwfSlice}; -use gc_arena::{rootless_arena, GcCell, MutationContext}; +use gc_arena::{rootless_arena, MutationContext}; use rand::{rngs::SmallRng, SeedableRng}; use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; @@ -21,22 +21,22 @@ use std::sync::Arc; pub fn with_avm(swf_version: u8, test: F) where F: for<'a, 'gc> FnOnce( - &mut Avm1<'gc>, + &mut Activation<'_, 'gc>, &mut UpdateContext<'a, 'gc, '_>, Object<'gc>, ) -> Result<(), Error<'gc>>, { - fn in_the_arena<'gc, F>(swf_version: u8, test: F, gc_context: MutationContext<'gc, '_>) + fn in_the_arena<'a, 'gc: 'a, F>(swf_version: u8, test: F, gc_context: MutationContext<'gc, '_>) where - F: for<'a> FnOnce( - &mut Avm1<'gc>, - &mut UpdateContext<'a, 'gc, '_>, + F: FnOnce( + &mut Activation<'_, 'gc>, + &mut UpdateContext<'_, 'gc, '_>, Object<'gc>, ) -> Result<(), Error<'gc>>, { let mut avm = Avm1::new(gc_context, swf_version); let swf = Arc::new(SwfMovie::empty(swf_version)); - let mut root: DisplayObject<'_> = + let mut root: DisplayObject<'gc> = MovieClip::new(SwfSlice::empty(swf.clone()), gc_context).into(); root.set_depth(gc_context, 0); let mut levels = BTreeMap::new(); @@ -77,17 +77,35 @@ where root.post_instantiation(&mut avm, &mut context, root, None, false); root.set_name(context.gc_context, ""); - let globals = avm.global_object_cell(); - avm.insert_stack_frame(GcCell::allocate( - gc_context, - Activation::from_nothing(swf_version, globals, gc_context, root), - )); - - let this = root.object().coerce_to_object(&mut avm, &mut context); - - if let Err(e) = test(&mut avm, &mut context, this) { - panic!("Encountered exception during test: {}", e); + fn run_test<'a, 'gc: 'a, F>( + activation: &mut Activation<'_, 'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + root: DisplayObject<'gc>, + test: F, + ) where + F: FnOnce( + &mut Activation<'_, 'gc>, + &mut UpdateContext<'_, 'gc, '_>, + Object<'gc>, + ) -> Result<(), Error<'gc>>, + { + let this = root.object().coerce_to_object(activation, context); + let result = test(activation, context, this); + if let Err(e) = result { + panic!("Encountered exception during test: {}", e); + } } + + let globals = avm.global_object_cell(); + let mut activation = Activation::from_nothing( + &mut avm, + context.swf.version(), + globals, + context.gc_context, + *context.levels.get(&0).unwrap(), + ); + + run_test(&mut activation, &mut context, root, test) } rootless_arena(|gc_context| in_the_arena(swf_version, test, gc_context)) @@ -100,9 +118,9 @@ macro_rules! test_method { use $crate::avm1::test_utils::*; $( for version in &$versions { - with_avm(*version, |avm, context, _root| -> Result<(), Error> { - let object = $object(avm, context); - let function = object.get($name, avm, context)?; + with_avm(*version, |activation, context, _root| -> Result<(), Error> { + let object = $object(activation, context); + let function = object.get($name, activation, context)?; $( #[allow(unused_mut)] @@ -110,7 +128,7 @@ macro_rules! test_method { $( args.push($arg.into()); )* - assert_eq!(function.call(avm, context, object, None, &args)?, $out.into(), "{:?} => {:?} in swf {}", args, $out, version); + assert_eq!(function.call(activation, context, object, None, &args)?, $out.into(), "{:?} => {:?} in swf {}", args, $out, version); )* Ok(()) diff --git a/core/src/avm1/tests.rs b/core/src/avm1/tests.rs index 048cd5b7c..8c4f2ecea 100644 --- a/core/src/avm1/tests.rs +++ b/core/src/avm1/tests.rs @@ -1,28 +1,18 @@ -use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::test_utils::with_avm; use crate::avm1::TObject; -use gc_arena::GcCell; #[test] fn locals_into_form_values() { - with_avm(19, |avm, context, _this| -> Result<(), Error> { - let my_activation = Activation::from_nothing( - 19, - avm.global_object_cell(), - context.gc_context, - *context.levels.get(&0).expect("_level0 in test"), - ); - let my_locals = my_activation.scope().locals().to_owned(); - + with_avm(19, |activation, context, _this| -> Result<(), Error> { + let my_locals = activation.scope().locals().to_owned(); my_locals - .set("value1", "string".into(), avm, context) + .set("value1", "string".into(), activation, context) .unwrap(); - my_locals.set("value2", 2.0.into(), avm, context).unwrap(); - - avm.insert_stack_frame(GcCell::allocate(context.gc_context, my_activation)); - - let my_local_values = avm.locals_into_form_values(context); + my_locals + .set("value2", 2.0.into(), activation, context) + .unwrap(); + let my_local_values = activation.locals_into_form_values(context); assert_eq!(my_local_values.len(), 2); assert_eq!(my_local_values.get("value1"), Some(&"string".to_string())); diff --git a/core/src/avm1/value.rs b/core/src/avm1/value.rs index 6c7066c5c..6b24fe99a 100644 --- a/core/src/avm1/value.rs +++ b/core/src/avm1/value.rs @@ -1,6 +1,7 @@ +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::value_object::ValueObject; -use crate::avm1::{Avm1, Object, TObject, UpdateContext}; +use crate::avm1::{Object, TObject, UpdateContext}; use std::borrow::Cow; use std::f64::NAN; @@ -157,19 +158,19 @@ impl<'gc> Value<'gc> { /// * In SWF5 and lower, hexadecimal is unsupported. fn primitive_as_number( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, ) -> f64 { match self { - Value::Undefined if avm.current_swf_version() < 7 => 0.0, - Value::Null if avm.current_swf_version() < 7 => 0.0, + Value::Undefined if activation.current_swf_version() < 7 => 0.0, + Value::Null if activation.current_swf_version() < 7 => 0.0, Value::Undefined => NAN, Value::Null => NAN, Value::Bool(false) => 0.0, Value::Bool(true) => 1.0, Value::Number(v) => *v, Value::String(v) => match v.as_str() { - v if avm.current_swf_version() >= 6 && v.starts_with("0x") => { + v if activation.current_swf_version() >= 6 && v.starts_with("0x") => { let mut n: u32 = 0; for c in v[2..].bytes() { n = n.wrapping_shl(4); @@ -195,7 +196,7 @@ impl<'gc> Value<'gc> { } f64::from(n as i32) } - v if avm.current_swf_version() >= 6 + v if activation.current_swf_version() >= 6 && (v.starts_with('0') || v.starts_with("+0") || v.starts_with("-0")) && v[1..].bytes().all(|c| c >= b'0' && c <= b'7') => { @@ -223,14 +224,14 @@ impl<'gc> Value<'gc> { /// ECMA-262 2nd edition s. 9.3 ToNumber pub fn coerce_to_f64( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result> { Ok(match self { Value::Object(_) => self - .to_primitive_num(avm, context)? - .primitive_as_number(avm, context), - val => val.primitive_as_number(avm, context), + .to_primitive_num(activation, context)? + .primitive_as_number(activation, context), + val => val.primitive_as_number(activation, context), }) } @@ -247,11 +248,11 @@ impl<'gc> Value<'gc> { /// return `undefined` rather than yielding a runtime error. pub fn to_primitive_num( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error<'gc>> { Ok(match self { - Value::Object(object) => object.call_method("valueOf", &[], avm, context)?, + Value::Object(object) => object.call_method("valueOf", &[], activation, context)?, val => val.to_owned(), }) } @@ -261,18 +262,18 @@ impl<'gc> Value<'gc> { pub fn abstract_lt( &self, other: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error<'gc>> { - let prim_self = self.to_primitive_num(avm, context)?; - let prim_other = other.to_primitive_num(avm, context)?; + let prim_self = self.to_primitive_num(activation, context)?; + let prim_other = other.to_primitive_num(activation, context)?; if let (Value::String(a), Value::String(b)) = (&prim_self, &prim_other) { return Ok(a.to_string().bytes().lt(b.to_string().bytes()).into()); } - let num_self = prim_self.primitive_as_number(avm, context); - let num_other = prim_other.primitive_as_number(avm, context); + let num_self = prim_self.primitive_as_number(activation, context); + let num_other = prim_other.primitive_as_number(activation, context); if num_self.is_nan() || num_other.is_nan() { return Ok(Value::Undefined); @@ -301,7 +302,7 @@ impl<'gc> Value<'gc> { pub fn abstract_eq( &self, other: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, coerced: bool, ) -> Result, Error<'gc>> { @@ -327,62 +328,62 @@ impl<'gc> Value<'gc> { (Value::Bool(a), Value::Bool(b)) => Ok((a == b).into()), (Value::Object(a), Value::Object(b)) => Ok(Object::ptr_eq(*a, *b).into()), (Value::Object(a), Value::Null) | (Value::Object(a), Value::Undefined) => { - Ok(Object::ptr_eq(*a, avm.global_object_cell()).into()) + Ok(Object::ptr_eq(*a, activation.avm().global_object_cell()).into()) } (Value::Null, Value::Object(b)) | (Value::Undefined, Value::Object(b)) => { - Ok(Object::ptr_eq(*b, avm.global_object_cell()).into()) + Ok(Object::ptr_eq(*b, activation.avm().global_object_cell()).into()) } (Value::Undefined, Value::Null) => Ok(true.into()), (Value::Null, Value::Undefined) => Ok(true.into()), (Value::Number(_), Value::String(_)) => Ok(self.abstract_eq( - Value::Number(other.coerce_to_f64(avm, context)?), - avm, + Value::Number(other.coerce_to_f64(activation, context)?), + activation, context, true, )?), (Value::String(_), Value::Number(_)) => { - Ok(Value::Number(self.coerce_to_f64(avm, context)?) - .abstract_eq(other, avm, context, true)?) + Ok(Value::Number(self.coerce_to_f64(activation, context)?) + .abstract_eq(other, activation, context, true)?) } - (Value::Bool(_), _) => Ok(Value::Number(self.coerce_to_f64(avm, context)?) - .abstract_eq(other, avm, context, true)?), + (Value::Bool(_), _) => Ok(Value::Number(self.coerce_to_f64(activation, context)?) + .abstract_eq(other, activation, context, true)?), (_, Value::Bool(_)) => Ok(self.abstract_eq( - Value::Number(other.coerce_to_f64(avm, context)?), - avm, + Value::Number(other.coerce_to_f64(activation, context)?), + activation, context, true, )?), (Value::String(_), Value::Object(_)) => { - let non_obj_other = other.to_primitive_num(avm, context)?; + let non_obj_other = other.to_primitive_num(activation, context)?; if let Value::Object(_) = non_obj_other { return Ok(false.into()); } - Ok(self.abstract_eq(non_obj_other, avm, context, true)?) + Ok(self.abstract_eq(non_obj_other, activation, context, true)?) } (Value::Number(_), Value::Object(_)) => { - let non_obj_other = other.to_primitive_num(avm, context)?; + let non_obj_other = other.to_primitive_num(activation, context)?; if let Value::Object(_) = non_obj_other { return Ok(false.into()); } - Ok(self.abstract_eq(non_obj_other, avm, context, true)?) + Ok(self.abstract_eq(non_obj_other, activation, context, true)?) } (Value::Object(_), Value::String(_)) => { - let non_obj_self = self.to_primitive_num(avm, context)?; + let non_obj_self = self.to_primitive_num(activation, context)?; if let Value::Object(_) = non_obj_self { return Ok(false.into()); } - Ok(non_obj_self.abstract_eq(other, avm, context, true)?) + Ok(non_obj_self.abstract_eq(other, activation, context, true)?) } (Value::Object(_), Value::Number(_)) => { - let non_obj_self = self.to_primitive_num(avm, context)?; + let non_obj_self = self.to_primitive_num(activation, context)?; if let Value::Object(_) = non_obj_self { return Ok(false.into()); } - Ok(non_obj_self.abstract_eq(other, avm, context, true)?) + Ok(non_obj_self.abstract_eq(other, activation, context, true)?) } _ => Ok(false.into()), } @@ -408,10 +409,11 @@ impl<'gc> Value<'gc> { #[allow(dead_code)] pub fn coerce_to_u16( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result> { - self.coerce_to_f64(avm, context).map(f64_to_wrapping_u16) + self.coerce_to_f64(activation, context) + .map(f64_to_wrapping_u16) } /// Coerce a number to an `i16` following the wrapping behavior ECMAScript specifications. @@ -420,10 +422,11 @@ impl<'gc> Value<'gc> { #[allow(dead_code)] pub fn coerce_to_i16( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result> { - self.coerce_to_f64(avm, context).map(f64_to_wrapping_i16) + self.coerce_to_f64(activation, context) + .map(f64_to_wrapping_i16) } /// Coerce a number to an `i32` following the ECMAScript specifications for `ToInt32`. @@ -433,10 +436,11 @@ impl<'gc> Value<'gc> { #[allow(dead_code)] pub fn coerce_to_i32( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result> { - self.coerce_to_f64(avm, context).map(f64_to_wrapping_i32) + self.coerce_to_f64(activation, context) + .map(f64_to_wrapping_i32) } /// Coerce a number to an `u32` following the ECMAScript specifications for `ToUInt32`. @@ -445,25 +449,28 @@ impl<'gc> Value<'gc> { #[allow(dead_code)] pub fn coerce_to_u32( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result> { - self.coerce_to_f64(avm, context).map(f64_to_wrapping_u32) + self.coerce_to_f64(activation, context) + .map(f64_to_wrapping_u32) } /// Coerce a value to a string. pub fn coerce_to_string<'a>( &'a self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error<'gc>> { Ok(match self { - Value::Object(object) => match object.call_method("toString", &[], avm, context)? { - Value::String(s) => Cow::Owned(s), - _ => Cow::Borrowed("[type Object]"), - }, + Value::Object(object) => { + match object.call_method("toString", &[], activation, context)? { + Value::String(s) => Cow::Owned(s), + _ => Cow::Borrowed("[type Object]"), + } + } Value::Undefined => { - if avm.current_swf_version() >= 7 { + if activation.current_swf_version() >= 7 { Cow::Borrowed("undefined") } else { Cow::Borrowed("") @@ -510,22 +517,22 @@ impl<'gc> Value<'gc> { pub fn coerce_to_object( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Object<'gc> { - ValueObject::boxed(avm, context, self.to_owned()) + ValueObject::boxed(activation, context, self.to_owned()) } pub fn call( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Value::Object(object) = self { - object.call(avm, context, this, base_proto, args) + object.call(activation, context, this, base_proto, args) } else { Ok(Value::Undefined) } @@ -593,48 +600,54 @@ pub fn f64_to_wrapping_i32(n: f64) -> i32 { #[cfg(test)] mod test { + use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::{Executable, FunctionObject}; use crate::avm1::globals::create_globals; use crate::avm1::object::{Object, TObject}; - use crate::avm1::return_value::ReturnValue; use crate::avm1::script_object::ScriptObject; use crate::avm1::test_utils::with_avm; - use crate::avm1::{Avm1, Value}; + use crate::avm1::Value; use crate::context::UpdateContext; use enumset::EnumSet; use std::f64::{INFINITY, NAN, NEG_INFINITY}; #[test] fn to_primitive_num() { - with_avm(6, |avm, context, _this| -> Result<(), Error> { + with_avm(6, |activation, context, _this| -> Result<(), Error> { let true_value = Value::Bool(true); let undefined = Value::Undefined; let false_value = Value::Bool(false); let null = Value::Null; assert_eq!( - true_value.to_primitive_num(avm, context).unwrap(), + true_value.to_primitive_num(activation, context).unwrap(), true_value ); - assert_eq!(undefined.to_primitive_num(avm, context).unwrap(), undefined); assert_eq!( - false_value.to_primitive_num(avm, context).unwrap(), + undefined.to_primitive_num(activation, context).unwrap(), + undefined + ); + assert_eq!( + false_value.to_primitive_num(activation, context).unwrap(), false_value ); - assert_eq!(null.to_primitive_num(avm, context).unwrap(), null); + assert_eq!(null.to_primitive_num(activation, context).unwrap(), null); let (protos, global, _) = create_globals(context.gc_context); let vglobal = Value::Object(global); - assert_eq!(vglobal.to_primitive_num(avm, context).unwrap(), undefined); + assert_eq!( + vglobal.to_primitive_num(activation, context).unwrap(), + undefined + ); fn value_of_impl<'gc>( - _: &mut Avm1<'gc>, + _: &mut Activation<'_, 'gc>, _: &mut UpdateContext<'_, 'gc, '_>, _: Object<'gc>, _: &[Value<'gc>], - ) -> Result, Error<'gc>> { + ) -> Result, Error<'gc>> { Ok(5.0.into()) } @@ -654,7 +667,9 @@ mod test { ); assert_eq!( - Value::Object(o).to_primitive_num(avm, context).unwrap(), + Value::Object(o) + .to_primitive_num(activation, context) + .unwrap(), Value::Number(5.0) ); @@ -665,20 +680,20 @@ mod test { #[test] #[allow(clippy::float_cmp)] fn to_number_swf7() { - with_avm(7, |avm, context, _this| -> Result<(), Error> { + with_avm(7, |activation, context, _this| -> Result<(), Error> { let t = Value::Bool(true); let u = Value::Undefined; let f = Value::Bool(false); let n = Value::Null; - assert_eq!(t.coerce_to_f64(avm, context).unwrap(), 1.0); - assert!(u.coerce_to_f64(avm, context).unwrap().is_nan()); - assert_eq!(f.coerce_to_f64(avm, context).unwrap(), 0.0); - assert!(n.coerce_to_f64(avm, context).unwrap().is_nan()); + assert_eq!(t.coerce_to_f64(activation, context).unwrap(), 1.0); + assert!(u.coerce_to_f64(activation, context).unwrap().is_nan()); + assert_eq!(f.coerce_to_f64(activation, context).unwrap(), 0.0); + assert!(n.coerce_to_f64(activation, context).unwrap().is_nan()); let bo = Value::Object(ScriptObject::bare_object(context.gc_context).into()); - assert!(bo.coerce_to_f64(avm, context).unwrap().is_nan()); + assert!(bo.coerce_to_f64(activation, context).unwrap().is_nan()); Ok(()) }); @@ -687,20 +702,20 @@ mod test { #[test] #[allow(clippy::float_cmp)] fn to_number_swf6() { - with_avm(6, |avm, context, _this| -> Result<(), Error> { + with_avm(6, |activation, context, _this| -> Result<(), Error> { let t = Value::Bool(true); let u = Value::Undefined; let f = Value::Bool(false); let n = Value::Null; - assert_eq!(t.coerce_to_f64(avm, context).unwrap(), 1.0); - assert_eq!(u.coerce_to_f64(avm, context).unwrap(), 0.0); - assert_eq!(f.coerce_to_f64(avm, context).unwrap(), 0.0); - assert_eq!(n.coerce_to_f64(avm, context).unwrap(), 0.0); + assert_eq!(t.coerce_to_f64(activation, context).unwrap(), 1.0); + assert_eq!(u.coerce_to_f64(activation, context).unwrap(), 0.0); + assert_eq!(f.coerce_to_f64(activation, context).unwrap(), 0.0); + assert_eq!(n.coerce_to_f64(activation, context).unwrap(), 0.0); let bo = Value::Object(ScriptObject::bare_object(context.gc_context).into()); - assert_eq!(bo.coerce_to_f64(avm, context).unwrap(), 0.0); + assert_eq!(bo.coerce_to_f64(activation, context).unwrap(), 0.0); Ok(()) }); @@ -708,27 +723,36 @@ mod test { #[test] fn abstract_lt_num() { - with_avm(8, |avm, context, _this| -> Result<(), Error> { + with_avm(8, |activation, context, _this| -> Result<(), Error> { let a = Value::Number(1.0); let b = Value::Number(2.0); - assert_eq!(a.abstract_lt(b, avm, context).unwrap(), Value::Bool(true)); + assert_eq!( + a.abstract_lt(b, activation, context).unwrap(), + Value::Bool(true) + ); let nan = Value::Number(NAN); - assert_eq!(a.abstract_lt(nan, avm, context).unwrap(), Value::Undefined); + assert_eq!( + a.abstract_lt(nan, activation, context).unwrap(), + Value::Undefined + ); let inf = Value::Number(INFINITY); - assert_eq!(a.abstract_lt(inf, avm, context).unwrap(), Value::Bool(true)); + assert_eq!( + a.abstract_lt(inf, activation, context).unwrap(), + Value::Bool(true) + ); let neg_inf = Value::Number(NEG_INFINITY); assert_eq!( - a.abstract_lt(neg_inf, avm, context).unwrap(), + a.abstract_lt(neg_inf, activation, context).unwrap(), Value::Bool(false) ); let zero = Value::Number(0.0); assert_eq!( - a.abstract_lt(zero, avm, context).unwrap(), + a.abstract_lt(zero, activation, context).unwrap(), Value::Bool(false) ); @@ -738,36 +762,36 @@ mod test { #[test] fn abstract_gt_num() { - with_avm(8, |avm, context, _this| -> Result<(), Error> { + with_avm(8, |activation, context, _this| -> Result<(), Error> { let a = Value::Number(1.0); let b = Value::Number(2.0); assert_eq!( - b.abstract_lt(a.clone(), avm, context).unwrap(), + b.abstract_lt(a.clone(), activation, context).unwrap(), Value::Bool(false) ); let nan = Value::Number(NAN); assert_eq!( - nan.abstract_lt(a.clone(), avm, context).unwrap(), + nan.abstract_lt(a.clone(), activation, context).unwrap(), Value::Undefined ); let inf = Value::Number(INFINITY); assert_eq!( - inf.abstract_lt(a.clone(), avm, context).unwrap(), + inf.abstract_lt(a.clone(), activation, context).unwrap(), Value::Bool(false) ); let neg_inf = Value::Number(NEG_INFINITY); assert_eq!( - neg_inf.abstract_lt(a.clone(), avm, context).unwrap(), + neg_inf.abstract_lt(a.clone(), activation, context).unwrap(), Value::Bool(true) ); let zero = Value::Number(0.0); assert_eq!( - zero.abstract_lt(a, avm, context).unwrap(), + zero.abstract_lt(a, activation, context).unwrap(), Value::Bool(true) ); @@ -777,11 +801,14 @@ mod test { #[test] fn abstract_lt_str() { - with_avm(8, |avm, context, _this| -> Result<(), Error> { + with_avm(8, |activation, context, _this| -> Result<(), Error> { let a = Value::String("a".to_owned()); let b = Value::String("b".to_owned()); - assert_eq!(a.abstract_lt(b, avm, context).unwrap(), Value::Bool(true)); + assert_eq!( + a.abstract_lt(b, activation, context).unwrap(), + Value::Bool(true) + ); Ok(()) }) @@ -789,11 +816,14 @@ mod test { #[test] fn abstract_gt_str() { - with_avm(8, |avm, context, _this| -> Result<(), Error> { + with_avm(8, |activation, context, _this| -> Result<(), Error> { let a = Value::String("a".to_owned()); let b = Value::String("b".to_owned()); - assert_eq!(b.abstract_lt(a, avm, context).unwrap(), Value::Bool(false)); + assert_eq!( + b.abstract_lt(a, activation, context).unwrap(), + Value::Bool(false) + ); Ok(()) }) diff --git a/core/src/avm1/value_object.rs b/core/src/avm1/value_object.rs index 79c9adf60..5a78080ef 100644 --- a/core/src/avm1/value_object.rs +++ b/core/src/avm1/value_object.rs @@ -1,11 +1,11 @@ //! Object impl for boxed values +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::object::{ObjectPtr, TObject}; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, UpdateContext, Value}; +use crate::avm1::{Object, ScriptObject, UpdateContext, Value}; use enumset::EnumSet; use gc_arena::{Collect, GcCell, MutationContext}; use std::borrow::Cow; @@ -40,7 +40,7 @@ impl<'gc> ValueObject<'gc> { /// If a class exists for a given value type, this function automatically /// selects the correct prototype for it from the system prototypes list. pub fn boxed( - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, value: Value<'gc>, ) -> Object<'gc> { @@ -48,9 +48,9 @@ impl<'gc> ValueObject<'gc> { ob } else { let proto = match &value { - Value::Bool(_) => Some(avm.prototypes.boolean), - Value::Number(_) => Some(avm.prototypes.number), - Value::String(_) => Some(avm.prototypes.string), + Value::Bool(_) => Some(activation.avm().prototypes.boolean), + Value::Number(_) => Some(activation.avm().prototypes.number), + Value::String(_) => Some(activation.avm().prototypes.string), _ => None, }; @@ -65,16 +65,28 @@ impl<'gc> ValueObject<'gc> { // Constructor populates the boxed object with the value. match &value { Value::Bool(_) => { - let _ = - crate::avm1::globals::boolean::boolean(avm, context, obj.into(), &[value]); + let _ = crate::avm1::globals::boolean::boolean( + activation, + context, + obj.into(), + &[value], + ); } Value::Number(_) => { - let _ = - crate::avm1::globals::number::number(avm, context, obj.into(), &[value]); + let _ = crate::avm1::globals::number::number( + activation, + context, + obj.into(), + &[value], + ); } Value::String(_) => { - let _ = - crate::avm1::globals::string::string(avm, context, obj.into(), &[value]); + let _ = crate::avm1::globals::string::string( + activation, + context, + obj.into(), + &[value], + ); } _ => (), } @@ -123,26 +135,29 @@ impl<'gc> TObject<'gc> for ValueObject<'gc> { fn get_local( &self, name: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, ) -> Result, Error<'gc>> { - self.0.read().base.get_local(name, avm, context, this) + self.0 + .read() + .base + .get_local(name, activation, context, this) } fn set( &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error<'gc>> { - self.0.read().base.set(name, value, avm, context) + self.0.read().base.set(name, value, activation, context) } fn call( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, @@ -151,27 +166,26 @@ impl<'gc> TObject<'gc> for ValueObject<'gc> { self.0 .read() .base - .call(avm, context, this, base_proto, args) + .call(activation, context, this, base_proto, args) } fn call_setter( &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, - ) -> Result, Error<'gc>> { + ) -> Option> { self.0 .read() .base - .call_setter(name, value, avm, context, this) + .call_setter(name, value, activation, context) } #[allow(clippy::new_ret_no_self)] fn new( &self, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], @@ -181,11 +195,11 @@ impl<'gc> TObject<'gc> for ValueObject<'gc> { fn delete( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, ) -> bool { - self.0.read().base.delete(avm, gc_context, name) + self.0.read().base.delete(activation, gc_context, name) } fn add_property( @@ -204,7 +218,7 @@ impl<'gc> TObject<'gc> for ValueObject<'gc> { fn add_property_with_case( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, get: Executable<'gc>, @@ -214,7 +228,7 @@ impl<'gc> TObject<'gc> for ValueObject<'gc> { self.0 .read() .base - .add_property_with_case(avm, gc_context, name, get, set, attributes) + .add_property_with_case(activation, gc_context, name, get, set, attributes) } fn define_value( @@ -258,41 +272,50 @@ impl<'gc> TObject<'gc> for ValueObject<'gc> { fn has_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.0.read().base.has_property(avm, context, name) + self.0.read().base.has_property(activation, context, name) } fn has_own_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.0.read().base.has_own_property(avm, context, name) + self.0 + .read() + .base + .has_own_property(activation, context, name) } fn has_own_virtual( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.0.read().base.has_own_virtual(avm, context, name) + self.0 + .read() + .base + .has_own_virtual(activation, context, name) } - fn is_property_overwritable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.0.read().base.is_property_overwritable(avm, name) + fn is_property_overwritable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.0 + .read() + .base + .is_property_overwritable(activation, name) } - fn is_property_enumerable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.0.read().base.is_property_enumerable(avm, name) + fn is_property_enumerable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.0.read().base.is_property_enumerable(activation, name) } - fn get_keys(&self, avm: &mut Avm1<'gc>) -> Vec { - self.0.read().base.get_keys(avm) + fn get_keys(&self, activation: &mut Activation<'_, 'gc>) -> Vec { + self.0.read().base.get_keys(activation) } fn as_string(&self) -> Cow { diff --git a/core/src/avm1/xml_attributes_object.rs b/core/src/avm1/xml_attributes_object.rs index 3c56af8c6..aa5b2bd4a 100644 --- a/core/src/avm1/xml_attributes_object.rs +++ b/core/src/avm1/xml_attributes_object.rs @@ -1,11 +1,11 @@ //! AVM1 object type to represent the attributes of XML nodes +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::object::{ObjectPtr, TObject}; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, UpdateContext, Value}; +use crate::avm1::{Object, ScriptObject, UpdateContext, Value}; use crate::xml::{XMLName, XMLNode}; use enumset::EnumSet; use gc_arena::{Collect, MutationContext}; @@ -60,7 +60,7 @@ impl<'gc> TObject<'gc> for XMLAttributesObject<'gc> { fn get_local( &self, name: &str, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, ) -> Result, Error<'gc>> { @@ -75,61 +75,61 @@ impl<'gc> TObject<'gc> for XMLAttributesObject<'gc> { &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error<'gc>> { self.node().set_attribute_value( context.gc_context, &XMLName::from_str(name), - &value.coerce_to_string(avm, context)?, + &value.coerce_to_string(activation, context)?, ); - self.base().set(name, value, avm, context) + self.base().set(name, value, activation, context) } fn call( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - self.base().call(avm, context, this, base_proto, args) + self.base() + .call(activation, context, this, base_proto, args) } fn call_setter( &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, - ) -> Result, Error<'gc>> { - self.base().call_setter(name, value, avm, context, this) + ) -> Option> { + self.base().call_setter(name, value, activation, context) } #[allow(clippy::new_ret_no_self)] fn new( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { //TODO: `new xmlnode.attributes()` returns undefined, not an object log::warn!("Cannot create new XML Attributes object"); - Ok(Value::Undefined.coerce_to_object(avm, context)) + Ok(Value::Undefined.coerce_to_object(activation, context)) } fn delete( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, ) -> bool { self.node() .delete_attribute(gc_context, &XMLName::from_str(name)); - self.base().delete(avm, gc_context, name) + self.base().delete(activation, gc_context, name) } fn add_property( @@ -146,7 +146,7 @@ impl<'gc> TObject<'gc> for XMLAttributesObject<'gc> { fn add_property_with_case( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, get: Executable<'gc>, @@ -154,7 +154,7 @@ impl<'gc> TObject<'gc> for XMLAttributesObject<'gc> { attributes: EnumSet, ) { self.base() - .add_property_with_case(avm, gc_context, name, get, set, attributes) + .add_property_with_case(activation, gc_context, name, get, set, attributes) } fn define_value( @@ -189,16 +189,16 @@ impl<'gc> TObject<'gc> for XMLAttributesObject<'gc> { fn has_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base().has_property(avm, context, name) + self.base().has_property(activation, context, name) } fn has_own_property( &self, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { @@ -209,23 +209,23 @@ impl<'gc> TObject<'gc> for XMLAttributesObject<'gc> { fn has_own_virtual( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base().has_own_virtual(avm, context, name) + self.base().has_own_virtual(activation, context, name) } - fn is_property_overwritable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.base().is_property_overwritable(avm, name) + fn is_property_overwritable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.base().is_property_overwritable(activation, name) } - fn is_property_enumerable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.base().is_property_enumerable(avm, name) + fn is_property_enumerable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.base().is_property_enumerable(activation, name) } - fn get_keys(&self, avm: &mut Avm1<'gc>) -> Vec { - self.base().get_keys(avm) + fn get_keys(&self, activation: &mut Activation<'_, 'gc>) -> Vec { + self.base().get_keys(activation) } fn as_string(&self) -> Cow { diff --git a/core/src/avm1/xml_idmap_object.rs b/core/src/avm1/xml_idmap_object.rs index fe6b251fb..4ba730493 100644 --- a/core/src/avm1/xml_idmap_object.rs +++ b/core/src/avm1/xml_idmap_object.rs @@ -1,11 +1,11 @@ //! AVM1 object type to represent the attributes of XML nodes +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::object::{ObjectPtr, TObject}; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, UpdateContext, Value}; +use crate::avm1::{Object, ScriptObject, UpdateContext, Value}; use crate::xml::{XMLDocument, XMLNode}; use enumset::EnumSet; use gc_arena::{Collect, MutationContext}; @@ -60,16 +60,19 @@ impl<'gc> TObject<'gc> for XMLIDMapObject<'gc> { fn get_local( &self, name: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, ) -> Result, Error<'gc>> { if let Some(mut node) = self.document().get_node_by_id(name) { Ok(node - .script_object(context.gc_context, Some(avm.prototypes().xml_node)) + .script_object( + context.gc_context, + Some(activation.avm().prototypes().xml_node), + ) .into()) } else { - self.base().get_local(name, avm, context, this) + self.base().get_local(name, activation, context, this) } } @@ -77,54 +80,54 @@ impl<'gc> TObject<'gc> for XMLIDMapObject<'gc> { &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error<'gc>> { - self.base().set(name, value, avm, context) + self.base().set(name, value, activation, context) } fn call( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - self.base().call(avm, context, this, base_proto, args) + self.base() + .call(activation, context, this, base_proto, args) } fn call_setter( &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, - ) -> Result, Error<'gc>> { - self.base().call_setter(name, value, avm, context, this) + ) -> Option> { + self.base().call_setter(name, value, activation, context) } #[allow(clippy::new_ret_no_self)] fn new( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { //TODO: `new xmlnode.attributes()` returns undefined, not an object log::warn!("Cannot create new XML Attributes object"); - Ok(Value::Undefined.coerce_to_object(avm, context)) + Ok(Value::Undefined.coerce_to_object(activation, context)) } fn delete( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, ) -> bool { - self.base().delete(avm, gc_context, name) + self.base().delete(activation, gc_context, name) } fn add_property( @@ -141,7 +144,7 @@ impl<'gc> TObject<'gc> for XMLIDMapObject<'gc> { fn add_property_with_case( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, get: Executable<'gc>, @@ -149,7 +152,7 @@ impl<'gc> TObject<'gc> for XMLIDMapObject<'gc> { attributes: EnumSet, ) { self.base() - .add_property_with_case(avm, gc_context, name, get, set, attributes) + .add_property_with_case(activation, gc_context, name, get, set, attributes) } fn define_value( @@ -184,42 +187,42 @@ impl<'gc> TObject<'gc> for XMLIDMapObject<'gc> { fn has_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base().has_property(avm, context, name) + self.base().has_property(activation, context, name) } fn has_own_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { self.document().get_node_by_id(name).is_some() - || self.base().has_own_property(avm, context, name) + || self.base().has_own_property(activation, context, name) } fn has_own_virtual( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base().has_own_virtual(avm, context, name) + self.base().has_own_virtual(activation, context, name) } - fn is_property_overwritable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.base().is_property_overwritable(avm, name) + fn is_property_overwritable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.base().is_property_overwritable(activation, name) } - fn is_property_enumerable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.base().is_property_enumerable(avm, name) + fn is_property_enumerable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.base().is_property_enumerable(activation, name) } - fn get_keys(&self, avm: &mut Avm1<'gc>) -> Vec { - let mut keys = self.base().get_keys(avm); + fn get_keys(&self, activation: &mut Activation<'_, 'gc>) -> Vec { + let mut keys = self.base().get_keys(activation); keys.extend(self.document().get_node_ids().into_iter()); keys } diff --git a/core/src/avm1/xml_object.rs b/core/src/avm1/xml_object.rs index 76c98448c..dcbf8a8cf 100644 --- a/core/src/avm1/xml_object.rs +++ b/core/src/avm1/xml_object.rs @@ -1,11 +1,11 @@ //! AVM1 object type to represent XML nodes +use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::object::{ObjectPtr, TObject}; use crate::avm1::property::Attribute; -use crate::avm1::return_value::ReturnValue; -use crate::avm1::{Avm1, Object, ScriptObject, UpdateContext, Value}; +use crate::avm1::{Object, ScriptObject, UpdateContext, Value}; use crate::xml::{XMLDocument, XMLNode}; use enumset::EnumSet; use gc_arena::{Collect, MutationContext}; @@ -61,49 +61,49 @@ impl<'gc> TObject<'gc> for XMLObject<'gc> { fn get_local( &self, name: &str, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, ) -> Result, Error<'gc>> { - self.base().get_local(name, avm, context, this) + self.base().get_local(name, activation, context, this) } fn set( &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error<'gc>> { - self.base().set(name, value, avm, context) + self.base().set(name, value, activation, context) } fn call( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - self.base().call(avm, context, this, base_proto, args) + self.base() + .call(activation, context, this, base_proto, args) } fn call_setter( &self, name: &str, value: Value<'gc>, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, - this: Object<'gc>, - ) -> Result, Error<'gc>> { - self.base().call_setter(name, value, avm, context, this) + ) -> Option> { + self.base().call_setter(name, value, activation, context) } #[allow(clippy::new_ret_no_self)] fn new( &self, - _avm: &mut Avm1<'gc>, + _activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, _args: &[Value<'gc>], @@ -113,11 +113,11 @@ impl<'gc> TObject<'gc> for XMLObject<'gc> { fn delete( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, ) -> bool { - self.base().delete(avm, gc_context, name) + self.base().delete(activation, gc_context, name) } fn add_property( @@ -134,7 +134,7 @@ impl<'gc> TObject<'gc> for XMLObject<'gc> { fn add_property_with_case( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, gc_context: MutationContext<'gc, '_>, name: &str, get: Executable<'gc>, @@ -142,7 +142,7 @@ impl<'gc> TObject<'gc> for XMLObject<'gc> { attributes: EnumSet, ) { self.base() - .add_property_with_case(avm, gc_context, name, get, set, attributes) + .add_property_with_case(activation, gc_context, name, get, set, attributes) } fn define_value( @@ -177,41 +177,41 @@ impl<'gc> TObject<'gc> for XMLObject<'gc> { fn has_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base().has_property(avm, context, name) + self.base().has_property(activation, context, name) } fn has_own_property( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base().has_own_property(avm, context, name) + self.base().has_own_property(activation, context, name) } fn has_own_virtual( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { - self.base().has_own_virtual(avm, context, name) + self.base().has_own_virtual(activation, context, name) } - fn is_property_overwritable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.base().is_property_overwritable(avm, name) + fn is_property_overwritable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.base().is_property_overwritable(activation, name) } - fn is_property_enumerable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { - self.base().is_property_enumerable(avm, name) + fn is_property_enumerable(&self, activation: &mut Activation<'_, 'gc>, name: &str) -> bool { + self.base().is_property_enumerable(activation, name) } - fn get_keys(&self, avm: &mut Avm1<'gc>) -> Vec { - self.base().get_keys(avm) + fn get_keys(&self, activation: &mut Activation<'_, 'gc>) -> Vec { + self.base().get_keys(activation) } fn as_string(&self) -> Cow { diff --git a/core/src/display_object.rs b/core/src/display_object.rs index b21f2575f..b40cb2ca5 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -20,6 +20,7 @@ mod morph_shape; mod movie_clip; mod text; +use crate::avm1::activation::Activation; use crate::events::{ClipEvent, ClipEventResult}; pub use bitmap::Bitmap; pub use button::Button; @@ -913,7 +914,7 @@ pub trait TDisplayObject<'gc>: 'gc + Collect + Debug + Into> fn bind_text_field_variables( &self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) { // Check all unbound text fields to see if they apply to this object. @@ -921,7 +922,9 @@ pub trait TDisplayObject<'gc>: 'gc + Collect + Debug + Into> let mut i = 0; let mut len = context.unbound_text_fields.len(); while i < len { - if context.unbound_text_fields[i].try_bind_text_field_variable(avm, context, false) { + if context.unbound_text_fields[i] + .try_bind_text_field_variable(activation, context, false) + { context.unbound_text_fields.swap_remove(i); len -= 1; } else { diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index c00c0ebc0..66068000d 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -1,4 +1,5 @@ //! `EditText` display object and support code. +use crate::avm1::activation::Activation; use crate::avm1::globals::text_field::attach_virtual_properties; use crate::avm1::{Avm1, Object, StageObject, TObject, Value}; use crate::context::{RenderContext, UpdateContext}; @@ -502,7 +503,7 @@ impl<'gc> EditText<'gc> { pub fn set_variable( self, variable: Option, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) { // Clear previous binding. @@ -526,7 +527,7 @@ impl<'gc> EditText<'gc> { let _ = self.set_text(text, context); self.0.write(context.gc_context).variable = variable; - self.try_bind_text_field_variable(avm, context, true); + self.try_bind_text_field_variable(activation, context, true); } /// Construct a base text transform for this `EditText`, to be used for @@ -698,7 +699,7 @@ impl<'gc> EditText<'gc> { /// This is called when the text field is created, and, if the text field is in the unbound list, anytime a display object is created. pub fn try_bind_text_field_variable( self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, set_initial_value: bool, ) -> bool { @@ -714,41 +715,46 @@ impl<'gc> EditText<'gc> { let parent = self.parent().unwrap(); - avm.insert_stack_frame_for_display_object( + activation.run_with_child_frame_for_display_object( parent, context.swf.header().version, context, - ); + |activation, context| { + if let Ok(Some((object, property))) = + activation.resolve_text_field_variable_path(context, parent, &variable) + { + // If this text field was just created, we immediately propagate the text to the variable (or vice versa). + if set_initial_value { + // If the property exists on the object, we overwrite the text with the property's value. + if object.has_property(activation, context, property) { + let value = object.get(property, activation, context).unwrap(); + let _ = self.set_text( + value + .coerce_to_string(activation, 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().into(), activation, context); + } + } - if let Ok(Some((object, property))) = - avm.resolve_text_field_variable_path(context, parent, &variable) - { - // If this text field was just created, we immediately propagate the text to the variable (or vice versa). - if set_initial_value { - // 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().into(), avm, context); + if let Some(stage_object) = object.as_stage_object() { + self.0.write(context.gc_context).bound_stage_object = + Some(stage_object); + stage_object.register_text_field_binding( + context.gc_context, + self, + property, + ); + bound = true; + } } - } - - if let Some(stage_object) = object.as_stage_object() { - self.0.write(context.gc_context).bound_stage_object = Some(stage_object); - stage_object.register_text_field_binding(context.gc_context, self, property); - bound = true; - } - } - - avm.retire_stack_frame(context, Value::Undefined); + }, + ); } bound @@ -765,7 +771,7 @@ impl<'gc> EditText<'gc> { /// pub fn propagate_text_binding( self, - avm: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) { if !self.0.read().firing_variable_binding { @@ -776,7 +782,7 @@ impl<'gc> EditText<'gc> { let variable_path = variable.to_string(); drop(variable); - if let Ok(Some((object, property))) = avm.resolve_text_field_variable_path( + if let Ok(Some((object, property))) = activation.resolve_text_field_variable_path( context, self.parent().unwrap(), &variable_path, @@ -791,13 +797,14 @@ impl<'gc> EditText<'gc> { // Note that this can call virtual setters, even though the opposite direction won't work // (virtual property changes do not affect the text field) - avm.insert_stack_frame_for_display_object( + activation.run_with_child_frame_for_display_object( self.parent().unwrap(), context.swf.header().version, context, + |activation, context| { + let _ = object.set(property, text.into(), activation, context); + }, ); - let _ = object.set(property, text.into(), avm, context); - avm.retire_stack_frame(context, Value::Undefined); } } self.0.write(context.gc_context).firing_variable_binding = false; @@ -861,12 +868,18 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> { drop(text); // If this text field has a variable set, initialize text field binding. - if !self.try_bind_text_field_variable(avm, context, true) { - context.unbound_text_fields.push(*self); - } - - // People can bind to properties of TextFields the same as other display objects. - self.bind_text_field_variables(avm, context); + avm.run_with_stack_frame_for_display_object( + (*self).into(), + context.swf.version(), + context, + |activation, context| { + if !self.try_bind_text_field_variable(activation, context, true) { + context.unbound_text_fields.push(*self); + } + // People can bind to properties of TextFields the same as other display objects. + self.bind_text_field_variables(activation, context); + }, + ); } fn object(&self) -> Value<'gc> { diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index ea017c716..e270cd320 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -2,6 +2,7 @@ use crate::avm1::{Avm1, Object, StageObject, TObject, Value}; use crate::backend::audio::AudioStreamHandle; +use crate::avm1::activation::Activation; use crate::character::Character; use crate::context::{ActionType, RenderContext, UpdateContext}; use crate::display_object::{ @@ -373,14 +374,12 @@ impl<'gc> MovieClip<'gc> { ) })?; - avm.insert_stack_frame_for_init_action( + avm.run_stack_frame_for_init_action( *context.levels.get(&0).unwrap(), context.swf.header().version, slice, context, ); - let frame = avm.current_stack_frame().unwrap(); - let _ = avm.run_current_frame(context, frame); Ok(()) } @@ -976,10 +975,18 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { return Some(self_node); } - let object = self.object().coerce_to_object(avm, context); + let mut activation = Activation::from_nothing( + avm, + context.swf.version(), + avm.global_object_cell(), + context.gc_context, + *context.levels.get(&0).unwrap(), + ); + let object = self.object().coerce_to_object(&mut activation, context); + if ClipEvent::BUTTON_EVENT_METHODS .iter() - .any(|handler| object.has_property(avm, context, handler)) + .any(|handler| object.has_property(&mut activation, context, handler)) { return Some(self_node); } @@ -1033,11 +1040,18 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { // If we are running within the AVM, this must be an immediate action. // If we are not, then this must be queued to be ran first-thing if instantiated_from_avm && self.0.read().avm1_constructor.is_some() { - let constructor = self.0.read().avm1_constructor.unwrap(); + let mut activation = Activation::from_nothing( + avm, + context.swf.version(), + avm.global_object_cell(), + context.gc_context, + *context.levels.get(&0).unwrap(), + ); + let constructor = self.0.read().avm1_constructor.unwrap(); if let Ok(prototype) = constructor - .get("prototype", avm, context) - .map(|v| v.coerce_to_object(avm, context)) + .get("prototype", &mut activation, context) + .map(|v| v.coerce_to_object(&mut activation, context)) { let object: Object<'gc> = StageObject::for_display_object( context.gc_context, @@ -1046,16 +1060,17 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { ) .into(); if let Some(init_object) = init_object { - for key in init_object.get_keys(avm) { - if let Ok(value) = init_object.get(&key, avm, context) { - let _ = object.set(&key, value, avm, context); + for key in init_object.get_keys(&mut activation) { + if let Ok(value) = init_object.get(&key, &mut activation, context) { + let _ = object.set(&key, value, &mut activation, context); } } } self.0.write(context.gc_context).object = Some(object); - let _ = constructor.call(avm, context, object, None, &[]); - return; + let _ = constructor.call(&mut activation, context, object, None, &[]); } + + return; } let object = StageObject::for_display_object( @@ -1064,9 +1079,17 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { Some(context.system_prototypes.movie_clip), ); if let Some(init_object) = init_object { - for key in init_object.get_keys(avm) { - if let Ok(value) = init_object.get(&key, avm, context) { - let _ = object.set(&key, value, avm, context); + let mut activation = Activation::from_nothing( + avm, + context.swf.version(), + avm.global_object_cell(), + context.gc_context, + *context.levels.get(&0).unwrap(), + ); + + for key in init_object.get_keys(&mut activation) { + if let Ok(value) = init_object.get(&key, &mut activation, context) { + let _ = object.set(&key, value, &mut activation, context); } } } @@ -1093,7 +1116,15 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { ); } - self.bind_text_field_variables(avm, context); + // If this text field has a variable set, initialize text field binding. + avm.run_with_stack_frame_for_display_object( + (*self).into(), + context.swf.version(), + context, + |activation, context| { + self.bind_text_field_variables(activation, context); + }, + ); } fn object(&self) -> Value<'gc> { diff --git a/core/src/html/text_format.rs b/core/src/html/text_format.rs index e3bbc4024..f22342f92 100644 --- a/core/src/html/text_format.rs +++ b/core/src/html/text_format.rs @@ -1,5 +1,6 @@ //! Classes that store formatting options -use crate::avm1::{Avm1, Object, ScriptObject, TObject, Value}; +use crate::avm1::activation::Activation; +use crate::avm1::{Object, ScriptObject, TObject, Value}; use crate::context::UpdateContext; use crate::html::iterators::TextSpanIter; use crate::tag_utils::SwfMovie; @@ -85,57 +86,57 @@ pub struct TextFormat { fn getstr_from_avm1_object<'gc>( object: Object<'gc>, name: &str, - avm1: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, uc: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, crate::avm1::error::Error<'gc>> { - Ok(match object.get(name, avm1, uc)? { + Ok(match object.get(name, activation, uc)? { Value::Undefined => None, Value::Null => None, - v => Some(v.coerce_to_string(avm1, uc)?.to_string()), + v => Some(v.coerce_to_string(activation, uc)?.to_string()), }) } fn getfloat_from_avm1_object<'gc>( object: Object<'gc>, name: &str, - avm1: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, uc: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, crate::avm1::error::Error<'gc>> { - Ok(match object.get(name, avm1, uc)? { + Ok(match object.get(name, activation, uc)? { Value::Undefined => None, Value::Null => None, - v => Some(v.coerce_to_f64(avm1, uc)?), + v => Some(v.coerce_to_f64(activation, uc)?), }) } fn getbool_from_avm1_object<'gc>( object: Object<'gc>, name: &str, - avm1: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, uc: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, crate::avm1::error::Error<'gc>> { - Ok(match object.get(name, avm1, uc)? { + Ok(match object.get(name, activation, uc)? { Value::Undefined => None, Value::Null => None, - v => Some(v.as_bool(avm1.current_swf_version())), + v => Some(v.as_bool(activation.current_swf_version())), }) } fn getfloatarray_from_avm1_object<'gc>( object: Object<'gc>, name: &str, - avm1: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, uc: &mut UpdateContext<'_, 'gc, '_>, ) -> Result>, crate::avm1::error::Error<'gc>> { - Ok(match object.get(name, avm1, uc)? { + Ok(match object.get(name, activation, uc)? { Value::Undefined => None, Value::Null => None, v => { let mut output = Vec::new(); - let v = v.coerce_to_object(avm1, uc); + let v = v.coerce_to_object(activation, uc); for i in 0..v.length() { - output.push(v.array_element(i).coerce_to_f64(avm1, uc)?); + output.push(v.array_element(i).coerce_to_f64(activation, uc)?); } Some(output) @@ -197,37 +198,38 @@ impl TextFormat { /// Construct a `TextFormat` from an object that is pub fn from_avm1_object<'gc>( object1: Object<'gc>, - avm1: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, uc: &mut UpdateContext<'_, 'gc, '_>, ) -> Result> { Ok(Self { - font: getstr_from_avm1_object(object1, "font", avm1, uc)?, - size: getfloat_from_avm1_object(object1, "size", avm1, uc)?, - color: getfloat_from_avm1_object(object1, "color", avm1, uc)? + font: getstr_from_avm1_object(object1, "font", activation, uc)?, + size: getfloat_from_avm1_object(object1, "size", activation, uc)?, + color: getfloat_from_avm1_object(object1, "color", activation, uc)? .map(|v| swf::Color::from_rgb(v as u32, 0xFF)), - align: getstr_from_avm1_object(object1, "align", avm1, uc)?.and_then(|v| { - match v.to_lowercase().as_str() { - "left" => Some(swf::TextAlign::Left), - "center" => Some(swf::TextAlign::Center), - "right" => Some(swf::TextAlign::Right), - "justify" => Some(swf::TextAlign::Justify), - _ => None, - } + align: getstr_from_avm1_object(object1, "align", activation, uc)?.and_then(|v| match v + .to_lowercase() + .as_str() + { + "left" => Some(swf::TextAlign::Left), + "center" => Some(swf::TextAlign::Center), + "right" => Some(swf::TextAlign::Right), + "justify" => Some(swf::TextAlign::Justify), + _ => None, }), - bold: getbool_from_avm1_object(object1, "bold", avm1, uc)?, - italic: getbool_from_avm1_object(object1, "italic", avm1, uc)?, - underline: getbool_from_avm1_object(object1, "underline", avm1, uc)?, - left_margin: getfloat_from_avm1_object(object1, "leftMargin", avm1, uc)?, - right_margin: getfloat_from_avm1_object(object1, "rightMargin", avm1, uc)?, - indent: getfloat_from_avm1_object(object1, "indent", avm1, uc)?, - block_indent: getfloat_from_avm1_object(object1, "blockIndent", avm1, uc)?, - kerning: getbool_from_avm1_object(object1, "kerning", avm1, uc)?, - leading: getfloat_from_avm1_object(object1, "leading", avm1, uc)?, - letter_spacing: getfloat_from_avm1_object(object1, "letterSpacing", avm1, uc)?, - tab_stops: getfloatarray_from_avm1_object(object1, "tabStops", avm1, uc)?, - bullet: getbool_from_avm1_object(object1, "bullet", avm1, uc)?, - url: getstr_from_avm1_object(object1, "url", avm1, uc)?, - target: getstr_from_avm1_object(object1, "target", avm1, uc)?, + bold: getbool_from_avm1_object(object1, "bold", activation, uc)?, + italic: getbool_from_avm1_object(object1, "italic", activation, uc)?, + underline: getbool_from_avm1_object(object1, "underline", activation, uc)?, + left_margin: getfloat_from_avm1_object(object1, "leftMargin", activation, uc)?, + right_margin: getfloat_from_avm1_object(object1, "rightMargin", activation, uc)?, + indent: getfloat_from_avm1_object(object1, "indent", activation, uc)?, + block_indent: getfloat_from_avm1_object(object1, "blockIndent", activation, uc)?, + kerning: getbool_from_avm1_object(object1, "kerning", activation, uc)?, + leading: getfloat_from_avm1_object(object1, "leading", activation, uc)?, + letter_spacing: getfloat_from_avm1_object(object1, "letterSpacing", activation, uc)?, + tab_stops: getfloatarray_from_avm1_object(object1, "tabStops", activation, uc)?, + bullet: getbool_from_avm1_object(object1, "bullet", activation, uc)?, + url: getstr_from_avm1_object(object1, "url", activation, uc)?, + target: getstr_from_avm1_object(object1, "target", activation, uc)?, }) } @@ -350,21 +352,24 @@ impl TextFormat { /// Construct a `TextFormat` AVM1 object from this text format object. pub fn as_avm1_object<'gc>( &self, - avm1: &mut Avm1<'gc>, + activation: &mut Activation<'_, 'gc>, uc: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, crate::avm1::error::Error<'gc>> { - let object = ScriptObject::object(uc.gc_context, Some(avm1.prototypes().text_format)); + let object = ScriptObject::object( + uc.gc_context, + Some(activation.avm().prototypes().text_format), + ); object.set( "font", self.font.clone().map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( "size", self.size.map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( @@ -373,7 +378,7 @@ impl TextFormat { .clone() .map(|v| (((v.r as u32) << 16) + ((v.g as u32) << 8) + v.b as u32).into()) .unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( @@ -389,90 +394,91 @@ impl TextFormat { .into() }) .unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( "bold", self.bold.map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( "italic", self.italic.map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( "underline", self.underline.map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( "leftMargin", self.left_margin.map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( "rightMargin", self.right_margin.map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( "indent", self.indent.map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( "blockIndent", self.block_indent.map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( "kerning", self.kerning.map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( "leading", self.leading.map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( "letterSpacing", self.letter_spacing.map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( "bullet", self.bullet.map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( "url", self.url.clone().map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; object.set( "target", self.target.clone().map(|v| v.into()).unwrap_or(Value::Null), - avm1, + activation, uc, )?; if let Some(ts) = &self.tab_stops { - let tab_stops = ScriptObject::array(uc.gc_context, Some(avm1.prototypes().array)); + let tab_stops = + ScriptObject::array(uc.gc_context, Some(activation.avm().prototypes().array)); tab_stops.set_length(uc.gc_context, ts.len()); @@ -480,9 +486,9 @@ impl TextFormat { tab_stops.set_array_element(index, (*tab).into(), uc.gc_context); } - object.set("tabStops", tab_stops.into(), avm1, uc)?; + object.set("tabStops", tab_stops.into(), activation, uc)?; } else { - object.set("tabStops", Value::Null, avm1, uc)?; + object.set("tabStops", Value::Null, activation, uc)?; } Ok(object.into()) diff --git a/core/src/loader.rs b/core/src/loader.rs index 981ab3f81..6ac40af9c 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -1,5 +1,6 @@ //! Management of async loaders +use crate::avm1::activation::Activation; use crate::avm1::{Object, TObject, Value}; use crate::backend::navigator::OwnedFuture; use crate::context::{ActionQueue, ActionType}; @@ -314,7 +315,7 @@ impl<'gc> Loader<'gc> { .replace_with_movie(uc.gc_context, None); if let Some(broadcaster) = broadcaster { - avm.insert_stack_frame_for_method( + avm.run_stack_frame_for_method( clip, broadcaster, NEWEST_PLAYER_VERSION, @@ -322,7 +323,6 @@ impl<'gc> Loader<'gc> { "broadcastMessage", &["onLoadStart".into(), Value::Object(broadcaster)], ); - avm.run_stack_till_empty(uc)?; } Ok(()) @@ -348,7 +348,7 @@ impl<'gc> Loader<'gc> { }; if let Some(broadcaster) = broadcaster { - avm.insert_stack_frame_for_method( + avm.run_stack_frame_for_method( clip, broadcaster, NEWEST_PLAYER_VERSION, @@ -361,7 +361,6 @@ impl<'gc> Loader<'gc> { length.into(), ], ); - avm.run_stack_till_empty(uc)?; } let mut mc = clip @@ -386,7 +385,7 @@ impl<'gc> Loader<'gc> { } if let Some(broadcaster) = broadcaster { - avm.insert_stack_frame_for_method( + avm.run_stack_frame_for_method( clip, broadcaster, NEWEST_PLAYER_VERSION, @@ -394,7 +393,6 @@ impl<'gc> Loader<'gc> { "broadcastMessage", &["onLoadComplete".into(), Value::Object(broadcaster)], ); - avm.run_stack_till_empty(uc)?; } if let Some(Loader::Movie { load_complete, .. }) = @@ -424,7 +422,7 @@ impl<'gc> Loader<'gc> { }; if let Some(broadcaster) = broadcaster { - avm.insert_stack_frame_for_method( + avm.run_stack_frame_for_method( clip, broadcaster, NEWEST_PLAYER_VERSION, @@ -436,7 +434,6 @@ impl<'gc> Loader<'gc> { "LoadNeverCompleted".into(), ], ); - avm.run_stack_till_empty(uc)?; } if let Some(Loader::Movie { load_complete, .. }) = @@ -477,8 +474,15 @@ impl<'gc> Loader<'gc> { _ => return Err(Error::NotMovieLoader), }; + let mut activation = Activation::from_nothing( + avm, + uc.swf.version(), + avm.global_object_cell(), + uc.gc_context, + *uc.levels.get(&0).unwrap(), + ); for (k, v) in form_urlencoded::parse(&data) { - that.set(&k, v.into_owned().into(), avm, uc)?; + that.set(&k, v.into_owned().into(), &mut activation, uc)?; } Ok(()) @@ -562,7 +566,7 @@ impl<'gc> Loader<'gc> { let object = node.script_object(uc.gc_context, Some(avm.prototypes().xml_node)); - avm.insert_stack_frame_for_method( + avm.run_stack_frame_for_method( active_clip, object, NEWEST_PLAYER_VERSION, @@ -570,9 +574,8 @@ impl<'gc> Loader<'gc> { "onHTTPStatus", &[200.into()], ); - avm.run_stack_till_empty(uc)?; - avm.insert_stack_frame_for_method( + avm.run_stack_frame_for_method( active_clip, object, NEWEST_PLAYER_VERSION, @@ -580,7 +583,6 @@ impl<'gc> Loader<'gc> { "onData", &[xmlstring.into()], ); - avm.run_stack_till_empty(uc)?; Ok(()) }, @@ -600,7 +602,7 @@ impl<'gc> Loader<'gc> { let object = node.script_object(uc.gc_context, Some(avm.prototypes().xml_node)); - avm.insert_stack_frame_for_method( + avm.run_stack_frame_for_method( active_clip, object, NEWEST_PLAYER_VERSION, @@ -608,9 +610,8 @@ impl<'gc> Loader<'gc> { "onHTTPStatus", &[404.into()], ); - avm.run_stack_till_empty(uc)?; - avm.insert_stack_frame_for_method( + avm.run_stack_frame_for_method( active_clip, object, NEWEST_PLAYER_VERSION, @@ -618,7 +619,6 @@ impl<'gc> Loader<'gc> { "onData", &[], ); - avm.run_stack_till_empty(uc)?; Ok(()) }, diff --git a/core/src/player.rs b/core/src/player.rs index bccf223f3..0c5f843d6 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -1,8 +1,9 @@ +use crate::avm1::activation::Activation; use crate::avm1::debug::VariableDumper; use crate::avm1::globals::system::SystemProperties; use crate::avm1::listeners::SystemListener; use crate::avm1::object::Object; -use crate::avm1::{Activation, Avm1, TObject, Value}; +use crate::avm1::{Avm1, TObject, Value}; use crate::backend::input::{InputBackend, MouseCursor}; use crate::backend::storage::StorageBackend; use crate::backend::{ @@ -271,11 +272,18 @@ impl Player { root.set_name(context.gc_context, ""); context.levels.insert(0, root); - let object = root.object().coerce_to_object(avm, context); + let mut activation = Activation::from_nothing( + avm, + context.swf.version(), + avm.global_object_cell(), + context.gc_context, + *context.levels.get(&0).unwrap(), + ); + let object = root.object().coerce_to_object(&mut activation, context); object.define_value( context.gc_context, "$version", - context.system.get_version_string(avm).into(), + context.system.get_version_string(&mut activation).into(), EnumSet::empty(), ); }); @@ -379,21 +387,32 @@ impl Player { if self.input.is_key_down(KeyCode::Control) && self.input.is_key_down(KeyCode::Alt) { self.mutate_with_update_context(|avm, context| { let mut dumper = VariableDumper::new(" "); + + let mut activation = Activation::from_nothing( + avm, + context.swf.version(), + avm.global_object_cell(), + context.gc_context, + *context.levels.get(&0).unwrap(), + ); + dumper.print_variables( "Global Variables:", "_global", - &avm.global_object_cell(), - avm, + &activation.avm().global_object_cell(), + &mut activation, context, ); let levels = context.levels.clone(); for (level, display_object) in levels { - let object = display_object.object().coerce_to_object(avm, context); + let object = display_object + .object() + .coerce_to_object(&mut activation, context); dumper.print_variables( &format!("Level #{}:", level), &format!("_level{}", level), &object, - avm, + &mut activation, context, ); } @@ -521,7 +540,7 @@ impl Player { /// Update dragged object, if any. fn update_drag(&mut self) { let mouse_pos = self.mouse_pos; - self.mutate_with_update_context(|_avm, context| { + self.mutate_with_update_context(|_activation, context| { if let Some(drag_object) = &mut context.drag_object { if drag_object.display_object.removed() { // Be sure to clear the drag if the object was removed. @@ -608,12 +627,12 @@ impl Player { /// This should only be called once. Further movie loads should preload the /// specific `MovieClip` referenced. fn preload(&mut self) { - self.mutate_with_update_context(|avm, context| { + self.mutate_with_update_context(|activation, context| { let mut morph_shapes = fnv::FnvHashMap::default(); let root = *context.levels.get(&0).expect("root level"); root.as_movie_clip() .unwrap() - .preload(avm, context, &mut morph_shapes); + .preload(activation, context, &mut morph_shapes); // Finalize morph shapes. for (id, static_data) in morph_shapes { @@ -719,7 +738,7 @@ impl Player { match actions.action_type { // DoAction/clip event code ActionType::Normal { bytecode } => { - avm.insert_stack_frame_for_action( + avm.run_stack_frame_for_action( actions.clip, context.swf.header().version, bytecode, @@ -731,41 +750,29 @@ impl Player { constructor: Some(constructor), events, } => { - avm.insert_stack_frame(GcCell::allocate( + let mut activation = Activation::from_nothing( + avm, + context.swf.version(), + avm.global_object_cell(), context.gc_context, - Activation::from_nothing( - context.swf.header().version, - avm.global_object_cell(), - context.gc_context, - actions.clip, - ), - )); + *context.levels.get(&0).unwrap(), + ); if let Ok(prototype) = constructor - .get("prototype", avm, context) - .map(|v| v.coerce_to_object(avm, context)) + .get("prototype", &mut activation, context) + .map(|v| v.coerce_to_object(&mut activation, context)) { if let Value::Object(object) = actions.clip.object() { object.set_proto(context.gc_context, Some(prototype)); for event in events { - avm.insert_stack_frame_for_action( + let _ = activation.run_child_frame_for_action( actions.clip, context.swf.header().version, event, context, ); } - let _ = avm.run_stack_till_empty(context); - avm.insert_stack_frame(GcCell::allocate( - context.gc_context, - Activation::from_nothing( - context.swf.header().version, - avm.global_object_cell(), - context.gc_context, - actions.clip, - ), - )); - let _ = constructor.call(avm, context, object, None, &[]); + let _ = constructor.call(&mut activation, context, object, None, &[]); } } } @@ -775,7 +782,7 @@ impl Player { events, } => { for event in events { - avm.insert_stack_frame_for_action( + avm.run_stack_frame_for_action( actions.clip, context.swf.header().version, event, @@ -785,7 +792,7 @@ impl Player { } // Event handler method call (e.g. onEnterFrame) ActionType::Method { object, name, args } => { - avm.insert_stack_frame_for_method( + avm.run_stack_frame_for_method( actions.clip, object, context.swf.header().version, @@ -813,8 +820,6 @@ impl Player { ); } } - // Execute the stack frame (if any). - let _ = avm.run_stack_till_empty(context); } } @@ -997,10 +1002,18 @@ impl Player { } pub fn flush_shared_objects(&mut self) { - self.update(|avm, update_context| { - let shared_objects = update_context.shared_objects.clone(); + self.update(|avm, context| { + let mut activation = Activation::from_nothing( + avm, + context.swf.version(), + avm.global_object_cell(), + context.gc_context, + *context.levels.get(&0).unwrap(), + ); + let shared_objects = context.shared_objects.clone(); for so in shared_objects.values() { - let _ = crate::avm1::globals::shared_object::flush(avm, update_context, *so, &[]); + let _ = + crate::avm1::globals::shared_object::flush(&mut activation, context, *so, &[]); } }); }