Skip to content

Commit c3257fc

Browse files
author
Jose Pereda
committed
8159048: Animation and AnimationTimer methods must be called on JavaFX Application thread
Reviewed-by: angorya, kcr
1 parent a5183e5 commit c3257fc

File tree

4 files changed

+266
-13
lines changed

4 files changed

+266
-13
lines changed

Diff for: modules/javafx.graphics/src/main/java/javafx/animation/Animation.java

+20-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2010, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -83,7 +83,8 @@
8383
* the {@code Animation} from the specified position.
8484
* <p>
8585
* Inverting the value of {@link #rateProperty() rate} toggles the play direction.
86-
*
86+
* <p>
87+
* The animation runs on the JavaFX Application Thread.
8788
* @see Timeline
8889
* @see Transition
8990
*
@@ -978,12 +979,15 @@ public void playFromStart() {
978979
* Note: <ul>
979980
* <li>{@code play()} is an asynchronous call, the {@code Animation} may not
980981
* start immediately. </ul>
982+
* <p>
983+
* This method must be called on the JavaFX Application thread.
981984
*
982-
* @throws IllegalStateException
983-
* if embedded in another animation,
985+
* @throws IllegalStateException if this method is called on a thread
986+
* other than the JavaFX Application Thread, or if embedded in another animation,
984987
* such as {@link SequentialTransition} or {@link ParallelTransition}
985988
*/
986989
public void play() {
990+
Toolkit.getToolkit().checkFxUserThread();
987991
if (parent != null) {
988992
throw new IllegalStateException("Cannot start when embedded in another animation");
989993
}
@@ -1035,11 +1039,15 @@ void doResume() {
10351039
* Note: <ul>
10361040
* <li>{@code stop()} is an asynchronous call, the {@code Animation} may not stop
10371041
* immediately. </ul>
1038-
* @throws IllegalStateException
1039-
* if embedded in another animation,
1042+
* <p>
1043+
* This method must be called on the JavaFX Application thread.
1044+
*
1045+
* @throws IllegalStateException if this method is called on a thread
1046+
* other than the JavaFX Application Thread, or if embedded in another animation,
10401047
* such as {@link SequentialTransition} or {@link ParallelTransition}
10411048
*/
10421049
public void stop() {
1050+
Toolkit.getToolkit().checkFxUserThread();
10431051
if (parent != null) {
10441052
throw new IllegalStateException("Cannot stop when embedded in another animation");
10451053
}
@@ -1066,11 +1074,15 @@ void doStop() {
10661074
* Note: <ul>
10671075
* <li>{@code pause()} is an asynchronous call, the {@code Animation} may not pause
10681076
* immediately. </ul>
1069-
* @throws IllegalStateException
1070-
* if embedded in another animation,
1077+
* <p>
1078+
* This method must be called on the JavaFX Application thread.
1079+
*
1080+
* @throws IllegalStateException if this method is called on a thread
1081+
* other than the JavaFX Application Thread, or if embedded in another animation,
10711082
* such as {@link SequentialTransition} or {@link ParallelTransition}
10721083
*/
10731084
public void pause() {
1085+
Toolkit.getToolkit().checkFxUserThread();
10741086
if (parent != null) {
10751087
throw new IllegalStateException("Cannot pause when embedded in another animation");
10761088
}

Diff for: modules/javafx.graphics/src/main/java/javafx/animation/AnimationTimer.java

+18-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -35,13 +35,14 @@
3535
/**
3636
* The class {@code AnimationTimer} allows to create a timer, that is called in
3737
* each frame while it is active.
38-
*
38+
* <p>
3939
* An extending class has to override the method {@link #handle(long)} which
4040
* will be called in every frame.
41-
*
41+
* <p>
4242
* The methods {@link AnimationTimer#start()} and {@link #stop()} allow to start
4343
* and stop the timer.
44-
*
44+
* <p>
45+
* The animation timer runs on the JavaFX Application Thread.
4546
*
4647
* @since JavaFX 2.0
4748
*/
@@ -96,11 +97,17 @@ public AnimationTimer() {
9697
* Starts the {@code AnimationTimer}. Once it is started, the
9798
* {@link #handle(long)} method of this {@code AnimationTimer} will be
9899
* called in every frame.
99-
*
100+
* <p>
100101
* The {@code AnimationTimer} can be stopped by calling {@link #stop()}.
102+
* <p>
103+
* This method must be called on the JavaFX Application thread.
104+
*
105+
* @throws IllegalStateException if this method is called on a thread
106+
* other than the JavaFX Application Thread.
101107
*/
102108
@SuppressWarnings("removal")
103109
public void start() {
110+
Toolkit.getToolkit().checkFxUserThread();
104111
if (!active) {
105112
// Capture the Access Control Context to be used during the animation pulse
106113
accessCtrlCtx = AccessController.getContext();
@@ -112,8 +119,14 @@ public void start() {
112119
/**
113120
* Stops the {@code AnimationTimer}. It can be activated again by calling
114121
* {@link #start()}.
122+
* <p>
123+
* This method must be called on the JavaFX Application thread.
124+
*
125+
* @throws IllegalStateException if this method is called on a thread
126+
* other than the JavaFX Application Thread.
115127
*/
116128
public void stop() {
129+
Toolkit.getToolkit().checkFxUserThread();
117130
if (active) {
118131
timer.removeAnimationTimer(timerReceiver);
119132
active = false;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package test.com.sun.javafx.animation;
27+
28+
import javafx.animation.PauseTransition;
29+
import javafx.application.Application;
30+
import javafx.application.Platform;
31+
import javafx.stage.Stage;
32+
import javafx.util.Duration;
33+
import org.junit.AfterClass;
34+
import org.junit.BeforeClass;
35+
import org.junit.Test;
36+
import test.util.Util;
37+
38+
import java.util.concurrent.CountDownLatch;
39+
40+
import static org.junit.Assert.assertFalse;
41+
import static org.junit.Assert.assertTrue;
42+
import static org.junit.jupiter.api.Assertions.assertThrows;
43+
44+
public class AnimationTest {
45+
46+
private static final CountDownLatch startupLatch = new CountDownLatch(1);
47+
48+
private static Stage primaryStage;
49+
50+
public static class TestApp extends Application {
51+
52+
@Override
53+
public void init() throws Exception {
54+
assertFalse(Platform.isFxApplicationThread());
55+
}
56+
57+
@Override
58+
public void start(Stage stage) throws Exception {
59+
primaryStage = stage;
60+
assertTrue(Platform.isFxApplicationThread());
61+
62+
startupLatch.countDown();
63+
}
64+
65+
}
66+
67+
@BeforeClass
68+
public static void setup() throws Exception {
69+
Util.launch(startupLatch, TestApp.class);
70+
}
71+
72+
@AfterClass
73+
public static void shutdown() {
74+
Util.shutdown(primaryStage);
75+
}
76+
77+
@Test
78+
public void animationOnFXThreadTest() throws InterruptedException {
79+
final CountDownLatch l = new CountDownLatch(1);
80+
Platform.runLater(() -> {
81+
assertTrue(Platform.isFxApplicationThread());
82+
PauseTransition pause = new PauseTransition(Duration.seconds(1));
83+
pause.play();
84+
pause.pause();
85+
pause.stop();
86+
l.countDown();
87+
});
88+
l.await();
89+
}
90+
91+
@Test
92+
public void startAnimationNotOnFXThreadTest() {
93+
assertFalse(Platform.isFxApplicationThread());
94+
PauseTransition pause = new PauseTransition(Duration.seconds(1));
95+
assertThrows(IllegalStateException.class, pause::play);
96+
}
97+
98+
@Test
99+
public void pauseAnimationNotOnFXThreadTest() {
100+
assertFalse(Platform.isFxApplicationThread());
101+
PauseTransition pause = new PauseTransition(Duration.seconds(1));
102+
Platform.runLater(pause::play);
103+
assertThrows(IllegalStateException.class, pause::pause);
104+
}
105+
106+
@Test
107+
public void stopAnimationNotOnFXThreadTest() {
108+
assertFalse(Platform.isFxApplicationThread());
109+
PauseTransition pause = new PauseTransition(Duration.seconds(1));
110+
Platform.runLater(() -> {
111+
pause.play();
112+
pause.pause();
113+
});
114+
assertThrows(IllegalStateException.class, pause::stop);
115+
}
116+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package test.com.sun.javafx.animation;
27+
28+
import java.util.concurrent.CountDownLatch;
29+
import javafx.animation.AnimationTimer;
30+
import javafx.application.Application;
31+
import javafx.application.Platform;
32+
import javafx.stage.Stage;
33+
import org.junit.AfterClass;
34+
import org.junit.BeforeClass;
35+
import org.junit.Test;
36+
import test.util.Util;
37+
38+
import static org.junit.Assert.assertFalse;
39+
import static org.junit.Assert.assertTrue;
40+
import static org.junit.jupiter.api.Assertions.assertThrows;
41+
42+
public class AnimationTimerTest {
43+
44+
private static final CountDownLatch startupLatch = new CountDownLatch(1);
45+
46+
private static Stage primaryStage;
47+
48+
public static class TestApp extends Application {
49+
50+
@Override
51+
public void init() throws Exception {
52+
assertFalse(Platform.isFxApplicationThread());
53+
}
54+
55+
@Override
56+
public void start(Stage stage) throws Exception {
57+
primaryStage = stage;
58+
assertTrue(Platform.isFxApplicationThread());
59+
60+
startupLatch.countDown();
61+
}
62+
63+
}
64+
65+
@BeforeClass
66+
public static void setup() throws Exception {
67+
Util.launch(startupLatch, TestApp.class);
68+
}
69+
70+
@AfterClass
71+
public static void shutdown() {
72+
Util.shutdown(primaryStage);
73+
}
74+
75+
@Test
76+
public void animationTimerOnFXThreadTest() throws InterruptedException {
77+
final CountDownLatch frameCounter = new CountDownLatch(3);
78+
Platform.runLater(() -> {
79+
AnimationTimer timer = new AnimationTimer() {
80+
@Override public void handle(long l) {
81+
frameCounter.countDown();
82+
if (frameCounter.getCount() == 0L) {
83+
stop();
84+
}
85+
}
86+
};
87+
assertTrue(Platform.isFxApplicationThread());
88+
timer.start();
89+
});
90+
frameCounter.await();
91+
}
92+
93+
@Test
94+
public void startAnimationTimerNotOnFXThreadTest() {
95+
assertFalse(Platform.isFxApplicationThread());
96+
AnimationTimer timer = new AnimationTimer() {
97+
@Override public void handle(long l) {}
98+
};
99+
assertThrows(IllegalStateException.class, timer::start);
100+
}
101+
102+
@Test
103+
public void stopAnimationTimerNotOnFXThreadTest() {
104+
assertFalse(Platform.isFxApplicationThread());
105+
AnimationTimer timer = new AnimationTimer() {
106+
@Override public void handle(long l) {
107+
assertThrows(IllegalStateException.class, () -> stop());
108+
}
109+
};
110+
Platform.runLater(timer::start);
111+
}
112+
}

0 commit comments

Comments
 (0)