Subject: | Infinite loop in focus_next (patch provided) |
Date: | Sun, 19 Jul 2020 23:42:07 -0400 |
To: | bug-Tickit-Widgets [...] rt.cpan.org |
From: | Jason Healy <jhealy [...] logn.net> |
Hello,
In a test application I encountered a bug where hitting Tab on the last entry in a VBox (or S-Tab in the first entry) caused the program to lock up in an infinite loop. After some analysis, I think I've located the bug and have included a patch below.
Specifics: my app had a root-level VBox with 3 nested VBoxes inside it (call them A, B, C). The first and last (A and C) only contained Static (no focusable elements); only the middle one (B) contained focusable Entries. When the last Entry in B had the focus and Tab was pressed, the bug would happen. It would also happen if the first Entry in B had S-Tab pressed.
Using the "last Entry in B" example, pressing Tab causes find_next to be invoked on the Entry's parent (B). B had no other focusable children, so find_next moved up to the parent (root VBox).
The root VBox would not find any focusable items in its next child (C), nor does it have a parent, so it would reset to "first" and cycle the tree again. It tries A as its next element, but in this case, A has no focusable items, so the search must continue. However, because the A was the "first" search, it simply searches "first" again in an infinite loop. The code assumes that you will find a focusable element in the first child of a parent, but that is not necessarily the case.
I think this only occurs when there is a nested structure and the "first" or "last" item contains no focusable items. I added a quick check to turn "first" and "last" back into "before" and "after" if a child search fails. That resolved the issue in my test setup, but I have not tested it against any more complex widget trees.
Patched code is only 2 lines; please review and let me know if you have questions.
Thanks,
Jason
--- /opt/local/lib/perl5/site_perl/5.30/Tickit/ContainerWidget.pm-orig 2020-07-15 22:19:19.000000000 -0400
+++ /opt/local/lib/perl5/site_perl/5.30/Tickit/ContainerWidget.pm 2020-07-19 23:32:59.000000000 -0400
@@ -377,6 +377,11 @@
# See if child has it
return 1 if $next->focus_next( $childhow => undef );
+ # If this was our first/last (meaning we wrapped), convert to
+ # a standard before/after
+ if( $how eq "first" ) { $how = "after" }
+ elsif ( $how eq "last" ) { $how = "before" }
+
$other = $next;
redo;
}