diff --git a/core/src/avm1/globals/button.rs b/core/src/avm1/globals/button.rs index 9010deafc073..18fbc9f200ef 100644 --- a/core/src/avm1/globals/button.rs +++ b/core/src/avm1/globals/button.rs @@ -47,6 +47,8 @@ const PROTO_DECLS: &[Declaration] = declare_properties! { "scale9Grid" => property(button_getter!(scale_9_grid), button_setter!(set_scale_9_grid); DONT_DELETE | DONT_ENUM | VERSION_8); "filters" => property(button_getter!(filters), button_setter!(set_filters); DONT_DELETE | DONT_ENUM | VERSION_8); "cacheAsBitmap" => property(button_getter!(cache_as_bitmap), button_setter!(set_cache_as_bitmap); DONT_DELETE | DONT_ENUM | VERSION_8); + // NOTE: `tabEnabled` is not a built-in property of Button. + "tabIndex" => property(button_getter!(tab_index), button_setter!(set_tab_index); VERSION_6); }; pub fn create_proto<'gc>( @@ -173,3 +175,36 @@ fn set_scale_9_grid<'gc>( }; Ok(()) } + +fn tab_index<'gc>( + this: Avm1Button<'gc>, + _activation: &mut Activation<'_, 'gc>, +) -> Result, Error<'gc>> { + if let Some(index) = this.tab_index_value() { + Ok(index.into()) + } else { + Ok(Value::Undefined) + } +} + +fn set_tab_index<'gc>( + this: Avm1Button<'gc>, + activation: &mut Activation<'_, 'gc>, + value: Value<'gc>, +) -> Result<(), Error<'gc>> { + match value { + Value::Undefined | Value::Null => { + this.set_tab_index_value(&mut activation.context, None); + } + Value::Bool(_) | Value::Number(_) => { + // FIXME This coercion is not perfect, as it wraps + // instead of falling back to MIN, as FP does + let i32_value = value.coerce_to_i32(activation)?; + this.set_tab_index_value(&mut activation.context, Some(i32_value)); + } + _ => { + this.set_tab_index_value(&mut activation.context, Some(i32::MIN)); + } + }; + Ok(()) +} diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index 2bd7dd6654e5..db100ab3e0f4 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -116,6 +116,9 @@ const PROTO_DECLS: &[Declaration] = declare_properties! { "transform" => property(mc_getter!(transform), mc_setter!(set_transform); DONT_ENUM | VERSION_8); "useHandCursor" => bool(true; DONT_ENUM); // NOTE: `focusEnabled` is not a built-in property of MovieClip. + // NOTE: `tabEnabled` is not a built-in property of MovieClip. + // NOTE: `tabIndex` is not enumerable in MovieClip, contrary to Button and TextField + "tabIndex" => property(mc_getter!(tab_index), mc_setter!(set_tab_index); DONT_ENUM | VERSION_6); }; /// Implements `MovieClip` @@ -1819,3 +1822,36 @@ fn set_filters<'gc>( this.set_filters(activation.context.gc_context, filters); Ok(()) } + +fn tab_index<'gc>( + this: MovieClip<'gc>, + _activation: &mut Activation<'_, 'gc>, +) -> Result, Error<'gc>> { + if let Some(index) = this.tab_index_value() { + Ok(index.into()) + } else { + Ok(Value::Undefined) + } +} + +fn set_tab_index<'gc>( + this: MovieClip<'gc>, + activation: &mut Activation<'_, 'gc>, + value: Value<'gc>, +) -> Result<(), Error<'gc>> { + match value { + Value::Undefined | Value::Null => { + this.set_tab_index_value(&mut activation.context, None); + } + Value::Bool(_) | Value::Number(_) => { + // FIXME This coercion is not perfect, as it wraps + // instead of falling back to MIN, as FP does + let i32_value = value.coerce_to_i32(activation)?; + this.set_tab_index_value(&mut activation.context, Some(i32_value)); + } + _ => { + this.set_tab_index_value(&mut activation.context, Some(i32::MIN)); + } + }; + Ok(()) +} diff --git a/core/src/avm1/globals/text_field.rs b/core/src/avm1/globals/text_field.rs index 7ef7619325b8..d93d0cae253e 100644 --- a/core/src/avm1/globals/text_field.rs +++ b/core/src/avm1/globals/text_field.rs @@ -93,6 +93,8 @@ const PROTO_DECLS: &[Declaration] = declare_properties! { "gridFitType" => property(tf_getter!(grid_fit_type), tf_setter!(set_grid_fit_type)); "sharpness" => property(tf_getter!(sharpness), tf_setter!(set_sharpness)); "thickness" => property(tf_getter!(thickness), tf_setter!(set_thickness)); + // NOTE: `tabEnabled` is not a built-in property of TextField. + "tabIndex" => property(tf_getter!(tab_index), tf_setter!(set_tab_index); VERSION_6); }; /// Implements `TextField` @@ -113,6 +115,7 @@ pub fn create_proto<'gc>( define_properties_on(PROTO_DECLS, context, object, fn_proto); object.into() } + pub fn password<'gc>( this: EditText<'gc>, _activation: &mut Activation<'_, 'gc>, @@ -911,3 +914,31 @@ fn set_restrict<'gc>( }; Ok(()) } + +pub fn tab_index<'gc>( + this: EditText<'gc>, + _activation: &mut Activation<'_, 'gc>, +) -> Result, Error<'gc>> { + if let Some(index) = this.tab_index_value() { + Ok(index.into()) + } else { + Ok(Value::Undefined) + } +} + +pub fn set_tab_index<'gc>( + this: EditText<'gc>, + activation: &mut Activation<'_, 'gc>, + value: Value<'gc>, +) -> Result<(), Error<'gc>> { + match value { + Value::Undefined | Value::Null => { + this.set_tab_index_value(&mut activation.context, None); + } + _ => { + let u32_value = value.coerce_to_u32(activation)?; + this.set_tab_index_value(&mut activation.context, Some(u32_value)); + } + }; + Ok(()) +} diff --git a/core/src/display_object.rs b/core/src/display_object.rs index 73e85f1c2042..208888686a2b 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -1890,11 +1890,23 @@ pub trait TDisplayObject<'gc>: } } - /// Whether or not this clip may be focusable for keyboard input. + /// Whether this clip may be focusable for keyboard input. fn is_focusable(&self, _context: &mut UpdateContext<'_, 'gc>) -> bool { false } + /// Whether this object is included in tab ordering. + fn is_tab_enabled(&self, _context: &mut UpdateContext<'_, 'gc>) -> bool { + false + } + + /// Used to customize tab ordering. + /// When not `None`, a custom ordering is used, and + /// objects are ordered according to this value. + fn tab_index(&self) -> Option { + None + } + /// Whether this display object has been created by ActionScript 3. /// When this flag is set, changes from SWF `RemoveObject` tags are /// ignored. @@ -2478,6 +2490,34 @@ pub trait TDisplayObject<'gc>: } } } + + /// Retrieve a named property from the AVM1 object. + /// + /// This is required as some boolean properties in AVM1 can in fact hold any value. + fn get_avm1_boolean_property( + self, + context: &mut UpdateContext<'_, 'gc>, + name: &'static str, + default: bool, + ) -> bool { + if let Avm1Value::Object(object) = self.object() { + let mut activation = Activation::from_nothing( + context.reborrow(), + Avm1ActivationIdentifier::root("[AVM1 Boolean Property]"), + self.avm1_root(), + ); + if let Ok(value) = object.get(name, &mut activation) { + match value { + Avm1Value::Undefined => default, + _ => value.as_bool(activation.swf_version()), + } + } else { + default + } + } else { + false + } + } } pub enum DisplayObjectPtr {} diff --git a/core/src/display_object/avm1_button.rs b/core/src/display_object/avm1_button.rs index fcd49552edae..f867db2a86ba 100644 --- a/core/src/display_object/avm1_button.rs +++ b/core/src/display_object/avm1_button.rs @@ -46,6 +46,9 @@ pub struct Avm1ButtonData<'gc> { object: Lock>>, initialized: Cell, has_focus: Cell, + // TODO Consider moving this to InteractiveObject along with + // TextField's and MovieClip's tab indices after AVM2 analysis + tab_index: Cell>, } #[derive(Clone, Collect)] @@ -99,6 +102,7 @@ impl<'gc> Avm1Button<'gc> { ButtonTracking::Push }), has_focus: Cell::new(false), + tab_index: Cell::new(None), }, )) } @@ -241,6 +245,18 @@ impl<'gc> Avm1Button<'gc> { fn use_hand_cursor(self, context: &mut UpdateContext<'_, 'gc>) -> bool { self.get_boolean_property(context, "useHandCursor", true) } + + /// Get the value of `tabIndex` used in AS. + /// + /// Do not confuse it with `tab_index`, which returns the value used for ordering. + pub fn tab_index_value(&self) -> Option { + self.0.tab_index.get() + } + + /// Set the value of `tabIndex` used in AS. + pub fn set_tab_index_value(&self, _context: &mut UpdateContext<'_, 'gc>, value: Option) { + self.0.tab_index.set(value) + } } impl<'gc> TDisplayObject<'gc> for Avm1Button<'gc> { @@ -406,6 +422,14 @@ impl<'gc> TDisplayObject<'gc> for Avm1Button<'gc> { self.call_focus_handler(context, focused, other); } + fn is_tab_enabled(&self, context: &mut UpdateContext<'_, 'gc>) -> bool { + self.get_avm1_boolean_property(context, "tabEnabled", true) + } + + fn tab_index(&self) -> Option { + self.0.tab_index.get().map(|i| i as i64) + } + fn avm1_unload(&self, context: &mut UpdateContext<'_, 'gc>) { let had_focus = self.0.has_focus.get(); if had_focus { diff --git a/core/src/display_object/container.rs b/core/src/display_object/container.rs index d633f19c0cce..96c2e912cb3c 100644 --- a/core/src/display_object/container.rs +++ b/core/src/display_object/container.rs @@ -475,6 +475,22 @@ pub trait TDisplayObjectContainer<'gc>: RenderIter::from_container(self.into()) } + fn fill_tab_order( + &self, + tab_order: &mut Vec>, + context: &mut UpdateContext<'_, 'gc>, + ) { + // TODO Add support for `tabChildren` + for child in self.iter_render_list() { + if child.is_tab_enabled(context) { + tab_order.push(child); + } + if let Some(container) = child.as_container() { + container.fill_tab_order(tab_order, context); + } + } + } + /// Renders the children of this container in render list order. fn render_children(self, context: &mut RenderContext<'_, 'gc>) { let mut clip_depth = 0; diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index ebac836e6438..0de67154f99c 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -180,6 +180,11 @@ pub struct EditTextData<'gc> { /// Restrict what characters the user may input. #[collect(require_static)] restrict: EditTextRestrict, + + // TODO Consider moving this to InteractiveObject along with + // MovieClip's and Button's tab indices after AVM2 analysis + // NOTE: `tabIndex` is u32 in TextField, compared to i32 in Button and MovieClip + tab_index: Option, } impl<'gc> EditTextData<'gc> { @@ -362,6 +367,7 @@ impl<'gc> EditText<'gc> { mouse_wheel_enabled: true, is_tlf: false, restrict: EditTextRestrict::allow_all(), + tab_index: None, }, )); @@ -2028,6 +2034,18 @@ impl<'gc> EditText<'gc> { .contains(Position::from((position.x, position.y))) }) } + + /// Get the value of `tabIndex` used in AS. + /// + /// Do not confuse it with `tab_index`, which returns the value used for ordering. + pub fn tab_index_value(&self) -> Option { + self.0.read().tab_index + } + + /// Set the value of `tabIndex` used in AS. + pub fn set_tab_index_value(&self, context: &mut UpdateContext<'_, 'gc>, value: Option) { + self.0.write(context.gc()).tab_index = value; + } } impl<'gc> TDisplayObject<'gc> for EditText<'gc> { @@ -2328,6 +2346,14 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> { // Even if this isn't selectable or editable, a script can focus on it manually. true } + + fn is_tab_enabled(&self, context: &mut UpdateContext<'_, 'gc>) -> bool { + self.get_avm1_boolean_property(context, "tabEnabled", true) + } + + fn tab_index(&self) -> Option { + self.0.read().tab_index.map(|i| i as i64) + } } impl<'gc> TInteractiveObject<'gc> for EditText<'gc> { diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index 41e5933ea8a4..95bb7b61d494 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -189,6 +189,10 @@ pub struct MovieClipData<'gc> { /// Attached audio (AVM1) attached_audio: Option>, + + // TODO Consider moving this to InteractiveObject along with + // TextField's and Button's tab indices after AVM2 analysis + tab_index: Option, } impl<'gc> MovieClip<'gc> { @@ -226,6 +230,7 @@ impl<'gc> MovieClip<'gc> { tag_frame_boundaries: Default::default(), queued_tags: HashMap::new(), attached_audio: None, + tab_index: None, }, )) } @@ -269,6 +274,7 @@ impl<'gc> MovieClip<'gc> { tag_frame_boundaries: Default::default(), queued_tags: HashMap::new(), attached_audio: None, + tab_index: None, }, )) } @@ -313,6 +319,7 @@ impl<'gc> MovieClip<'gc> { tag_frame_boundaries: Default::default(), queued_tags: HashMap::new(), attached_audio: None, + tab_index: None, }, )) } @@ -382,6 +389,7 @@ impl<'gc> MovieClip<'gc> { tag_frame_boundaries: Default::default(), queued_tags: HashMap::new(), attached_audio: None, + tab_index: None, }, )); @@ -2320,34 +2328,6 @@ impl<'gc> MovieClip<'gc> { Ok(()) } - /// Retrieve a named property from the AVM1 object. - /// - /// This is required as some boolean properties in AVM1 can in fact hold any value. - fn get_avm1_boolean_property( - self, - context: &mut UpdateContext<'_, 'gc>, - name: &'static str, - default: bool, - ) -> bool { - if let Avm1Value::Object(object) = self.object() { - let mut activation = Avm1Activation::from_nothing( - context.reborrow(), - ActivationIdentifier::root("[AVM1 Boolean Property]"), - self.avm1_root(), - ); - if let Ok(value) = object.get(name, &mut activation) { - match value { - Avm1Value::Undefined => default, - _ => value.as_bool(activation.swf_version()), - } - } else { - default - } - } else { - false - } - } - fn enabled(self, context: &mut UpdateContext<'_, 'gc>) -> bool { if !self.movie().is_action_script_3() { self.get_avm1_boolean_property(context, "enabled", true) @@ -2582,6 +2562,18 @@ impl<'gc> MovieClip<'gc> { } } } + + /// Get the value of `tabIndex` used in AS. + /// + /// Do not confuse it with `tab_index`, which returns the value used for ordering. + pub fn tab_index_value(&self) -> Option { + self.0.read().tab_index + } + + /// Set the value of `tabIndex` used in AS. + pub fn set_tab_index_value(&self, context: &mut UpdateContext<'_, 'gc>, value: Option) { + self.0.write(context.gc()).tab_index = value; + } } impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { @@ -3038,6 +3030,17 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { self.0.write(context.gc_context).has_focus = focused; self.call_focus_handler(context, focused, other); } + + fn is_tab_enabled(&self, context: &mut UpdateContext<'_, 'gc>) -> bool { + // TODO The default value here is more complicated. + // It will be true when there's at least one movie clip handler defined. + let default_value = false; + self.get_avm1_boolean_property(context, "tabEnabled", default_value) + } + + fn tab_index(&self) -> Option { + self.0.read().tab_index.map(|i| i as i64) + } } impl<'gc> TDisplayObjectContainer<'gc> for MovieClip<'gc> { diff --git a/core/src/focus_tracker.rs b/core/src/focus_tracker.rs index 98e6c62670bc..020108368fd0 100644 --- a/core/src/focus_tracker.rs +++ b/core/src/focus_tracker.rs @@ -66,4 +66,40 @@ impl<'gc> FocusTracker<'gc> { } } } + + pub fn cycle(&self, context: &mut UpdateContext<'_, 'gc>) { + let stage = context.stage; + let mut tab_order = vec![]; + stage.fill_tab_order(&mut tab_order, context); + + let custom_ordering = tab_order.iter().any(|o| o.tab_index().is_some()); + if custom_ordering { + // Custom ordering disables automatic ordering and + // ignores all objects without tabIndex. + tab_order = tab_order + .iter() + .filter(|o| o.tab_index().is_some()) + .copied() + .collect::>(); + + // Then, items are sorted according to their tab indices. + // TODO When two objects have the same index, the behavior is undefined. + // We should analyze and match FP's behavior here if possible. + tab_order.sort_by_key(|o| o.tab_index()); + } + + let next = if let Some(current_focus) = self.0.get() { + // Find the next object which should take the focus. + tab_order + .iter() + .skip_while(|o| o.as_ptr() != current_focus.as_ptr()) + .nth(1) + .or(tab_order.first()) + } else { + // If no focus is present, we start from the beginning. + tab_order.first() + }; + + self.set(next.copied(), context); + } } diff --git a/core/src/player.rs b/core/src/player.rs index b31cfdbdd46b..ea1772b4f962 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -992,21 +992,11 @@ impl Player { _ => None, }; - let mut key_press_handled = false; - if let Some(button_event) = button_event { - for level in context.stage.iter_render_list() { - let state = if let Some(interactive) = level.as_interactive() { - interactive.handle_clip_event(context, button_event) - } else { - ClipEventResult::NotHandled - }; - - if state == ClipEventResult::Handled { - key_press_handled = true; - break; - } - } - } + let key_press_handled = if let Some(button_event) = button_event { + context.stage.handle_clip_event(context, button_event) == ClipEventResult::Handled + } else { + false + }; if let PlayerEvent::KeyDown { key_code, key_char } | PlayerEvent::KeyUp { key_code, key_char } = event @@ -1189,6 +1179,17 @@ impl Player { self.needs_render = true; } } + + if let PlayerEvent::KeyDown { + key_code: KeyCode::Tab, + .. + } = event + { + self.mutate_with_update_context(|context| { + let tracker = context.focus_tracker; + tracker.cycle(context); + }); + } } /// Update dragged object, if any. diff --git a/desktop/src/gui/controller.rs b/desktop/src/gui/controller.rs index 23f0776fcdfc..d821cfcc91cf 100644 --- a/desktop/src/gui/controller.rs +++ b/desktop/src/gui/controller.rs @@ -17,7 +17,9 @@ use std::time::{Duration, Instant}; use unic_langid::LanguageIdentifier; use url::Url; use winit::dpi::PhysicalSize; +use winit::event::WindowEvent; use winit::event_loop::EventLoop; +use winit::keyboard::{Key, NamedKey}; use winit::window::{Theme, Window}; /// Integration layer connecting wgpu+winit to egui. @@ -140,8 +142,8 @@ impl GuiController { } #[must_use] - pub fn handle_event(&mut self, event: &winit::event::WindowEvent) -> bool { - if let winit::event::WindowEvent::Resized(size) = &event { + pub fn handle_event(&mut self, event: &WindowEvent) -> bool { + if let WindowEvent::Resized(size) = &event { if size.width > 0 && size.height > 0 { self.surface.configure( &self.descriptors.device, @@ -166,7 +168,7 @@ impl GuiController { } } - if let winit::event::WindowEvent::ThemeChanged(theme) = &event { + if let WindowEvent::ThemeChanged(theme) = &event { let visuals = match theme { Theme::Dark => egui::Visuals::dark(), Theme::Light => egui::Visuals::light(), @@ -174,6 +176,20 @@ impl GuiController { self.egui_winit.egui_ctx().set_visuals(visuals); } + if matches!( + &event, + WindowEvent::KeyboardInput { + event: winit::event::KeyEvent { + logical_key: Key::Named(NamedKey::Tab), + .. + }, + .. + } + ) { + // Prevent egui from consuming the Tab key. + return false; + } + let response = self.egui_winit.on_window_event(&self.window, event); if response.repaint { self.window.request_redraw(); diff --git a/tests/tests/swfs/avm1/tab_ordering_automatic_basic/input.json b/tests/tests/swfs/avm1/tab_ordering_automatic_basic/input.json new file mode 100644 index 000000000000..5c9b519f883a --- /dev/null +++ b/tests/tests/swfs/avm1/tab_ordering_automatic_basic/input.json @@ -0,0 +1,25 @@ +[ + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 27 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 27 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 27 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 } +] diff --git a/tests/tests/swfs/avm1/tab_ordering_automatic_basic/output.txt b/tests/tests/swfs/avm1/tab_ordering_automatic_basic/output.txt new file mode 100644 index 000000000000..0b213048ecae --- /dev/null +++ b/tests/tests/swfs/avm1/tab_ordering_automatic_basic/output.txt @@ -0,0 +1,92 @@ +Focus changed + old: null + new: _level0.text1 +Tab pressed +Focus changed + old: _level0.text1 + new: _level0.text2 +Tab pressed +Focus changed + old: _level0.text2 + new: _level0.text3 +Tab pressed +Focus changed + old: _level0.text3 + new: _level0.text4 +Tab pressed +Focus changed + old: _level0.text4 + new: _level0.text1 +Tab pressed +Focus changed + old: _level0.text1 + new: _level0.text2 +Escape pressed, moving to stage 1 +Focus changed + old: _level0.text2 + new: _level0.text4 +Tab pressed +Focus changed + old: _level0.text4 + new: _level0.text1 +Tab pressed +Focus changed + old: _level0.text1 + new: _level0.text2 +Tab pressed +Focus changed + old: _level0.text2 + new: _level0.text3 +Tab pressed +Focus changed + old: _level0.text3 + new: _level0.text4 +Tab pressed +Focus changed + old: _level0.text4 + new: _level0.text1 +Escape pressed, moving to stage 2 +Tab pressed +Focus changed + old: _level0.text1 + new: _level0.text3 +Tab pressed +Focus changed + old: _level0.text3 + new: _level0.text4 +Tab pressed +Focus changed + old: _level0.text4 + new: _level0.text1 +Tab pressed +Focus changed + old: _level0.text1 + new: _level0.text3 +Tab pressed +Focus changed + old: _level0.text3 + new: _level0.text4 +Escape pressed, moving to stage 3 +Focus changed + old: _level0.text4 + new: _level0.text1 +Tab pressed +Focus changed + old: _level0.text1 + new: _level0.text2 +Tab pressed +Focus changed + old: _level0.text2 + new: _level0.text3 +Tab pressed +Focus changed + old: _level0.text3 + new: _level0.text4 +Tab pressed +Focus changed + old: _level0.text4 + new: _level0.text2 +Tab pressed +Focus changed + old: _level0.text2 + new: _level0.text3 diff --git a/tests/tests/swfs/avm1/tab_ordering_automatic_basic/test.as b/tests/tests/swfs/avm1/tab_ordering_automatic_basic/test.as new file mode 100644 index 000000000000..1d9fc284a7a9 --- /dev/null +++ b/tests/tests/swfs/avm1/tab_ordering_automatic_basic/test.as @@ -0,0 +1,42 @@ +var listener = new Object(); +listener.onSetFocus = function(oldFocus, newFocus) { + if (newFocus) { + trace("Focus changed"); + trace(" old: " + oldFocus); + trace(" new: " + newFocus); + } +}; +Selection.addListener(listener); + +var testStage = 0; +function setUpStage(stage) { + if (stage == 0) { + // already set up + } + if (stage == 1) { + Selection.setFocus(text4); + } + if (stage == 2) { + Selection.setFocus(text1); + text2.tabEnabled = false; + } + if (stage == 3) { + Selection.setFocus(text1); + text1.tabEnabled = false; + text2.tabEnabled = true; + } +} + +var listener = new Object(); +listener.onKeyDown = function() { + if (Key.getCode() == 27) { + testStage += 1; + trace("Escape pressed, moving to stage " + testStage); + setUpStage(testStage); + } else if (Key.getCode() == 9) { + trace("Tab pressed"); + } +}; +Key.addListener(listener); + +Selection.setFocus(text1); diff --git a/tests/tests/swfs/avm1/tab_ordering_automatic_basic/test.swf b/tests/tests/swfs/avm1/tab_ordering_automatic_basic/test.swf new file mode 100644 index 000000000000..358e00590e04 Binary files /dev/null and b/tests/tests/swfs/avm1/tab_ordering_automatic_basic/test.swf differ diff --git a/tests/tests/swfs/avm1/tab_ordering_automatic_basic/test.toml b/tests/tests/swfs/avm1/tab_ordering_automatic_basic/test.toml new file mode 100644 index 000000000000..cf6123969a1d --- /dev/null +++ b/tests/tests/swfs/avm1/tab_ordering_automatic_basic/test.toml @@ -0,0 +1 @@ +num_ticks = 1 diff --git a/tests/tests/swfs/avm1/tab_ordering_custom_basic/input.json b/tests/tests/swfs/avm1/tab_ordering_custom_basic/input.json new file mode 100644 index 000000000000..7c7d8c3fbc71 --- /dev/null +++ b/tests/tests/swfs/avm1/tab_ordering_custom_basic/input.json @@ -0,0 +1,19 @@ +[ + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 27 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 }, + { "type": "KeyDown", "key_code": 9 } +] diff --git a/tests/tests/swfs/avm1/tab_ordering_custom_basic/output.txt b/tests/tests/swfs/avm1/tab_ordering_custom_basic/output.txt new file mode 100644 index 000000000000..38954ddd8dc6 --- /dev/null +++ b/tests/tests/swfs/avm1/tab_ordering_custom_basic/output.txt @@ -0,0 +1,71 @@ +Focus changed + old: null + new: _level0.text1 +Tab pressed +Focus changed + old: _level0.text1 + new: _level0.text3 +Tab pressed +Focus changed + old: _level0.text3 + new: _level0.text2 +Tab pressed +Focus changed + old: _level0.text2 + new: _level0.text5 +Tab pressed +Focus changed + old: _level0.text5 + new: _level0.text6 +Tab pressed +Focus changed + old: _level0.text6 + new: _level0.text3 +Tab pressed +Focus changed + old: _level0.text3 + new: _level0.text2 +Tab pressed +Focus changed + old: _level0.text2 + new: _level0.text5 +Tab pressed +Focus changed + old: _level0.text5 + new: _level0.text6 +Escape pressed +Focus changed + old: _level0.text6 + new: _level0.text5 +Tab pressed +Focus changed + old: _level0.text5 + new: _level0.text6 +Tab pressed +Focus changed + old: _level0.text6 + new: _level0.text3 +Tab pressed +Focus changed + old: _level0.text3 + new: _level0.text2 +Tab pressed +Focus changed + old: _level0.text2 + new: _level0.text5 +Tab pressed +Focus changed + old: _level0.text5 + new: _level0.text6 +Tab pressed +Focus changed + old: _level0.text6 + new: _level0.text3 +Tab pressed +Focus changed + old: _level0.text3 + new: _level0.text2 +Tab pressed +Focus changed + old: _level0.text2 + new: _level0.text5 diff --git a/tests/tests/swfs/avm1/tab_ordering_custom_basic/test.as b/tests/tests/swfs/avm1/tab_ordering_custom_basic/test.as new file mode 100644 index 000000000000..0ddc33899830 --- /dev/null +++ b/tests/tests/swfs/avm1/tab_ordering_custom_basic/test.as @@ -0,0 +1,29 @@ +var listener = new Object(); +listener.onSetFocus = function(oldFocus, newFocus) { + if (newFocus) { + trace("Focus changed"); + trace(" old: " + oldFocus); + trace(" new: " + newFocus); + } +}; +Selection.addListener(listener); + +var listener = new Object(); +listener.onKeyDown = function() { + if (Key.getCode() == 27) { + trace("Escape pressed"); + Selection.setFocus(text5); + } else if (Key.getCode() == 9) { + trace("Tab pressed"); + } +}; +Key.addListener(listener); + +text2.tabIndex = 2; +text3.tabIndex = 1; +text4.tabIndex = 4; +text4.tabEnabled = false; +text5.tabIndex = 3; +text6.tabIndex = 5; + +Selection.setFocus(text1); diff --git a/tests/tests/swfs/avm1/tab_ordering_custom_basic/test.swf b/tests/tests/swfs/avm1/tab_ordering_custom_basic/test.swf new file mode 100644 index 000000000000..dc0d133e9012 Binary files /dev/null and b/tests/tests/swfs/avm1/tab_ordering_custom_basic/test.swf differ diff --git a/tests/tests/swfs/avm1/tab_ordering_custom_basic/test.toml b/tests/tests/swfs/avm1/tab_ordering_custom_basic/test.toml new file mode 100644 index 000000000000..cf6123969a1d --- /dev/null +++ b/tests/tests/swfs/avm1/tab_ordering_custom_basic/test.toml @@ -0,0 +1 @@ +num_ticks = 1 diff --git a/tests/tests/swfs/avm1/tab_ordering_properties/output.txt b/tests/tests/swfs/avm1/tab_ordering_properties/output.txt new file mode 100644 index 000000000000..fad8532031ac --- /dev/null +++ b/tests/tests/swfs/avm1/tab_ordering_properties/output.txt @@ -0,0 +1,217 @@ +===== text ===== + default + tabEnabled = undefined + tabIndex = undefined + tabChildren = undefined + enumerated tabIndex + after set 1 + tabEnabled = true + tabIndex = 0 + tabChildren = true + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 2 + tabEnabled = false + tabIndex = 4 + tabChildren = false + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 3 + tabEnabled = undefined + tabIndex = undefined + tabChildren = undefined + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 4 + tabEnabled = -4 + tabIndex = 4294967292 + tabChildren = -4 + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 5 + tabEnabled = 2147483647 + tabIndex = 2147483647 + tabChildren = 2147483647 + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 6 + tabEnabled = 2147483648 + tabIndex = 2147483648 + tabChildren = 2147483648 + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 7 + tabEnabled = x + tabIndex = 0 + tabChildren = x + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 8 + tabEnabled = -2147483648 + tabIndex = 2147483648 + tabChildren = -2147483648 + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 9 + tabEnabled = [object Object] + tabIndex = 0 + tabChildren = [object Object] + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 10 + tabEnabled = 1.1 + tabIndex = 1 + tabChildren = 1.1 + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren +===== button ===== + default + tabEnabled = undefined + tabIndex = undefined + tabChildren = undefined + enumerated tabIndex + after set 1 + tabEnabled = true + tabIndex = 0 + tabChildren = true + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 2 + tabEnabled = false + tabIndex = 4 + tabChildren = false + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 3 + tabEnabled = undefined + tabIndex = undefined + tabChildren = undefined + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 4 + tabEnabled = -4 + tabIndex = -4 + tabChildren = -4 + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 5 + tabEnabled = 2147483647 + tabIndex = 2147483647 + tabChildren = 2147483647 + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 6 + tabEnabled = 2147483648 + tabIndex = -2147483648 + tabChildren = 2147483648 + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 7 + tabEnabled = x + tabIndex = -2147483648 + tabChildren = x + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 8 + tabEnabled = -2147483648 + tabIndex = -2147483648 + tabChildren = -2147483648 + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 9 + tabEnabled = [object Object] + tabIndex = -2147483648 + tabChildren = [object Object] + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren + after set 10 + tabEnabled = 1.1 + tabIndex = 1 + tabChildren = 1.1 + enumerated tabEnabled + enumerated tabIndex + enumerated tabChildren +===== movie clip ===== + default + tabEnabled = undefined + tabIndex = undefined + tabChildren = undefined + after set 1 + tabEnabled = true + tabIndex = 0 + tabChildren = true + enumerated tabEnabled + enumerated tabChildren + after set 2 + tabEnabled = false + tabIndex = 4 + tabChildren = false + enumerated tabEnabled + enumerated tabChildren + after set 3 + tabEnabled = undefined + tabIndex = undefined + tabChildren = undefined + enumerated tabEnabled + enumerated tabChildren + after set 4 + tabEnabled = -4 + tabIndex = -4 + tabChildren = -4 + enumerated tabEnabled + enumerated tabChildren + after set 5 + tabEnabled = 2147483647 + tabIndex = 2147483647 + tabChildren = 2147483647 + enumerated tabEnabled + enumerated tabChildren + after set 6 + tabEnabled = 2147483648 + tabIndex = -2147483648 + tabChildren = 2147483648 + enumerated tabEnabled + enumerated tabChildren + after set 7 + tabEnabled = x + tabIndex = -2147483648 + tabChildren = x + enumerated tabEnabled + enumerated tabChildren + after set 8 + tabEnabled = -2147483648 + tabIndex = -2147483648 + tabChildren = -2147483648 + enumerated tabEnabled + enumerated tabChildren + after set 9 + tabEnabled = [object Object] + tabIndex = -2147483648 + tabChildren = [object Object] + enumerated tabEnabled + enumerated tabChildren + after set 10 + tabEnabled = 1.1 + tabIndex = 1 + tabChildren = 1.1 + enumerated tabEnabled + enumerated tabChildren diff --git a/tests/tests/swfs/avm1/tab_ordering_properties/test.as b/tests/tests/swfs/avm1/tab_ordering_properties/test.as new file mode 100644 index 000000000000..c5cc27f57cd3 --- /dev/null +++ b/tests/tests/swfs/avm1/tab_ordering_properties/test.as @@ -0,0 +1,102 @@ +function printProperties(obj) { + trace(' tabEnabled = ' + obj.tabEnabled); + trace(' tabIndex = ' + obj.tabIndex); + trace(' tabChildren = ' + obj.tabChildren); + for (var i in obj) { + if (i == 'tabEnabled') { + trace(' enumerated tabEnabled'); + } + } + for (var i in obj) { + if (i == 'tabIndex') { + trace(' enumerated tabIndex'); + } + } + for (var i in obj) { + if (i == 'tabChildren') { + trace(' enumerated tabChildren'); + } + } +} + +function testProperties(obj) { + trace(' default'); + printProperties(obj); + + obj.tabEnabled = true; + obj.tabIndex = 0; + obj.tabChildren = true; + + trace(' after set 1'); + printProperties(obj); + + obj.tabEnabled = false; + obj.tabIndex = 4; + obj.tabChildren = false; + + trace(' after set 2'); + printProperties(obj); + + obj.tabEnabled = undefined; + obj.tabIndex = undefined; + obj.tabChildren = undefined; + + trace(' after set 3'); + printProperties(obj); + + obj.tabEnabled = -4; + obj.tabIndex = -4; + obj.tabChildren = -4; + + trace(' after set 4'); + printProperties(obj); + + obj.tabEnabled = 2147483647; + obj.tabIndex = 2147483647; + obj.tabChildren = 2147483647; + + trace(' after set 5'); + printProperties(obj); + + obj.tabEnabled = 2147483648; + obj.tabIndex = 2147483648; + obj.tabChildren = 2147483648; + + trace(' after set 6'); + printProperties(obj); + + obj.tabEnabled = 'x'; + obj.tabIndex = 'x'; + obj.tabChildren = 'x'; + + trace(' after set 7'); + printProperties(obj); + + obj.tabEnabled = -2147483648; + obj.tabIndex = -2147483648; + obj.tabChildren = -2147483648; + + trace(' after set 8'); + printProperties(obj); + + obj.tabEnabled = new Object(); + obj.tabIndex = new Object(); + obj.tabChildren = new Object(); + + trace(' after set 9'); + printProperties(obj); + + obj.tabEnabled = 1.1; + obj.tabIndex = 1.1; + obj.tabChildren = 1.1; + + trace(' after set 10'); + printProperties(obj); +} + +trace('===== text ====='); +testProperties(text); +trace('===== button ====='); +testProperties(button); +trace('===== movie clip ====='); +testProperties(_root); diff --git a/tests/tests/swfs/avm1/tab_ordering_properties/test.swf b/tests/tests/swfs/avm1/tab_ordering_properties/test.swf new file mode 100644 index 000000000000..0ca6be93745f Binary files /dev/null and b/tests/tests/swfs/avm1/tab_ordering_properties/test.swf differ diff --git a/tests/tests/swfs/avm1/tab_ordering_properties/test.toml b/tests/tests/swfs/avm1/tab_ordering_properties/test.toml new file mode 100644 index 000000000000..cf6123969a1d --- /dev/null +++ b/tests/tests/swfs/avm1/tab_ordering_properties/test.toml @@ -0,0 +1 @@ +num_ticks = 1 diff --git a/tests/tests/swfs/avm1/tab_ordering_properties_tab_index_edge_case/output.txt b/tests/tests/swfs/avm1/tab_ordering_properties_tab_index_edge_case/output.txt new file mode 100644 index 000000000000..5d7586fde5e3 --- /dev/null +++ b/tests/tests/swfs/avm1/tab_ordering_properties_tab_index_edge_case/output.txt @@ -0,0 +1,4 @@ +asdf +asdf2 +5 +asdf3 diff --git a/tests/tests/swfs/avm1/tab_ordering_properties_tab_index_edge_case/test.as b/tests/tests/swfs/avm1/tab_ordering_properties_tab_index_edge_case/test.as new file mode 100644 index 000000000000..776d0118e4f7 --- /dev/null +++ b/tests/tests/swfs/avm1/tab_ordering_properties_tab_index_edge_case/test.as @@ -0,0 +1,8 @@ +this.tabIndex = "asdf"; +trace(this.tabIndex); +this.tabIndex = "asdf2"; +trace(this.tabIndex); +this.tabIndex = 5; +trace(this.tabIndex); +this.tabIndex = "asdf3"; +trace(this.tabIndex); diff --git a/tests/tests/swfs/avm1/tab_ordering_properties_tab_index_edge_case/test.swf b/tests/tests/swfs/avm1/tab_ordering_properties_tab_index_edge_case/test.swf new file mode 100644 index 000000000000..449492534a69 Binary files /dev/null and b/tests/tests/swfs/avm1/tab_ordering_properties_tab_index_edge_case/test.swf differ diff --git a/tests/tests/swfs/avm1/tab_ordering_properties_tab_index_edge_case/test.toml b/tests/tests/swfs/avm1/tab_ordering_properties_tab_index_edge_case/test.toml new file mode 100644 index 000000000000..29f3cef79022 --- /dev/null +++ b/tests/tests/swfs/avm1/tab_ordering_properties_tab_index_edge_case/test.toml @@ -0,0 +1,2 @@ +num_ticks = 1 +known_failure = true