On Fri, 26 Aug 2022 00:22:54 GMT, Michael Strauß <mstra...@openjdk.org> wrote:

> It's true that `Parent.getChildren()` must not return `null`, and that we 
> shouldn't add null checks to protect against bugs.
> 
> However, in this specific instance, `Node.setTreeVisible` is called from the 
> constructor of `Node`. At that point in time, the `Parent` constructor has 
> not yet run, so its final fields are still unassigned (and therefore `null`).
> 
> The same problem exists for `SubScene.getRoot()` a few lines earlier, which 
> is specified to never return `null`, but will indeed return `null` if called 
> when the `SubScene` is under construction.
> 
> But there's a serious problem with the null checking approach: it's calling a 
> protected method at an unexpected time, before the constructor of a derived 
> class has run. Since that's not what derived classes would expect, I have 
> instead opted for a different solution, which includes a 
> `underInitialization` flag that is passed to `Node.setTreeVisible`. When the 
> flag is set, the calls to `SubScene.getRoot()` and `Parent.getChildren()` are 
> elided (they'd return `null` anyway, so there's no point in calling these 
> methods).

Can this not be done in a way that doesn't require this `underInitialization` 
flag?  The call in the constructor to `updateTreeVisible` looks misplaced, or 
at least, won't do much; visible is going to be `true`, parent is gonna be 
`null`, sub scene is going to be `null`... all that it is going to do is set 
`treeVisible` to `true`, so why not just initialize the field that way?

Result of this code for an uninitialized `Node` is going to be a call to 
`setTreeVisible(true)`, it won't do anything else:

    private void updateTreeVisible(boolean parentChanged) {
        boolean isTreeVisible = isVisible();  // is going to be true
        final Node parentNode = getParent() != null ? getParent() :    // 
parentNode is going to be null
                    clipParent != null ? clipParent :
                    getSubScene() != null ? getSubScene() : null;
        if (isTreeVisible) {
            isTreeVisible = parentNode == null || parentNode.isTreeVisible();   
// is going to be true
        }
        // When the parent has changed to visible and we have unsynchronized 
visibility,
        // we have to synchronize, because the rendering will now pass through 
the newly-visible parent
        // Otherwise an invisible Node might get rendered
        if (parentChanged && parentNode != null && parentNode.isTreeVisible()
                && isDirty(DirtyBits.NODE_VISIBLE)) {  // won't enter this if
            addToSceneDirtyList();
        }
        setTreeVisible(isTreeVisible);  // called with true
    }

Then `setTreeVisible`:

    final void setTreeVisible(boolean value) {
        if (treeVisible != value) {    // always enters if (as initial value of 
treeVisible is false, and called with true)
            treeVisible = value;
            updateCanReceiveFocus();  // doesn't do anything, canReceiveFocus 
was false initially and will be false again
            focusSetDirty(getScene());  // doesn't do anything, scene == null
            if (getClip() != null) {  // doesn't do anything, clip == null
                getClip().updateTreeVisible(true);
            }
            if (treeVisible && !isDirtyEmpty()) {  // doesn't do anything, 
dirty is empty
                addToSceneDirtyList();
            }
            ((TreeVisiblePropertyReadOnly) treeVisibleProperty()).invalidate(); 
 // doesn't do anything, there are no listeners yet
            if (Node.this instanceof SubScene) {  
                Node subSceneRoot = ((SubScene)Node.this).getRoot();
                if (subSceneRoot != null) {  // will always be null, we're 
still initializing
                    // SubScene.getRoot() is only null if it's constructor
                    // has not finished.
                    subSceneRoot.setTreeVisible(value && 
subSceneRoot.isVisible());
                }
            }
        }
    }

End result of running all that code... `treeVisible` goes from `false` to 
`true`.

-------------

PR: https://git.openjdk.org/jfx/pull/841

Reply via email to