Refire event while it is delivered is evil - always?!

Jeanette Winzenburg fastegal at swingempire.de
Thu Oct 17 11:52:49 UTC 2019


While fixing https://bugs.openjdk.java.net/browse/JDK-8207759 it  
turned out that the underlying reason for the bug was a broken event  
dispatch sequence introduced by behavior.forwardToParent. Which is a  
call to parent.fireEvent with the event that was received. This builds  
a nested chain and delivers  the event to all handlers in that new  
chain - down and up again - _before_ the current chain is completed.  
Consequently, the consuming singleton handler for the same event is  
notified _after_ the scene-level handlers (in particular the  
accelerators) have seen and handled it.

Looks like it happens for any control (not only for a TextField as in  
the referenced issue, nor only for controls with a FakeFocusTextField  
which refire while processing keyEvents), as the example below  
demonstrates.

My current understanding of event dispatch is, that a  chain has a  
life-cycle consisting of separate (?) states:

- building the chain from eventTargets
- delivering the event along the dispatchers in the chain

There's a contract for dispatch sequence (like  capturing/bubbling  
phase, dispatch from specialized to super event types and other  
rules). That can be guaranteed as long as chain building and event  
delivering are separate phases, it seems to break down if they are  
mixed (there are other issues with a similar/same underlying reason,  
f.i. in all controls with a FakeFocusTextField).

Now the questions:

- is there any specification about not mixing the life-cycle states?  
if so, where?
- or is there a way to safely re-fire an event at the moment of receiving it?
- or maybe I got it all wrong, if so please guide me :)

-- Cheers, Jeanette


The example:

public class NestedEventDispatchChain extends Application {


     private KeyCode key = DIGIT1;
     private Parent createContent() {
         Button button = new Button("the evil button!");
         // re-firing handler
         button.addEventHandler(KEY_PRESSED, e -> {
             if (key == e.getCode()) {
                 System.out.println("before refire " + e);
                 button.getParent().fireEvent(e);
                 System.out.println("after refire " + e);
             }
         });

         // consuming singleton handler
         button.setOnKeyPressed(e -> {
             if (key == e.getCode()) {
                 e.consume();
                 System.out.println("consumed in singleton " + e.getCode());
             }
          });
         BorderPane content = new BorderPane(button);
         return content;
     }

     @Override
     public void start(Stage stage) throws Exception {
         Scene scene = new Scene(createContent());
         // accelerator that shouldn't be triggered because singleton  
handler consumed
          
scene.getAccelerators().put(KeyCombination.keyCombination(key.getName()), ()  
-> {
             System.out.println("accelerator triggered for " + key);
         });
         stage.setScene(scene);
         stage.show();
     }

     public static void main(String[] args) {
         launch(args);
     }
}




More information about the openjfx-dev mailing list