On Fri, 26 Aug 2022 00:22:54 GMT, Michael Strauß <[email protected]> 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