<!DOCTYPE html>
<html>
  <head>

    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <p>Hello,<br>
      <br>
      We encountered an issue after updating our application
      implementation to frequently change the visibility of nodes. We
      are essentially now running an implementation that very frequently
      changes the visibility of various children nodes based on when
      they are needed and shown. When the user performs a lot of
      actions, the visibility of many nodes will be changed rapidly. <br>
      <br>
      For that, there are many listeners in place that listen for bounds
      changes of nodes to recheck whether they need to be made visible
      or not. All the visibility changes are queued up, so they are not
      immediately done in the listener after any bounds changes of
      parents. They are all properly done on the platform thread with
      runLater. When this implementation is running on many client
      systems, we sometimes receive an error report with an exception
      that looks something like this:<br>
      <br>
      java.lang.IndexOutOfBoundsException: Index -1 out of bounds for
      length 2<br>
          at
java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:100)<br>
          at
java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:106)<br>
          at
java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:302)<br>
          at java.base/java.util.Objects.checkIndex(Objects.java:365)<br>
          at java.base/java.util.ArrayList.get(ArrayList.java:428)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.base@25-ea/com.sun.javafx.collections.ObservableListWrapper.get">javafx.base@25-ea/com.sun.javafx.collections.ObservableListWrapper.get</a>(ObservableListWrapper.java:88)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.base@25-ea/com.sun.javafx.collections.VetoableListDecorator.get">javafx.base@25-ea/com.sun.javafx.collections.VetoableListDecorator.get</a>(VetoableListDecorator.java:326)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/javafx.scene.Parent.updateCachedBounds">javafx.graphics@25-ea/javafx.scene.Parent.updateCachedBounds</a>(Parent.java:1769)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/javafx.scene.Parent.recomputeBounds">javafx.graphics@25-ea/javafx.scene.Parent.recomputeBounds</a>(Parent.java:1713)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/javafx.scene.Parent.doComputeGeomBounds">javafx.graphics@25-ea/javafx.scene.Parent.doComputeGeomBounds</a>(Parent.java:1566)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/javafx.scene.Parent$1.doComputeGeomBounds">javafx.graphics@25-ea/javafx.scene.Parent$1.doComputeGeomBounds</a>(Parent.java:116)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/com.sun.javafx.scene.ParentHelper.computeGeomBoundsImpl">javafx.graphics@25-ea/com.sun.javafx.scene.ParentHelper.computeGeomBoundsImpl</a>(ParentHelper.java:84)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/com.sun.javafx.scene.layout.RegionHelper.superComputeGeomBoundsImpl">javafx.graphics@25-ea/com.sun.javafx.scene.layout.RegionHelper.superComputeGeomBoundsImpl</a>(RegionHelper.java:78)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/com.sun.javafx.scene.layout.RegionHelper.superComputeGeomBounds">javafx.graphics@25-ea/com.sun.javafx.scene.layout.RegionHelper.superComputeGeomBounds</a>(RegionHelper.java:62)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/javafx.scene.layout.Region.doComputeGeomBounds">javafx.graphics@25-ea/javafx.scene.layout.Region.doComputeGeomBounds</a>(Region.java:3301)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/javafx.scene.layout.Region$1.doComputeGeomBounds">javafx.graphics@25-ea/javafx.scene.layout.Region$1.doComputeGeomBounds</a>(Region.java:166)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/com.sun.javafx.scene.layout.RegionHelper.computeGeomBoundsImpl">javafx.graphics@25-ea/com.sun.javafx.scene.layout.RegionHelper.computeGeomBoundsImpl</a>(RegionHelper.java:89)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/com.sun.javafx.scene.NodeHelper.computeGeomBounds">javafx.graphics@25-ea/com.sun.javafx.scene.NodeHelper.computeGeomBounds</a>(NodeHelper.java:101)<br>
          at
      <a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/javafx.scene.Node.updateGeomBounds">javafx.graphics@25-ea/javafx.scene.Node.updateGeomBounds</a>(Node.java:3908)<br>
          at
      <a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/javafx.scene.Node.getGeomBounds">javafx.graphics@25-ea/javafx.scene.Node.getGeomBounds</a>(Node.java:3870)<br>
          at
      <a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/javafx.scene.Node.getLocalBounds">javafx.graphics@25-ea/javafx.scene.Node.getLocalBounds</a>(Node.java:3818)<br>
          at
      <a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/javafx.scene.Node.updateTxBounds">javafx.graphics@25-ea/javafx.scene.Node.updateTxBounds</a>(Node.java:3972)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/javafx.scene.Node.getTransformedBounds">javafx.graphics@25-ea/javafx.scene.Node.getTransformedBounds</a>(Node.java:3764)<br>
          at
      <a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/javafx.scene.Node.updateBounds">javafx.graphics@25-ea/javafx.scene.Node.updateBounds</a>(Node.java:828)<br>
          at
      <a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/javafx.scene.Parent.updateBounds">javafx.graphics@25-ea/javafx.scene.Parent.updateBounds</a>(Parent.java:1900)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/javafx.scene.Scene$ScenePulseListener.pulse">javafx.graphics@25-ea/javafx.scene.Scene$ScenePulseListener.pulse</a>(Scene.java:2670)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/com.sun.javafx.tk.Toolkit.runPulse">javafx.graphics@25-ea/com.sun.javafx.tk.Toolkit.runPulse</a>(Toolkit.java:380)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/com.sun.javafx.tk.Toolkit.firePulse">javafx.graphics@25-ea/com.sun.javafx.tk.Toolkit.firePulse</a>(Toolkit.java:401)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulse">javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulse</a>(QuantumToolkit.java:592)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulse">javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulse</a>(QuantumToolkit.java:572)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue">javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue</a>(QuantumToolkit.java:565)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$6">javafx.graphics@25-ea/com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$6</a>(QuantumToolkit.java:346)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/com.sun.glass.ui.InvokeLaterDispatcher$Future.run$$$capture">javafx.graphics@25-ea/com.sun.glass.ui.InvokeLaterDispatcher$Future.run$$$capture</a>(InvokeLaterDispatcher.java:95)<br>
          at
<a class="moz-txt-link-abbreviated" href="mailto:javafx.graphics@25-ea/com.sun.glass.ui.InvokeLaterDispatcher$Future.run">javafx.graphics@25-ea/com.sun.glass.ui.InvokeLaterDispatcher$Future.run</a>(InvokeLaterDispatcher.java)<br>
          <br>
      The index out of bounds is not always the same, there are various
      variations of this. It happens on all operating systems. It seems
      like there is a very specific scenario where an index can be out
      of bounds. This happens very rarely, like only a few times out of
      some hundred application runs, so I tried my best at forcing it to
      reproduce.<br>
      <br>
      The following reproducer works most of the time, but it might have
      to be run multiple times. I am aware that it eventually results in
      a StackOverflow, but that was the best way to force it reliably,
      by just continuously spamming visibility changes to eventually
      encounter this rare issue. But I want to emphasize that the same
      error also occurs naturally when not being forced like this, but
      it is just a lot more rare. So the StackOverflow in the reproducer
      has nothing to do with this issue, it also happens later on.</p>
    <div style="background-color:#1e1f22;color:#bcbec4">
      <pre
      style="font-family:'JetBrains Mono',monospace;font-size:9,8pt;">
<span style="color:#cf8e6d;">import </span>javafx.application.Application;
<span style="color:#cf8e6d;">import </span>javafx.scene.Scene;
<span style="color:#cf8e6d;">import </span>javafx.scene.control.Button;
<span style="color:#cf8e6d;">import </span>javafx.scene.layout.Region;
<span style="color:#cf8e6d;">import </span>javafx.scene.layout.StackPane;
<span style="color:#cf8e6d;">import </span>javafx.scene.layout.VBox;
<span style="color:#cf8e6d;">import </span>javafx.stage.Stage;

<span style="color:#cf8e6d;">import </span>java.io.IOException;

<span style="color:#cf8e6d;">public class </span>ParentBoundsBug <span
      style="color:#cf8e6d;">extends </span>Application {

    <span style="color:#b3ae60;">@Override
</span><span style="color:#b3ae60;">    </span><span
      style="color:#cf8e6d;">public void </span><span
      style="color:#56a8f5;">start</span>(Stage stage) <span
      style="color:#cf8e6d;">throws </span>IOException {
        Scene scene = <span style="color:#cf8e6d;">new </span>Scene(createContent(), <span
      style="color:#2aacb8;">640</span>, <span style="color:#2aacb8;">480</span>);
        stage.setScene(scene);
        stage.show();
        stage.centerOnScreen();
    }

    <span style="color:#cf8e6d;">private </span>Region <span
      style="color:#56a8f5;">createContent</span>() {
        <span style="color:#cf8e6d;">var </span>b1 = <span
      style="color:#cf8e6d;">new </span>Button(<span
      style="color:#6aab73;">"Click me!"</span>);
        <span style="color:#cf8e6d;">var </span>b2 = <span
      style="color:#cf8e6d;">new </span>Button(<span
      style="color:#6aab73;">"Click me!"</span>);
        <span style="color:#cf8e6d;">var </span>vbox = <span
      style="color:#cf8e6d;">new </span>VBox(b1, b2);
        b1.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
            <span style="color:#c77dbb;">vbox</span>.setVisible(!<span
      style="color:#c77dbb;">vbox</span>.isVisible());
        });
        b2.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
            <span style="color:#c77dbb;">vbox</span>.setVisible(!<span
      style="color:#c77dbb;">vbox</span>.isVisible());
        });
        vbox.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
            <span style="color:#c77dbb;">vbox</span>.setVisible(!<span
      style="color:#c77dbb;">vbox</span>.isVisible());
        });

        <span style="color:#cf8e6d;">var </span>stack = <span
      style="color:#cf8e6d;">new </span>StackPane(vbox, <span
      style="color:#cf8e6d;">new </span>StackPane());
        stack.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
            <span style="color:#c77dbb;">vbox</span>.setVisible(!<span
      style="color:#c77dbb;">vbox</span>.isVisible());
        });
        <span style="color:#cf8e6d;">return </span>stack;
    }

    <span style="color:#cf8e6d;">public static void </span><span
      style="color:#56a8f5;">main</span>(String[] args) {
        <span style="font-style:italic;">launch</span>();
    }
}</pre>
    </div>
    <p></p>
    <p><br>
      It doesn't necessarily have something to do with running the
      visibility change directly in the listener, our application does a
      runLater to change the visibility state, still with the same
      results. To properly debug this, you will have to launch the
      reproducer with a bigger stack size like -Xss8m to increase the
      chance that it occurs. Then, you can just set a breakpoint at
      jdk.internal.util.Preconditions:302, and wait for it to trigger
      the OOB eventually.<br>
      <br>
      This problem is currently the biggest JavaFX issue for us as it
      breaks the layout and usually requires a restart to fix. <br>
      <br>
      Looking at the bounds calculation code, the list index bounds
      check is very optimistic in that it doesn't check any indices and
      relies on multiple assumtions to hold. So if it is very difficult
      to find the cause, a simple index bounds check for the list access
      would also work fine.</p>
    <p>Best<br>
      Christopher Schnick<br>
    </p>
  </body>
</html>