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