use a framerate-capped interpolated timeline for the server toggle and wallet tab loading pulse animations, and stop any running server toggle pulse before starting a new one
What changed, and why it matters
This commit tweaks two visual animations in the Sparrow Wallet desktop app: the server connection toggle pulse and the wallet loading pulse. It caps how often the screen is redrawn during the pulse and makes sure any already-running pulse is stopped before a new one starts. There is no direct security vulnerability here; it is primarily a performance and robustness improvement for UI animations.
No security action required. Treat as a normal UI performance/stability fix. If reviewing more broadly, consider auditing other custom Timelines in the codebase for similar high-frequency redraw or overlapping-animation issues.
Security signals we found
Resource-consumption / performance hardening: capped animation framerate reduces CPU/GPU load from continuous 60 Hz redraws.
State-management hardening: stopping an existing pulse before starting a new one prevents accumulation of running Timelines.
No direct security flaw is present in the diff; signals are defensive-hardening in nature.
Evidence from the diff
The patch changes AnimationUtil.getPulse() from building a Timeline out of many per-increment KeyFrames that call node.setOpacity() to a single interpolated Timeline with two KeyValues bound to node.opacityProperty(). It also computes a target framerate (numIncrements / duration) so JavaFX only redraws the node a fixed number of times per cycle instead of every 60 Hz frame. In AppController, serverToggleStartAnimation() now calls serverToggleStopAnimation() first to avoid overlapping Timelines. The old per-increment approach created many KeyFrames and used event handlers to set opacity; the new approach is more idiomatic and less resource-intensive.
Changed components
src/main/java/com/sparrowwallet/sparrow/AppController.javasrc/main/java/com/sparrowwallet/sparrow/control/AnimationUtil.javaInspect captured patch +7 / −13
### src/main/java/com/sparrowwallet/sparrow/AppController.java
@@ -2650,6 +2650,7 @@ public void setTheme(ActionEvent event) {
}
private void serverToggleStartAnimation() {
+ serverToggleStopAnimation();
Node thumbArea = serverToggle.lookup(".thumb-area");
if(thumbArea != null) {
Timeline timeline = AnimationUtil.getPulse(thumbArea, Duration.millis(600), 1.0, 0.25, 8);
### src/main/java/com/sparrowwallet/sparrow/control/AnimationUtil.java
@@ -2,6 +2,7 @@
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
+import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.scene.Node;
import javafx.util.Duration;
@@ -20,25 +21,17 @@ public static Timeline getSlowFadeOut(Node node, Duration duration, double fromV
}
public static Timeline getPulse(Node node, Duration duration, double fromValue, double toValue, int numIncrements) {
- Timeline pulseTimeline = getFade(node, duration, fromValue, toValue, numIncrements);
+ //Cap the framerate so the node is only redrawn numIncrements times per cycle, rather than on every 60Hz pulse
+ double targetFramerate = numIncrements / duration.toSeconds();
+ Timeline pulseTimeline = new Timeline(targetFramerate,
+ new KeyFrame(Duration.ZERO, new KeyValue(node.opacityProperty(), fromValue)),
+ new KeyFrame(duration, new KeyValue(node.opacityProperty(), toValue)));
pulseTimeline.setCycleCount(Animation.INDEFINITE);
pulseTimeline.setAutoReverse(true);
return pulseTimeline;
}
- public static Timeline getFade(Node node, Duration duration, double fromValue, double toValue, int numIncrements) {
- Timeline fadeTimeline = new Timeline();
- Duration incrementDuration = duration.divide(numIncrements);
- for(int i = 0; i < numIncrements; i++) {
- double percent = ((double) numIncrements - i - 1) / numIncrements; //From 99% to 0%
- double opacity = (percent * (fromValue - toValue)) + toValue;
- fadeTimeline.getKeyFrames().add(new KeyFrame(incrementDuration.multiply(i+1), event -> node.setOpacity(opacity)));
- }
-
- return fadeTimeline;
- }
-
public record AnimatedNode (Node node, Timeline timeline) {}
}Why this scored 17/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.