Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

avm2: Ensure the focus is dropped when it's removed #16231

Merged
merged 5 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions core/src/display_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1587,10 +1587,25 @@ pub trait TDisplayObject<'gc>:

/// Set the parent of this display object.
fn set_parent(&self, context: &mut UpdateContext<'_, 'gc>, parent: Option<DisplayObject<'gc>>) {
let had_parent = self.parent().is_some();
self.base_mut(context.gc_context)
.set_parent_ignoring_orphan_list(parent);
let has_parent = self.parent().is_some();
let parent_removed = had_parent && !has_parent;

if parent_removed {
if let Some(int) = self.as_interactive() {
int.drop_focus(context);
}

self.on_parent_removed(context);
}
}

/// This method is called when the parent is removed.
/// It may be overwritten to inject some implementation-specific behavior.
fn on_parent_removed(&self, _context: &mut UpdateContext<'_, 'gc>) {}

/// Retrieve the parent of this display object.
///
/// This version of the function implements the concept of parenthood as
Expand Down
19 changes: 2 additions & 17 deletions core/src/display_object/avm1_button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ pub struct Avm1ButtonData<'gc> {
tracking: Cell<ButtonTracking>,
object: Lock<Option<Object<'gc>>>,
initialized: Cell<bool>,
has_focus: Cell<bool>,
}

#[derive(Clone, Collect)]
Expand Down Expand Up @@ -98,7 +97,6 @@ impl<'gc> Avm1Button<'gc> {
} else {
ButtonTracking::Push
}),
has_focus: Cell::new(false),
},
))
}
Expand Down Expand Up @@ -393,11 +391,7 @@ impl<'gc> TDisplayObject<'gc> for Avm1Button<'gc> {
}

fn avm1_unload(&self, context: &mut UpdateContext<'_, 'gc>) {
let had_focus = self.0.has_focus.get();
if had_focus {
let tracker = context.focus_tracker;
tracker.set(None, context);
}
self.drop_focus(context);
if let Some(node) = self.maskee() {
node.set_masker(context.gc(), None, true);
} else if let Some(node) = self.masker() {
Expand Down Expand Up @@ -530,7 +524,7 @@ impl<'gc> TInteractiveObject<'gc> for Avm1Button<'gc> {
if let Some(name) = event.method_name() {
// Keyboard events don't fire their methods
// unless the Button has focus (like for MovieClips).
if !event.is_key_event() || self.0.has_focus.get() {
if !event.is_key_event() || self.has_focus() {
context.action_queue.queue_action(
self_display_object,
ActionType::Method {
Expand Down Expand Up @@ -608,15 +602,6 @@ impl<'gc> TInteractiveObject<'gc> for Avm1Button<'gc> {
}
}

fn on_focus_changed(
&self,
_context: &mut UpdateContext<'_, 'gc>,
focused: bool,
_other: Option<InteractiveObject<'gc>>,
) {
self.0.has_focus.set(focused);
}

fn tab_enabled_avm1(&self, context: &mut UpdateContext<'_, 'gc>) -> bool {
self.get_avm1_boolean_property(context, "tabEnabled", |_| true)
}
Expand Down
11 changes: 0 additions & 11 deletions core/src/display_object/avm2_button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ pub struct Avm2ButtonData<'gc> {
/// The AVM2 representation of this button.
object: Lock<Option<Avm2Object<'gc>>>,

has_focus: Cell<bool>,
enabled: Cell<bool>,
use_hand_cursor: Cell<bool>,

Expand Down Expand Up @@ -135,7 +134,6 @@ impl<'gc> Avm2Button<'gc> {
} else {
ButtonTracking::Push
}),
has_focus: Cell::new(false),
enabled: Cell::new(true),
use_hand_cursor: Cell::new(true),
skip_current_frame: Cell::new(false),
Expand Down Expand Up @@ -823,15 +821,6 @@ impl<'gc> TInteractiveObject<'gc> for Avm2Button<'gc> {
}
}

fn on_focus_changed(
&self,
_context: &mut UpdateContext<'_, 'gc>,
focused: bool,
_other: Option<InteractiveObject<'gc>>,
) {
self.0.has_focus.set(focused);
}

fn tab_enabled_avm2_default(&self, _context: &mut UpdateContext<'_, 'gc>) -> bool {
true
}
Expand Down
34 changes: 6 additions & 28 deletions core/src/display_object/edit_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,7 @@ impl<'gc> EditText<'gc> {
..Default::default()
});

let visible_selection = if edit_text.flags.contains(EditTextFlag::HAS_FOCUS) {
let visible_selection = if self.has_focus() {
edit_text.selection
} else {
None
Expand Down Expand Up @@ -2114,21 +2114,6 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
self.0.write(context.gc_context).object = Some(to.into());
}

fn set_parent(&self, context: &mut UpdateContext<'_, 'gc>, parent: Option<DisplayObject<'gc>>) {
let had_parent = self.parent().is_some();
self.base_mut(context.gc_context)
.set_parent_ignoring_orphan_list(parent);
let has_parent = self.parent().is_some();

if self.movie().is_action_script_3() && had_parent && !has_parent {
let had_focus = self.0.read().flags.contains(EditTextFlag::HAS_FOCUS);
if had_focus {
let tracker = context.focus_tracker;
tracker.set(None, context);
}
}
}

fn self_bounds(&self) -> Rectangle<Twips> {
self.0.read().bounds.clone()
}
Expand Down Expand Up @@ -2237,7 +2222,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
});

if edit_text.layout.is_empty() && !edit_text.flags.contains(EditTextFlag::READ_ONLY) {
let visible_selection = if edit_text.flags.contains(EditTextFlag::HAS_FOCUS) {
let visible_selection = if self.has_focus() {
edit_text.selection
} else {
None
Expand Down Expand Up @@ -2275,11 +2260,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
}

fn avm1_unload(&self, context: &mut UpdateContext<'_, 'gc>) {
let had_focus = self.0.read().flags.contains(EditTextFlag::HAS_FOCUS);
if had_focus {
let tracker = context.focus_tracker;
tracker.set(None, context);
}
self.drop_focus(context);

if let Some(node) = self.maskee() {
node.set_masker(context.gc_context, None, true);
Expand Down Expand Up @@ -2459,11 +2440,9 @@ impl<'gc> TInteractiveObject<'gc> for EditText<'gc> {
focused: bool,
_other: Option<InteractiveObject<'gc>>,
) {
let is_action_script_3 = self.movie().is_action_script_3();
let mut text = self.0.write(context.gc_context);
text.flags.set(EditTextFlag::HAS_FOCUS, focused);
if !focused && !is_action_script_3 {
text.selection = None;
let is_avm1 = !self.movie().is_action_script_3();
if !focused && is_avm1 {
self.0.write(context.gc_context).selection = None;
}
}

Expand Down Expand Up @@ -2494,7 +2473,6 @@ bitflags::bitflags! {
struct EditTextFlag: u16 {
const FIRING_VARIABLE_BINDING = 1 << 0;
const HAS_BACKGROUND = 1 << 1;
const HAS_FOCUS = 1 << 2;

// The following bits need to match `swf::EditTextFlag`.
const READ_ONLY = 1 << 3;
Expand Down
23 changes: 23 additions & 0 deletions core/src/display_object/interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ bitflags! {

/// Whether this `InteractiveObject` accepts double-clicks.
const DOUBLE_CLICK_ENABLED = 1 << 1;

/// Whether this `InteractiveObject` is currently focused.
const HAS_FOCUS = 1 << 2;
}
}

Expand Down Expand Up @@ -167,6 +170,18 @@ pub trait TInteractiveObject<'gc>:
.set(InteractiveObjectFlags::DOUBLE_CLICK_ENABLED, value)
}

fn has_focus(self) -> bool {
self.raw_interactive()
.flags
.contains(InteractiveObjectFlags::HAS_FOCUS)
}

fn set_has_focus(self, mc: &Mutation<'gc>, value: bool) {
self.raw_interactive_mut(mc)
.flags
.set(InteractiveObjectFlags::HAS_FOCUS, value)
}

fn context_menu(self) -> Avm2Value<'gc> {
self.raw_interactive().context_menu
}
Expand Down Expand Up @@ -536,6 +551,14 @@ pub trait TInteractiveObject<'gc>:
) {
}

/// If this object has focus, this method drops it.
fn drop_focus(&self, context: &mut UpdateContext<'_, 'gc>) {
if self.has_focus() {
let tracker = context.focus_tracker;
tracker.set(None, context);
}
}

fn call_focus_handler(
&self,
context: &mut UpdateContext<'_, 'gc>,
Expand Down
9 changes: 2 additions & 7 deletions core/src/display_object/loader_display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,8 @@ impl<'gc> TDisplayObject<'gc> for LoaderDisplay<'gc> {
self.0.read().movie.clone()
}

fn set_parent(&self, context: &mut UpdateContext<'_, 'gc>, parent: Option<DisplayObject<'gc>>) {
let had_parent = self.parent().is_some();
self.base_mut(context.gc_context)
.set_parent_ignoring_orphan_list(parent);
let has_parent = self.parent().is_some();

if self.movie().is_action_script_3() && had_parent && !has_parent {
fn on_parent_removed(&self, context: &mut UpdateContext<'_, 'gc>) {
if self.movie().is_action_script_3() {
context.avm2.add_orphan_obj((*self).into())
}
}
Expand Down
31 changes: 4 additions & 27 deletions core/src/display_object/movie_clip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ pub struct MovieClipData<'gc> {
avm2_class: Option<Avm2ClassObject<'gc>>,
#[collect(require_static)]
drawing: Drawing,
has_focus: bool,
avm2_enabled: bool,

/// Show a hand cursor when the clip is in button mode.
Expand Down Expand Up @@ -212,7 +211,6 @@ impl<'gc> MovieClip<'gc> {
flags: MovieClipFlags::empty(),
avm2_class: None,
drawing: Drawing::new(),
has_focus: false,
avm2_enabled: true,
avm2_use_hand_cursor: true,
button_mode: false,
Expand Down Expand Up @@ -255,7 +253,6 @@ impl<'gc> MovieClip<'gc> {
flags: MovieClipFlags::empty(),
avm2_class: Some(class),
drawing: Drawing::new(),
has_focus: false,
avm2_enabled: true,
avm2_use_hand_cursor: true,
button_mode: false,
Expand Down Expand Up @@ -299,7 +296,6 @@ impl<'gc> MovieClip<'gc> {
flags: MovieClipFlags::PLAYING,
avm2_class: None,
drawing: Drawing::new(),
has_focus: false,
avm2_enabled: true,
avm2_use_hand_cursor: true,
button_mode: false,
Expand Down Expand Up @@ -368,7 +364,6 @@ impl<'gc> MovieClip<'gc> {
flags: MovieClipFlags::PLAYING,
avm2_class: None,
drawing: Drawing::new(),
has_focus: false,
avm2_enabled: true,
avm2_use_hand_cursor: true,
button_mode: false,
Expand Down Expand Up @@ -2933,13 +2928,8 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
}
}

fn set_parent(&self, context: &mut UpdateContext<'_, 'gc>, parent: Option<DisplayObject<'gc>>) {
let had_parent = self.parent().is_some();
self.base_mut(context.gc_context)
.set_parent_ignoring_orphan_list(parent);
let has_parent = self.parent().is_some();

if self.movie().is_action_script_3() && had_parent && !has_parent {
fn on_parent_removed(&self, context: &mut UpdateContext<'_, 'gc>) {
if self.movie().is_action_script_3() {
context.avm2.add_orphan_obj((*self).into())
}
}
Expand All @@ -2962,11 +2952,7 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
}
}

let had_focus = self.0.read().has_focus;
if had_focus {
let tracker = context.focus_tracker;
tracker.set(None, context);
}
self.drop_focus(context);

{
let mut mc = self.0.write(context.gc_context);
Expand Down Expand Up @@ -3101,7 +3087,7 @@ impl<'gc> TInteractiveObject<'gc> for MovieClip<'gc> {
if swf_version >= 6 {
if let Some(name) = event.method_name() {
// Keyboard events don't fire their methods unless the MovieClip has focus (#2120).
if !event.is_key_event() || read.has_focus {
if !event.is_key_event() || self.has_focus() {
context.action_queue.queue_action(
self.into(),
ActionType::Method {
Expand Down Expand Up @@ -3386,15 +3372,6 @@ impl<'gc> TInteractiveObject<'gc> for MovieClip<'gc> {
}
}

fn on_focus_changed(
&self,
context: &mut UpdateContext<'_, 'gc>,
focused: bool,
_other: Option<InteractiveObject<'gc>>,
) {
self.0.write(context.gc_context).has_focus = focused;
}

fn tab_enabled_avm1(&self, context: &mut UpdateContext<'_, 'gc>) -> bool {
self.get_avm1_boolean_property(context, "tabEnabled", |context| {
self.tab_index().is_some() || self.is_button_mode(context)
Expand Down
2 changes: 2 additions & 0 deletions core/src/focus_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,12 @@ impl<'gc> FocusTracker<'gc> {
self.update_highlight(context);

if let Some(old) = old {
old.set_has_focus(context.gc(), false);
old.on_focus_changed(context, false, new);
old.call_focus_handler(context, false, new);
}
if let Some(new) = new {
new.set_has_focus(context.gc(), true);
new.on_focus_changed(context, true, old);
new.call_focus_handler(context, true, old);
}
Expand Down
24 changes: 24 additions & 0 deletions tests/tests/swfs/avm1/focus_remove/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
===== clip
Object: _level0.clip
Focus changed
old: null
new: _level0.clip
Focus: _level0.clip
Focus: null
Focus: null
===== text
Object: _level0.clip.text
Focus changed
old: null
new: _level0.clip.text
Focus: _level0.clip.text
Focus: null
Focus: null
===== button
Object: _level0.clip.button
Focus changed
old: null
new: _level0.clip.button
Focus: _level0.clip.button
Focus: null
Focus: null
Loading