On Thu, 20 Feb 2025 23:04:15 GMT, Andy Goryachev <ango...@openjdk.org> wrote:
>> Root Cause: >> (Multiple) properties are getting bound to the global >> `Platform.accessibilityActive` property. Binding (and I say, accessing) of >> properties is not thread-safe. >> >> I also changed the design a bit. Originally, every symbol in a chart had >> its `focusTraversableProperty` bound to `Platform.accessibilityActive` in >> order to enable the accessibility navigation across the chart data points. >> This is rather inefficient, as the property has to manage (thousands?) of >> listeners. >> >> Instead, a single boolean property is added to each chart, with a listener >> added to it which iterates over data symbols to toggle the >> `focusTraversableProperty` directly. >> >> The exact moment when the new property gets bound is also important, and has >> to happen in the FX application thread. >> >> With this change, it is safe to create and populate charts with data in a >> background thread. >> >> --- >> >> ## NOTES >> >> 1. It looks like the `Platform.accessibilityActive` property never >> transitions back to false after it transitioned to true. Some say it is >> because there is no mechanism in the platform to get notified which cannot >> possibly be true. >> 2. The javadoc for `Platform.accessibilityActiveProperty()` method >> stipulates that "_This method may be called from any thread_" which is >> patently not true as the current implementation is simply not thread-safe. >> >> ## Note to the Reviewers >> >> To avoid merge conflicts, the preferred order of integrations: >> >> #1697 >> #1713 >> #1717 > > Andy Goryachev has updated the pull request with a new target base due to a > merge or a rebase. The pull request now contains 22 commits: > > - Merge remote-tracking branch 'origin/master' into > 8349091.charts.thread.safety > - enabled pie chart test > - Merge branch 'master' into 8349091.charts.thread.safety > - Merge branch 'master' into 8349091.charts.thread.safety > - whitespace > - Merge remote-tracking branch 'origin/master' into > 8349091.charts.thread.safety > - cleanup > - tests pass > - chart tests only > - more jitter > - ... and 12 more: https://git.openjdk.org/jfx/compare/307e3087...e60c027b This is heading in the right direction, but has some bugs that will need to be fixed. modules/javafx.controls/src/main/java/javafx/scene/chart/AreaChart.java line 546: > 544: symbol.setAccessibleRole(AccessibleRole.TEXT); > 545: symbol.setAccessibleRoleDescription("Point"); > 546: symbol.setFocusTraversable(isAccessibilityActive()); It looks like you are mixing the setting up of the listeners with setting the focus traversable of this symbol to the current state of accessibilityActive, which will be `Platform.accessibilityActiveProperty` for the FX app thread) and "false" (for background thread. As a result, this method, which is called once per symbol, will set up a new listener each time it is called. You might consider refactoring this a bit to only ever set up the listeners when the object is constructed. Either that or ensure that you guard against repeatedly recreating the listener. modules/javafx.controls/src/main/java/javafx/scene/chart/Chart.java line 536: > 534: */ > 535: // package protected: custom charts must handle accessbility on > their own > 536: boolean isAccessibilityActive() { This can be final, although as I mentioned in an earlier comment, it could probably use to be refactored. modules/javafx.controls/src/main/java/javafx/scene/chart/Chart.java line 551: > 549: ChangeListener<Scene> li = new ChangeListener<>() { > 550: @Override > 551: public void changed(ObservableValue<? extends Scene> > src, Scene prev, Scene scene) { This is insufficient. It will miss the case where both the node and the scene are created in the background thread, and the scene is later added to a window on the FX app thread. I tested this case and verified my assertion. This also means that in some cases, if this listener is called from a background thread, you will create a new scene listener from the current scene listener. Consider instead using `TreeShowingProperty`, which is used by `ProgressIndicatorSkin` to set up its animation.`TreeShowingProperty` sets up a listener chain that fires when the the "tree showing" status of a node changed (tree showing means the node and all ancestors are visible and is attached to a scene that is attached to a window that is showing. Alternatively, since you might not care whether the node is visible, you can just set up a binding using `ObservableValue::flatMap`. The code snippet of the docs: ObservableValue<Boolean> isShowing = sceneProperty() .flatMap(Scene::windowProperty) .flatMap(Window::showingProperty) .orElse(false); And then you can then add a listener (or a value subscription might be better) on `isShowing` -- when it becomes true, you can setup the binding or listener to `Platform.accessibilityActiveProperty`. With either of the above two solutions, the showing state will only become true on the FX app thread. ------------- PR Review: https://git.openjdk.org/jfx/pull/1697#pullrequestreview-2634906931 PR Review Comment: https://git.openjdk.org/jfx/pull/1697#discussion_r1968581638 PR Review Comment: https://git.openjdk.org/jfx/pull/1697#discussion_r1968583768 PR Review Comment: https://git.openjdk.org/jfx/pull/1697#discussion_r1968591700