1 /*
   2  * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene;
  27 
  28 import com.sun.javafx.scene.traversal.ParentTraversalEngine;
  29 import javafx.beans.property.ReadOnlyBooleanProperty;
  30 import javafx.beans.property.ReadOnlyBooleanWrapper;
  31 import javafx.collections.FXCollections;
  32 import javafx.collections.ListChangeListener.Change;
  33 import javafx.collections.ObservableList;
  34 import java.util.ArrayList;
  35 import java.util.HashSet;
  36 import java.util.List;
  37 import java.util.Set;
  38 
  39 import com.sun.javafx.util.TempState;
  40 import com.sun.javafx.util.Utils;
  41 import com.sun.javafx.collections.TrackableObservableList;
  42 import com.sun.javafx.collections.VetoableListDecorator;
  43 import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection;
  44 import javafx.css.Selector;
  45 import com.sun.javafx.css.StyleManager;
  46 import com.sun.javafx.geom.BaseBounds;
  47 import com.sun.javafx.geom.PickRay;
  48 import com.sun.javafx.geom.Point2D;
  49 import com.sun.javafx.geom.RectBounds;
  50 import com.sun.javafx.geom.transform.BaseTransform;
  51 import com.sun.javafx.geom.transform.NoninvertibleTransformException;
  52 import com.sun.javafx.jmx.MXNodeAlgorithm;
  53 import com.sun.javafx.jmx.MXNodeAlgorithmContext;
  54 import com.sun.javafx.scene.CssFlags;
  55 import com.sun.javafx.scene.DirtyBits;
  56 import com.sun.javafx.scene.input.PickResultChooser;
  57 import com.sun.javafx.sg.prism.NGGroup;
  58 import com.sun.javafx.sg.prism.NGNode;
  59 import com.sun.javafx.tk.Toolkit;
  60 import com.sun.javafx.scene.LayoutFlags;
  61 import com.sun.javafx.scene.NodeHelper;
  62 import com.sun.javafx.scene.ParentHelper;
  63 import com.sun.javafx.stage.WindowHelper;
  64 import java.util.Collections;
  65 import javafx.stage.Window;
  66 
  67 /**
  68  * The base class for all nodes that have children in the scene graph.
  69  * <p>
  70  * This class handles all hierarchical scene graph operations, including adding/removing
  71  * child nodes, marking branches dirty for layout and rendering, picking,
  72  * bounds calculations, and executing the layout pass on each pulse.
  73  * <p>
  74  * There are two direct concrete Parent subclasses
  75  * <ul>
  76  * <li>{@link Group} effects and transforms to be applied to a collection of child nodes.</li>
  77  * <li>{@link javafx.scene.layout.Region} class for nodes that can be styled with CSS and layout children. </li>
  78  * </ul>
  79  *
  80  * @since JavaFX 2.0
  81  */
  82 public abstract class Parent extends Node {
  83     // package private for testing
  84     static final int DIRTY_CHILDREN_THRESHOLD = 10;
  85 
  86     // If set to true, generate a warning message whenever adding a node to a
  87     // parent if it is currently a child of another parent.
  88     private static final boolean warnOnAutoMove = PropertyHelper.getBooleanProperty("javafx.sg.warn");
  89 
  90     /**
  91      * Threshold when it's worth to populate list of removed children.
  92      */
  93     private static final int REMOVED_CHILDREN_THRESHOLD = 20;
  94 
  95     /**
  96      * Do not populate list of removed children when its number exceeds threshold,
  97      * but mark whole parent dirty.
  98      */
  99     private boolean removedChildrenOptimizationDisabled = false;
 100 
 101     static {
 102         // This is used by classes in different packages to get access to
 103         // private and package private methods.
 104         ParentHelper.setParentAccessor(new ParentHelper.ParentAccessor() {
 105             @Override
 106             public NGNode doCreatePeer(Node node) {
 107                 return ((Parent) node).doCreatePeer();
 108             }
 109 
 110             @Override
 111             public void doUpdatePeer(Node node) {
 112                 ((Parent) node).doUpdatePeer();
 113             }
 114 
 115             @Override
 116             public boolean pickChildrenNode(Parent parent, PickRay pickRay, PickResultChooser result) {
 117                 return parent.pickChildrenNode(pickRay, result);
 118             }
 119 
 120             @Override
 121             public void setTraversalEngine(Parent parent, ParentTraversalEngine value) {
 122                 parent.setTraversalEngine(value);
 123             }
 124 
 125             @Override
 126             public ParentTraversalEngine getTraversalEngine(Parent parent) {
 127                 return parent.getTraversalEngine();
 128             }
 129         });
 130     }
 131 
 132     /*
 133      * Note: This method MUST only be called via its accessor method.
 134      */
 135     private void doUpdatePeer() {
 136         final NGGroup peer = getPeer();
 137 
 138         if (Utils.assertionEnabled()) {
 139             List<NGNode> pgnodes = peer.getChildren();
 140             if (pgnodes.size() != pgChildrenSize) {
 141                 java.lang.System.err.println("*** pgnodes.size() [" + pgnodes.size() + "] != pgChildrenSize [" + pgChildrenSize + "]");
 142             }
 143         }
 144 
 145         if (isDirty(DirtyBits.PARENT_CHILDREN)) {
 146             // Whether a permutation, or children having been added or
 147             // removed, we'll want to clear out the PG side starting
 148             // from startIdx. We know that everything up to but not
 149             // including startIdx is identical between the FX and PG
 150             // sides, so we only need to update the remaining portion.
 151             peer.clearFrom(startIdx);
 152             for (int idx = startIdx; idx < children.size(); idx++) {
 153                 peer.add(idx, children.get(idx).getPeer());
 154             }
 155             if (removedChildrenOptimizationDisabled) {
 156                 peer.markDirty();
 157                 removedChildrenOptimizationDisabled = false;
 158             } else {
 159                 if (removed != null && !removed.isEmpty()) {
 160                     for(int i = 0; i < removed.size(); i++) {
 161                         peer.addToRemoved(removed.get(i).getPeer());
 162                     }
 163                 }
 164             }
 165             if (removed != null) {
 166                 removed.clear();
 167             }
 168             pgChildrenSize = children.size();
 169             startIdx = pgChildrenSize;
 170         }
 171 
 172         if (isDirty(DirtyBits.PARENT_CHILDREN_VIEW_ORDER)) {
 173             computeViewOrderChidrenAndUpdatePeer();
 174         }
 175 
 176         if (Utils.assertionEnabled()) validatePG();
 177     }
 178 
 179 
 180     /***********************************************************************
 181      *                        Scenegraph Structure                         *
 182      *                                                                     *
 183      *  Functions and variables related to the scenegraph structure,       *
 184      *  modifying the structure, and walking the structure.                *
 185      *                                                                     *
 186      **********************************************************************/
 187 
 188     // Used to check for duplicate nodes
 189     private final Set<Node> childSet = new HashSet<Node>();
 190 
 191     // starting child index from which we need to send the children to the PGGroup
 192     private int startIdx = 0;
 193 
 194     // double of children in the PGGroup as of the last update
 195     private int pgChildrenSize = 0;
 196 
 197     void validatePG() {
 198         boolean assertionFailed = false;
 199         final NGGroup peer = getPeer();
 200         List<NGNode> pgnodes = peer.getChildren();
 201         if (pgnodes.size() != children.size()) {
 202             java.lang.System.err.println("*** pgnodes.size validatePG() [" + pgnodes.size() + "] != children.size() [" + children.size() + "]");
 203             assertionFailed = true;
 204         } else {
 205             for (int idx = 0; idx < children.size(); idx++) {
 206                 Node n = children.get(idx);
 207                 if (n.getParent() != this) {
 208                     java.lang.System.err.println("*** this=" + this + " validatePG children[" + idx + "].parent= " + n.getParent());
 209                     assertionFailed = true;
 210                 }
 211                 if (n.getPeer() != pgnodes.get(idx)) {
 212                     java.lang.System.err.println("*** pgnodes[" + idx + "] validatePG != children[" + idx + "]");
 213                     assertionFailed = true;
 214                 }
 215             }
 216         }
 217         if (assertionFailed) {
 218             throw new java.lang.AssertionError("validation of PGGroup children failed");
 219         }
 220 
 221     }
 222 
 223     void printSeq(String prefix, List<Node> nodes) {
 224         String str = prefix;
 225         for (Node nn : nodes) {
 226             str += nn + " ";
 227         }
 228         System.out.println(str);
 229     }
 230 
 231     /**
 232      * The viewOrderChildren is a list children sorted in decreasing viewOrder
 233      * order if it is not empty. Its size should always be equal to
 234      * children.size(). If viewOrderChildren is empty it implies that the
 235      * rendering order of the children is the same as the order in the children
 236      * list.
 237      */
 238     private final List<Node> viewOrderChildren = new ArrayList(1);
 239 
 240     void markViewOrderChildrenDirty() {
 241         NodeHelper.markDirty(this, DirtyBits.PARENT_CHILDREN_VIEW_ORDER);
 242     }
 243 
 244     private void computeViewOrderChidrenAndUpdatePeer() {
 245         boolean viewOrderSet = false;
 246         for (Node child : children) {
 247             double vo = child.getViewOrder();
 248 
 249             if (!viewOrderSet && vo != 0) {
 250                 viewOrderSet = true;
 251             }
 252         }
 253 
 254         viewOrderChildren.clear();
 255         if (viewOrderSet) {
 256             viewOrderChildren.addAll(children);
 257 
 258             // Sort in descending order (or big-to-small order)
 259             Collections.sort(viewOrderChildren, (Node a, Node b)
 260                     -> a.getViewOrder() < b.getViewOrder() ? 1
 261                             : a.getViewOrder() == b.getViewOrder() ? 0 : -1);
 262         }
 263 
 264         final NGGroup peer = getPeer();
 265         peer.setViewOrderChildren(viewOrderChildren);
 266     }
 267 
 268     // Call this method if children view order is needed for picking.
 269     // The returned list should be treated as read only.
 270     private List<Node> getOrderedChildren() {
 271         if (!viewOrderChildren.isEmpty()) {
 272             return viewOrderChildren;
 273         }
 274         return children;
 275     }
 276 
 277     // Variable used to indicate that the change to the children ObservableList is
 278     // a simple permutation as the result of a toFront or toBack operation.
 279     // We can avoid almost all of the processing of the on replace trigger in
 280     // this case.
 281     private boolean childrenTriggerPermutation = false;
 282 
 283     //accumulates all removed nodes between pulses, for dirty area calculation.
 284     private List<Node> removed;
 285 
 286     /**
 287      * A ObservableList of child {@code Node}s.
 288      * <p>
 289      * See the class documentation for {@link Node} for scene graph structure
 290      * restrictions on setting a {@link Parent}'s children ObservableList.
 291      * If these restrictions are violated by a change to the children ObservableList,
 292      * the change is ignored and the previous value of the child ObservableList is
 293      * restored.
 294      *
 295      * {@code <p>Throws AssignToBoundException} if the same node
 296      * appears in two different bound ObservableList.
 297      *
 298      * @defaultValue empty
 299      */
 300 
 301     // set to true if either childRemoved or childAdded returns
 302     // true. These functions will indicate whether the geom
 303     // bounds for the parent have changed
 304     private boolean geomChanged;
 305     private boolean childSetModified;
 306     private final ObservableList<Node> children = new VetoableListDecorator<Node>(new TrackableObservableList<Node>() {
 307 
 308 
 309         protected void onChanged(Change<Node> c) {
 310             // proceed with updating the scene graph
 311             unmodifiableManagedChildren = null;
 312             boolean relayout = false;
 313             boolean viewOrderChildrenDirty = false;
 314 
 315             if (childSetModified) {
 316                 while (c.next()) {
 317                     int from = c.getFrom();
 318                     int to = c.getTo();
 319                     for (int i = from; i < to; ++i) {
 320                         Node n = children.get(i);
 321                         if (n.getParent() != null && n.getParent() != Parent.this) {
 322                             if (warnOnAutoMove) {
 323                                 java.lang.System.err.println("WARNING added to a new parent without first removing it from its current");
 324                                 java.lang.System.err.println("    parent. It will be automatically removed from its current parent.");
 325                                 java.lang.System.err.println("    node=" + n + " oldparent= " + n.getParent() + " newparent=" + this);
 326                             }
 327                             n.getParent().children.remove(n);
 328                             if (warnOnAutoMove) {
 329                                 Thread.dumpStack();
 330                             }
 331                         }
 332                     }
 333 
 334                     List<Node> removed = c.getRemoved();
 335                     int removedSize = removed.size();
 336                     for (int i = 0; i < removedSize; ++i) {
 337                         final Node n = removed.get(i);
 338                         if (n.isManaged()) {
 339                             relayout = true;
 340                         }
 341                     }
 342 
 343                     // Mark viewOrderChildrenDirty if there is modification to children list
 344                     // and view order was set on one or more of the children prior to this change
 345                     if (((removedSize > 0) || (to - from) > 0) && !viewOrderChildren.isEmpty()) {
 346                         viewOrderChildrenDirty = true;
 347                     }
 348                     // update the parent and scene for each new node
 349                     for (int i = from; i < to; ++i) {
 350                         Node node = children.get(i);
 351 
 352                         // Newly added node has view order set.
 353                         if (node.getViewOrder() != 0) {
 354                             viewOrderChildrenDirty = true;
 355                         }
 356                         if (node.isManaged() || (node instanceof Parent && ((Parent) node).layoutFlag != LayoutFlags.CLEAN)) {
 357                             relayout = true;
 358                         }
 359                         node.setParent(Parent.this);
 360                         node.setScenes(getScene(), getSubScene(), /* reapplyCSS*/ true);
 361                         // assert !node.boundsChanged;
 362                         if (node.isVisible()) {
 363                             geomChanged = true;
 364                             childIncluded(node);
 365                         }
 366                     }
 367                 }
 368 
 369                 // check to see if the number of children exceeds
 370                 // DIRTY_CHILDREN_THRESHOLD and dirtyChildren is null.
 371                 // If so, then we need to create dirtyChildren and
 372                 // populate it.
 373                 if (dirtyChildren == null && children.size() > DIRTY_CHILDREN_THRESHOLD) {
 374                     dirtyChildren
 375                             = new ArrayList<Node>(2 * DIRTY_CHILDREN_THRESHOLD);
 376                     // only bother populating children if geom has
 377                     // changed, otherwise there is no need
 378                     if (dirtyChildrenCount > 0) {
 379                         int size = children.size();
 380                         for (int i = 0; i < size; ++i) {
 381                             Node ch = children.get(i);
 382                             if (ch.isVisible() && ch.boundsChanged) {
 383                                 dirtyChildren.add(ch);
 384                             }
 385                         }
 386                     }
 387                 }
 388             } else {
 389                 // If childSet was not modified, we still need to check whether the permutation
 390                 // did change the layout
 391                 layout_loop:while (c.next()) {
 392                     List<Node> removed = c.getRemoved();
 393                     for (int i = 0, removedSize = removed.size(); i < removedSize; ++i) {
 394                         if (removed.get(i).isManaged()) {
 395                             relayout = true;
 396                             break layout_loop;
 397                         }
 398                     }
 399 
 400                     for (int i = c.getFrom(), to = c.getTo(); i < to; ++i) {
 401                         if (children.get(i).isManaged()) {
 402                             relayout = true;
 403                             break layout_loop;
 404                         }
 405                     }
 406                 }
 407             }
 408 
 409 
 410             //
 411             // Note that the styles of a child do not affect the parent or
 412             // its siblings. Thus, it is only necessary to reapply css to
 413             // the Node just added and not to this parent and all of its
 414             // children. So the following call to impl_reapplyCSS was moved
 415             // to Node.parentProperty. The original comment and code were
 416             // purposely left here as documentation should there be any
 417             // question about how the code used to work and why the change
 418             // was made.
 419             //
 420             // if children have changed then I need to reapply
 421             // CSS from this node on down
 422 //                impl_reapplyCSS();
 423             //
 424 
 425             // request layout if a Group subclass has overridden doLayout OR
 426             // if one of the new children needs layout, in which case need to ensure
 427             // the needsLayout flag is set all the way to the root so the next layout
 428             // pass will reach the child.
 429             if (relayout) {
 430                 requestLayout();
 431             }
 432 
 433             if (geomChanged) {
 434                 impl_geomChanged();
 435             }
 436 
 437             // Note the starting index at which we need to update the
 438             // PGGroup on the next update, and mark the children dirty
 439             c.reset();
 440             c.next();
 441             if (startIdx > c.getFrom()) {
 442                 startIdx = c.getFrom();
 443             }
 444 
 445             NodeHelper.markDirty(Parent.this, DirtyBits.PARENT_CHILDREN);
 446             // Force synchronization to include the handling of invisible node
 447             // so that removed list will get cleanup to prevent memory leak.
 448             NodeHelper.markDirty(Parent.this, DirtyBits.NODE_FORCE_SYNC);
 449 
 450             if (viewOrderChildrenDirty) {
 451                 NodeHelper.markDirty(Parent.this, DirtyBits.PARENT_CHILDREN_VIEW_ORDER);
 452             }
 453         }
 454 
 455     }) {
 456         @Override
 457         protected void onProposedChange(final List<Node> newNodes, int[] toBeRemoved) {
 458             final Scene scene = getScene();
 459             if (scene != null) {
 460                 Window w = scene.getWindow();
 461                 if (w != null && WindowHelper.getPeer(w) != null) {
 462                     Toolkit.getToolkit().checkFxUserThread();
 463                 }
 464             }
 465             geomChanged = false;
 466 
 467             long newLength = children.size() + newNodes.size();
 468             int removedLength = 0;
 469             for (int i = 0; i < toBeRemoved.length; i += 2) {
 470                 removedLength += toBeRemoved[i + 1] - toBeRemoved[i];
 471             }
 472             newLength -= removedLength;
 473 
 474             // If the childrenTriggerPermutation flag is set, then we know it
 475             // is a simple permutation and no further checking is needed.
 476             if (childrenTriggerPermutation) {
 477                 childSetModified = false;
 478                 return;
 479             }
 480 
 481             // If the childrenTriggerPermutation flag is not set, then we will
 482             // check to see whether any element in the ObservableList has changed,
 483             // or whether the new ObservableList is a permutation on the existing
 484             // ObservableList. Note that even if the childrenModified flag is false,
 485             // we still have to check for duplicates. If it is a simple
 486             // permutation, we can avoid checking for cycles or other parents.
 487             childSetModified = true;
 488             if (newLength == childSet.size()) {
 489                 childSetModified = false;
 490                 for (int i = newNodes.size() - 1; i >= 0; --i ) {
 491                     Node n = newNodes.get(i);
 492                     if (!childSet.contains(n)) {
 493                         childSetModified = true;
 494                         break;
 495                     }
 496                 }
 497             }
 498 
 499             // Enforce scene graph invariants, and check for structural errors.
 500             //
 501             // 1. If a child has been added to this parent more than once,
 502             // then it is an error
 503             //
 504             // 2. If a child is a target of a clip, then it is an error.
 505             //
 506             // 3. If a node would cause a cycle, then it is an error.
 507             //
 508             // 4. If a node is null
 509             //
 510             // Note that if a node is the child of another parent, we will
 511             // implicitly remove the node from its former Parent after first
 512             // checking for errors.
 513 
 514             // iterate over the nodes that were removed and remove them from
 515             // the hash set.
 516             for (int i = 0; i < toBeRemoved.length; i += 2) {
 517                 for (int j = toBeRemoved[i]; j < toBeRemoved[i + 1]; j++) {
 518                     childSet.remove(children.get(j));
 519                 }
 520             }
 521 
 522             try {
 523                 if (childSetModified) {
 524                     // check individual children before duplication test
 525                     // if done in this order, the exception is more specific
 526                     for (int i = newNodes.size() - 1; i >= 0; --i ) {
 527                         Node node = newNodes.get(i);
 528                         if (node == null) {
 529                             throw new NullPointerException(
 530                                     constructExceptionMessage(
 531                                         "child node is null", null));
 532                         }
 533                         if (node.getClipParent() != null) {
 534                             throw new IllegalArgumentException(
 535                                     constructExceptionMessage(
 536                                         "node already used as a clip", node));
 537                         }
 538                         if (wouldCreateCycle(Parent.this, node)) {
 539                             throw new IllegalArgumentException(
 540                                     constructExceptionMessage(
 541                                         "cycle detected", node));
 542                         }
 543                     }
 544                 }
 545 
 546                 childSet.addAll(newNodes);
 547                 if (childSet.size() != newLength) {
 548                     throw new IllegalArgumentException(
 549                             constructExceptionMessage(
 550                                 "duplicate children added", null));
 551                 }
 552             } catch (RuntimeException e) {
 553                 //Return children to it's original state
 554                 childSet.clear();
 555                 childSet.addAll(children);
 556 
 557                 // rethrow
 558                 throw e;
 559             }
 560 
 561             // Done with error checking
 562 
 563             if (!childSetModified) {
 564                 return;
 565             }
 566 
 567             // iterate over the nodes that were removed and clear their
 568             // parent and scene. Add to them also to removed list for further
 569             // dirty regions calculation.
 570             if (removed == null) {
 571                 removed = new ArrayList<Node>();
 572             }
 573             if (removed.size() + removedLength > REMOVED_CHILDREN_THRESHOLD || !impl_isTreeVisible()) {
 574                 //do not populate too many children in removed list
 575                 removedChildrenOptimizationDisabled = true;
 576             }
 577             for (int i = 0; i < toBeRemoved.length; i += 2) {
 578                 for (int j = toBeRemoved[i]; j < toBeRemoved[i + 1]; j++) {
 579                     Node old = children.get(j);
 580                     final Scene oldScene = old.getScene();
 581                     if (oldScene != null) {
 582                         oldScene.generateMouseExited(old);
 583                     }
 584                     if (dirtyChildren != null) {
 585                         dirtyChildren.remove(old);
 586                     }
 587                     if (old.isVisible()) {
 588                         geomChanged = true;
 589                         childExcluded(old);
 590                     }
 591                     if (old.getParent() == Parent.this) {
 592                         old.setParent(null);
 593                         old.setScenes(null, null, /* reapplyCSS*/ false);
 594                     }
 595                     // Do not add node with null scene to the removed list.
 596                     // It will not be processed in the list and its memory
 597                     // will not be freed.
 598                     if (scene != null && !removedChildrenOptimizationDisabled) {
 599                         removed.add(old);
 600                     }
 601                 }
 602             }
 603         }
 604 
 605         private String constructExceptionMessage(
 606                 String cause, Node offendingNode) {
 607             final StringBuilder sb = new StringBuilder("Children: ");
 608             sb.append(cause);
 609             sb.append(": parent = ").append(Parent.this);
 610             if (offendingNode != null) {
 611                 sb.append(", node = ").append(offendingNode);
 612             }
 613 
 614             return sb.toString();
 615         }
 616     };
 617 
 618     /**
 619      * A constant reference to an unmodifiable view of the children, such that every time
 620      * we ask for an unmodifiable list of children, we don't actually create a new
 621      * collection and return it. The memory overhead is pretty lightweight compared
 622      * to all the garbage we would otherwise generate.
 623      */
 624     private final ObservableList<Node> unmodifiableChildren =
 625             FXCollections.unmodifiableObservableList(children);
 626 
 627     /**
 628      * A cached reference to the unmodifiable managed children of this Parent. This is
 629      * created whenever first asked for, and thrown away whenever children are added
 630      * or removed or when their managed state changes. This could be written
 631      * differently, such that this list is essentially a filtered copy of the
 632      * main children, but that additional overhead might not be worth it.
 633      */
 634     private List<Node> unmodifiableManagedChildren = null;
 635 
 636     /**
 637      * Gets the list of children of this {@code Parent}.
 638      *
 639      * <p>
 640      * See the class documentation for {@link Node} for scene graph structure
 641      * restrictions on setting a {@link Parent}'s children list.
 642      * If these restrictions are violated by a change to the list of children,
 643      * the change is ignored and the previous value of the children list is
 644      * restored. An {@link IllegalArgumentException} is thrown in this case.
 645      *
 646      * <p>
 647      * If this {@link Parent} node is attached to a {@link Scene} attached to a {@link Window}
 648      * that is showning ({@link javafx.stage.Window#isShowing()}), then its
 649      * list of children must only be modified on the JavaFX Application Thread.
 650      * An {@link IllegalStateException} is thrown if this restriction is
 651      * violated.
 652      *
 653      * <p>
 654      * Note to subclasses: if you override this method, you must return from
 655      * your implementation the result of calling this super method. The actual
 656      * list instance returned from any getChildren() implementation must be
 657      * the list owned and managed by this Parent. The only typical purpose
 658      * for overriding this method is to promote the method to be public.
 659      *
 660      * @return the list of children of this {@code Parent}.
 661      */
 662     protected ObservableList<Node> getChildren() {
 663         return children;
 664     }
 665 
 666     /**
 667      * Gets the list of children of this {@code Parent} as a read-only
 668      * list.
 669      *
 670      * @return read-only access to this parent's children ObservableList
 671      */
 672     @ReturnsUnmodifiableCollection
 673     public ObservableList<Node> getChildrenUnmodifiable() {
 674         return unmodifiableChildren;
 675     }
 676 
 677     /**
 678      * Gets the list of all managed children of this {@code Parent}.
 679      *
 680      * @param <E> the type of the children nodes
 681      * @return list of all managed children in this parent
 682      */
 683     @ReturnsUnmodifiableCollection
 684     protected <E extends Node> List<E> getManagedChildren() {
 685         if (unmodifiableManagedChildren == null) {
 686             unmodifiableManagedChildren = new ArrayList<Node>();
 687             for (int i=0, max=children.size(); i<max; i++) {
 688                 Node e = children.get(i);
 689                 if (e.isManaged()) {
 690                     unmodifiableManagedChildren.add(e);
 691                 }
 692             }
 693         }
 694         return (List<E>)unmodifiableManagedChildren;
 695     }
 696 
 697     /**
 698      * Called by Node whenever its managed state may have changed, this
 699      * method will cause the view of managed children to be updated
 700      * such that it properly includes or excludes this child.
 701      */
 702     final void managedChildChanged() {
 703         requestLayout();
 704         unmodifiableManagedChildren = null;
 705     }
 706 
 707     // implementation of Node.toFront function
 708     final void toFront(Node node) {
 709         if (Utils.assertionEnabled()) {
 710             if (!childSet.contains(node)) {
 711                 throw new java.lang.AssertionError(
 712                         "specified node is not in the list of children");
 713             }
 714         }
 715 
 716         if (children.get(children.size() - 1) != node) {
 717             childrenTriggerPermutation = true;
 718             try {
 719                 children.remove(node);
 720                 children.add(node);
 721             } finally {
 722                 childrenTriggerPermutation = false;
 723             }
 724         }
 725     }
 726 
 727     // implementation of Node.toBack function
 728     final void toBack(Node node) {
 729         if (Utils.assertionEnabled()) {
 730             if (!childSet.contains(node)) {
 731                 throw new java.lang.AssertionError(
 732                         "specified node is not in the list of children");
 733             }
 734         }
 735 
 736         if (children.get(0) != node) {
 737             childrenTriggerPermutation = true;
 738             try {
 739                 children.remove(node);
 740                 children.add(0, node);
 741             } finally {
 742                 childrenTriggerPermutation = false;
 743             }
 744         }
 745     }
 746 
 747     @Override
 748     void scenesChanged(final Scene newScene, final SubScene newSubScene,
 749                        final Scene oldScene, final SubScene oldSubScene) {
 750 
 751         if (oldScene != null && newScene == null) {
 752             // RT-34863 - clean up CSS cache when Parent is removed from scene-graph
 753             StyleManager.getInstance().forget(this);
 754 
 755             // Clear removed list on parent who is no longer in a scene
 756             if (removed != null) {
 757                 removed.clear();
 758             }
 759         }
 760 
 761         for (int i=0; i<children.size(); i++) {
 762             children.get(i).setScenes(newScene, newSubScene, /* reapplyCSS*/ false);
 763         }
 764 
 765         final boolean awaitingLayout = layoutFlag != LayoutFlags.CLEAN;
 766 
 767         sceneRoot = (newSubScene != null && newSubScene.getRoot() == this) ||
 768                     (newScene != null && newScene.getRoot() == this);
 769         layoutRoot = !isManaged() || sceneRoot;
 770 
 771 
 772         if (awaitingLayout) {
 773             // If this node is dirty and the new scene or subScene is not null
 774             // then add this node to the new scene's dirty list
 775             if (newScene != null && layoutRoot) {
 776                 if (newSubScene != null) {
 777                     newSubScene.setDirtyLayout(this);
 778                 }
 779             }
 780         }
 781     }
 782 
 783     @Override
 784     void setDerivedDepthTest(boolean value) {
 785         super.setDerivedDepthTest(value);
 786 
 787         for (int i=0, max=children.size(); i<max; i++) {
 788             final Node node = children.get(i);
 789             node.computeDerivedDepthTest();
 790         }
 791     }
 792 
 793     boolean pickChildrenNode(PickRay pickRay, PickResultChooser result) {
 794         List<Node> orderedChildren = getOrderedChildren();
 795         for (int i = orderedChildren.size() - 1; i >= 0; i--) {
 796             orderedChildren.get(i).impl_pickNode(pickRay, result);
 797             if (result.isClosed()) {
 798                 return false;
 799             }
 800         }
 801         return true;
 802     }
 803 
 804     /**
 805      * @treatAsPrivate implementation detail
 806      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 807      */
 808     @Deprecated
 809     @Override protected void impl_pickNodeLocal(PickRay pickRay, PickResultChooser result) {
 810          double boundsDistance = impl_intersectsBounds(pickRay);
 811 
 812         if (!Double.isNaN(boundsDistance) && pickChildrenNode(pickRay, result)) {
 813             if (isPickOnBounds()) {
 814                 result.offer(this, boundsDistance, PickResultChooser.computePoint(pickRay, boundsDistance));
 815             }
 816         }
 817     }
 818 
 819     @Override boolean isConnected() {
 820         return super.isConnected() || sceneRoot;
 821     }
 822 
 823     @Override public Node lookup(String selector) {
 824         Node n = super.lookup(selector);
 825         if (n == null) {
 826             for (int i=0, max=children.size(); i<max; i++) {
 827                 final Node node = children.get(i);
 828                 n = node.lookup(selector);
 829                 if (n != null) return n;
 830             }
 831         }
 832         return n;
 833     }
 834 
 835     /**
 836      * Please Note: This method should never create the results set,
 837      * let the Node class implementation do this!
 838      */
 839     @Override List<Node> lookupAll(Selector selector, List<Node> results) {
 840         results = super.lookupAll(selector, results);
 841         for (int i=0, max=children.size(); i<max; i++) {
 842             final Node node = children.get(i);
 843             results = node.lookupAll(selector, results);
 844         }
 845         return results;
 846     }
 847 
 848     private ParentTraversalEngine traversalEngine;
 849 
 850     private final void setTraversalEngine(ParentTraversalEngine value) {
 851         this.traversalEngine = value;
 852     }
 853 
 854     private final ParentTraversalEngine getTraversalEngine() {
 855         return traversalEngine;
 856     }
 857 
 858     /***********************************************************************
 859      *                               Layout                                *
 860      *                                                                     *
 861      *  Functions and variables related to the layout scheme used by       *
 862      *  JavaFX. Includes both public and private API.                      *
 863      *                                                                     *
 864      **********************************************************************/
 865     /**
 866      * Indicates that this Node and its subnodes requires a layout pass on
 867      * the next pulse.
 868      */
 869     private ReadOnlyBooleanWrapper needsLayout;
 870     LayoutFlags layoutFlag = LayoutFlags.CLEAN;
 871 
 872     protected final void setNeedsLayout(boolean value) {
 873         if (value) {
 874             markDirtyLayout(true, false);
 875         } else if (layoutFlag == LayoutFlags.NEEDS_LAYOUT) {
 876             boolean hasBranch = false;
 877             for (int i = 0, max = children.size(); i < max; i++) {
 878                 final Node child = children.get(i);
 879                 if (child instanceof Parent) {
 880                     if (((Parent)child).layoutFlag != LayoutFlags.CLEAN) {
 881                         hasBranch = true;
 882                         break;
 883                     }
 884 
 885                 }
 886             }
 887             setLayoutFlag(hasBranch ? LayoutFlags.DIRTY_BRANCH : LayoutFlags.CLEAN);
 888         }
 889     }
 890 
 891     public final boolean isNeedsLayout() {
 892         return layoutFlag == LayoutFlags.NEEDS_LAYOUT;
 893     }
 894 
 895     public final ReadOnlyBooleanProperty needsLayoutProperty() {
 896         if (needsLayout == null) {
 897             needsLayout = new ReadOnlyBooleanWrapper(this, "needsLayout", layoutFlag == LayoutFlags.NEEDS_LAYOUT);
 898         }
 899         return needsLayout;
 900     }
 901 
 902     /**
 903      * This is used only by CCS in Node. It is set to true while
 904      * the layout() function is processing and set to false on the conclusion.
 905      * It is used by the Node to decide whether to perform CSS updates
 906      * synchronously or asynchronously.
 907      */
 908     private boolean performingLayout = false;
 909 
 910     boolean isPerformingLayout() {
 911         return performingLayout;
 912     }
 913 
 914     private boolean sizeCacheClear = true;
 915     private double prefWidthCache = -1;
 916     private double prefHeightCache = -1;
 917     private double minWidthCache = -1;
 918     private double minHeightCache = -1;
 919 
 920     void setLayoutFlag(LayoutFlags flag) {
 921         if (needsLayout != null) {
 922             needsLayout.set(flag == LayoutFlags.NEEDS_LAYOUT);
 923         }
 924         layoutFlag = flag;
 925     }
 926 
 927     private void markDirtyLayout(boolean local, boolean forceParentLayout) {
 928         setLayoutFlag(LayoutFlags.NEEDS_LAYOUT);
 929         if (local || layoutRoot) {
 930             if (sceneRoot) {
 931                 Toolkit.getToolkit().requestNextPulse();
 932                 if (getSubScene() != null) {
 933                     getSubScene().setDirtyLayout(this);
 934                 }
 935             } else {
 936                 markDirtyLayoutBranch();
 937             }
 938         } else {
 939             requestParentLayout(forceParentLayout);
 940         }
 941     }
 942 
 943     /**
 944      * Requests a layout pass to be performed before the next scene is
 945      * rendered. This is batched up asynchronously to happen once per
 946      * "pulse", or frame of animation.
 947      * <p>
 948      * If this parent is either a layout root or unmanaged, then it will be
 949      * added directly to the scene's dirty layout list, otherwise requestParentLayout
 950      * will be invoked.
 951      * @since JavaFX 8.0
 952      */
 953     public void requestLayout() {
 954         requestLayout(false);
 955     }
 956 
 957     /**
 958      * A package scope method used by Node and serves as a helper method for
 959      * requestLayout() (see above). If forceParentLayout is true it will
 960      * propagate this force layout flag to its parent.
 961      */
 962     void requestLayout(boolean forceParentLayout) {
 963         clearSizeCache();
 964         markDirtyLayout(false, forceParentLayout);
 965     }
 966 
 967     /**
 968      * Requests a layout pass of the parent to be performed before the next scene is
 969      * rendered. This is batched up asynchronously to happen once per
 970      * "pulse", or frame of animation.
 971      * <p>
 972      * This may be used when the current parent have changed it's min/max/preferred width/height,
 973      * but doesn't know yet if the change will lead to it's actual size change. This will be determined
 974      * when it's parent recomputes the layout with the new hints.
 975      */
 976     protected final void requestParentLayout() {
 977        requestParentLayout(false);
 978     }
 979 
 980     /**
 981      * A package scope method used by Node and serves as a helper method for
 982      * requestParentLayout() (see above). If forceParentLayout is true it will
 983      * force a request layout call on its parent if its parent is not null.
 984      */
 985     void requestParentLayout(boolean forceParentLayout) {
 986         if (!layoutRoot) {
 987             final Parent p = getParent();
 988             if (p != null && (!p.performingLayout || forceParentLayout)) {
 989                 p.requestLayout();
 990             }
 991         }
 992     }
 993 
 994     void clearSizeCache() {
 995         if (sizeCacheClear) {
 996             return;
 997         }
 998         sizeCacheClear = true;
 999         prefWidthCache = -1;
1000         prefHeightCache = -1;
1001         minWidthCache = -1;
1002         minHeightCache = -1;
1003     }
1004 
1005     @Override public double prefWidth(double height) {
1006         if (height == -1) {
1007             if (prefWidthCache == -1) {
1008                 prefWidthCache = computePrefWidth(-1);
1009                 if (Double.isNaN(prefWidthCache) || prefWidthCache < 0) prefWidthCache = 0;
1010                 sizeCacheClear = false;
1011             }
1012             return prefWidthCache;
1013         } else {
1014             double result = computePrefWidth(height);
1015             return Double.isNaN(result) || result < 0 ? 0 : result;
1016         }
1017     }
1018 
1019     @Override public double prefHeight(double width) {
1020         if (width == -1) {
1021             if (prefHeightCache == -1) {
1022                 prefHeightCache = computePrefHeight(-1);
1023                 if (Double.isNaN(prefHeightCache) || prefHeightCache < 0) prefHeightCache = 0;
1024                 sizeCacheClear = false;
1025             }
1026             return prefHeightCache;
1027         } else {
1028             double result = computePrefHeight(width);
1029             return Double.isNaN(result) || result < 0 ? 0 : result;
1030         }
1031     }
1032 
1033     @Override public double minWidth(double height) {
1034         if (height == -1) {
1035             if (minWidthCache == -1) {
1036                 minWidthCache = computeMinWidth(-1);
1037                 if (Double.isNaN(minWidthCache) || minWidthCache < 0) minWidthCache = 0;
1038                 sizeCacheClear = false;
1039             }
1040             return minWidthCache;
1041         } else {
1042             double result = computeMinWidth(height);
1043             return Double.isNaN(result) || result < 0 ? 0 : result;
1044         }
1045     }
1046 
1047     @Override public double minHeight(double width) {
1048         if (width == -1) {
1049             if (minHeightCache == -1) {
1050                 minHeightCache = computeMinHeight(-1);
1051                 if (Double.isNaN(minHeightCache) || minHeightCache < 0) minHeightCache = 0;
1052                 sizeCacheClear = false;
1053             }
1054             return minHeightCache;
1055         } else {
1056             double result = computeMinHeight(width);
1057             return Double.isNaN(result) || result < 0 ? 0 : result;
1058         }
1059     }
1060 
1061     // PENDING_DOC_REVIEW
1062     /**
1063      * Calculates the preferred width of this {@code Parent}. The default
1064      * implementation calculates this width as the width of the area occupied
1065      * by its managed children when they are positioned at their
1066      * current positions at their preferred widths.
1067      *
1068      * @param height the height that should be used if preferred width depends
1069      *      on it
1070      * @return the calculated preferred width
1071      */
1072     protected double computePrefWidth(double height) {
1073         double minX = 0;
1074         double maxX = 0;
1075         for (int i=0, max=children.size(); i<max; i++) {
1076             Node node = children.get(i);
1077             if (node.isManaged()) {
1078                 final double x = node.getLayoutBounds().getMinX() + node.getLayoutX();
1079                 minX = Math.min(minX, x);
1080                 maxX = Math.max(maxX, x + boundedSize(node.prefWidth(-1), node.minWidth(-1), node.maxWidth(-1)));
1081             }
1082         }
1083         return maxX - minX;
1084     }
1085 
1086     // PENDING_DOC_REVIEW
1087     /**
1088      * Calculates the preferred height of this {@code Parent}. The default
1089      * implementation calculates this height as the height of the area occupied
1090      * by its managed children when they are positioned at their current
1091      * positions at their preferred heights.
1092      *
1093      * @param width the width that should be used if preferred height depends
1094      *      on it
1095      * @return the calculated preferred height
1096      */
1097     protected double computePrefHeight(double width) {
1098         double minY = 0;
1099         double maxY = 0;
1100         for (int i=0, max=children.size(); i<max; i++) {
1101             Node node = children.get(i);
1102             if (node.isManaged()) {
1103                 final double y = node.getLayoutBounds().getMinY() + node.getLayoutY();
1104                 minY = Math.min(minY, y);
1105                 maxY = Math.max(maxY, y + boundedSize(node.prefHeight(-1), node.minHeight(-1), node.maxHeight(-1)));
1106             }
1107         }
1108         return maxY - minY;
1109     }
1110 
1111     /**
1112      * Calculates the minimum width of this {@code Parent}. The default
1113      * implementation simply returns the pref width.
1114      *
1115      * @param height the height that should be used if min width depends
1116      *      on it
1117      * @return the calculated min width
1118      * @since JavaFX 2.1
1119      */
1120     protected double computeMinWidth(double height) {
1121         return prefWidth(height);
1122     }
1123 
1124     // PENDING_DOC_REVIEW
1125     /**
1126      * Calculates the min height of this {@code Parent}. The default
1127      * implementation simply returns the pref height;
1128      *
1129      * @param width the width that should be used if min height depends
1130      *      on it
1131      * @return the calculated min height
1132      * @since JavaFX 2.1
1133      */
1134     protected double computeMinHeight(double width) {
1135         return prefHeight(width);
1136     }
1137 
1138     /**
1139      * Calculates the baseline offset based on the first managed child. If there
1140      * is no such child, returns {@link Node#getBaselineOffset()}.
1141      *
1142      * @return baseline offset
1143      */
1144     @Override public double getBaselineOffset() {
1145         for (int i=0, max=children.size(); i<max; i++) {
1146             final Node child = children.get(i);
1147             if (child.isManaged()) {
1148                 double offset = child.getBaselineOffset();
1149                 if (offset == BASELINE_OFFSET_SAME_AS_HEIGHT) {
1150                     continue;
1151                 }
1152                 return child.getLayoutBounds().getMinY() + child.getLayoutY() + offset;
1153             }
1154         }
1155         return super.getBaselineOffset();
1156     }
1157 
1158     /***
1159      * It stores the reference to the current child being laid out by its parent.
1160      * This reference is important to differentiate whether a layout is triggered
1161      * by its parent or other events.
1162      */
1163     private Node currentLayoutChild = null;
1164 
1165     boolean isCurrentLayoutChild(Node node) {
1166         return node == currentLayoutChild;
1167     }
1168 
1169     /**
1170      * Executes a top-down layout pass on the scene graph under this parent.
1171      *
1172      * Calling this method while the Parent is doing layout is a no-op.
1173      */
1174     public final void layout() {
1175         // layoutFlag can be accessed or changed during layout processing.
1176         // Hence we need to cache and reset it before performing layout.
1177         LayoutFlags flag = layoutFlag;
1178         setLayoutFlag(LayoutFlags.CLEAN);
1179         switch(flag) {
1180             case CLEAN:
1181                 break;
1182             case NEEDS_LAYOUT:
1183                 if (performingLayout) {
1184                     /* This code is here mainly to avoid infinite loops as layout() is public and the call might be (indirectly) invoked accidentally
1185                      * while doing the layout.
1186                      * One example might be an invocation from Group layout bounds recalculation
1187                      *  (e.g. during the localToScene/localToParent calculation).
1188                      * The layout bounds will thus return layout bounds that are "old" (i.e. before the layout changes, that are just being done),
1189                      * which is likely what the code would expect.
1190                      * The changes will invalidate the layout bounds again however, so the layout bounds query after layout pass will return correct answer.
1191                      */
1192                     break;
1193                 }
1194                 performingLayout = true;
1195                 layoutChildren();
1196                 // Intended fall-through
1197             case DIRTY_BRANCH:
1198                 for (int i = 0, max = children.size(); i < max; i++) {
1199                     final Node child = children.get(i);
1200                     currentLayoutChild = child;
1201                     if (child instanceof Parent) {
1202                         ((Parent)child).layout();
1203                     } else if (child instanceof SubScene) {
1204                         ((SubScene)child).layoutPass();
1205                     }
1206                 }
1207                 currentLayoutChild = null;
1208                 performingLayout = false;
1209                 break;
1210         }
1211     }
1212 
1213     /**
1214      * Invoked during the layout pass to layout the children in this
1215      * {@code Parent}. By default it will only set the size of managed,
1216      * resizable content to their preferred sizes and does not do any node
1217      * positioning.
1218      * <p>
1219      * Subclasses should override this function to layout content as needed.
1220      */
1221     protected void layoutChildren() {
1222         for (int i=0, max=children.size(); i<max; i++) {
1223             final Node node = children.get(i);
1224             currentLayoutChild = node;
1225             if (node.isResizable() && node.isManaged()) {
1226                 node.autosize();
1227             }
1228         }
1229         currentLayoutChild = null;
1230     }
1231 
1232     /**
1233      * This field is managed by the Scene, and set on any node which is the
1234      * root of a Scene.
1235      */
1236     private boolean sceneRoot = false;
1237 
1238     /**
1239      * Keeps track of whether this node is a layout root. This is updated
1240      * whenever the sceneRoot field changes, or whenever the managed
1241      * property changes.
1242      */
1243     boolean layoutRoot = false;
1244     @Override final void notifyManagedChanged() {
1245         layoutRoot = !isManaged() || sceneRoot;
1246     }
1247 
1248     final boolean isSceneRoot() {
1249         return sceneRoot;
1250     }
1251 
1252     /***********************************************************************
1253      *                                                                     *
1254      *                         Stylesheet Handling                         *
1255      *                                                                     *
1256      **********************************************************************/
1257 
1258 
1259     /**
1260      * A ObservableList of string URLs linking to the stylesheets to use with this scene's
1261      * contents. For additional information about using CSS with the
1262      * scene graph, see the <a href="doc-files/cssref.html">CSS Reference
1263      * Guide</a>.
1264      */
1265     private final ObservableList<String> stylesheets = new TrackableObservableList<String>() {
1266         @Override
1267         protected void onChanged(Change<String> c) {
1268             final Scene scene = getScene();
1269             if (scene != null) {
1270 
1271                 // Notify the StyleManager if stylesheets change. This Parent's
1272                 // styleManager will get recreated in impl_processCSS.
1273                 StyleManager.getInstance().stylesheetsChanged(Parent.this, c);
1274 
1275                 // RT-9784 - if stylesheet is removed, reset styled properties to
1276                 // their initial value.
1277                 c.reset();
1278                 while(c.next()) {
1279                     if (c.wasRemoved() == false) {
1280                         continue;
1281                     }
1282                     break; // no point in resetting more than once...
1283                 }
1284 
1285                 impl_reapplyCSS();
1286             }
1287         }
1288     };
1289 
1290     /**
1291      * Gets an observable list of string URLs linking to the stylesheets to use
1292      * with this Parent's contents. See {@link Scene#getStylesheets()} for details.
1293      * <p>For additional information about using CSS
1294      * with the scene graph, see the <a href="doc-files/cssref.html">CSS Reference
1295      * Guide</a>.</p>
1296      *
1297      * @return the list of stylesheets to use with this Parent
1298      * @since JavaFX 2.1
1299      */
1300     public final ObservableList<String> getStylesheets() { return stylesheets; }
1301 
1302     /**
1303      * This method recurses up the parent chain until parent is null. As the
1304      * stack unwinds, if the Parent has stylesheets, they are added to the
1305      * list.
1306      *
1307      * It is possible to override this method to stop the recursion. This allows
1308      * a Parent to have a set of stylesheets distinct from its Parent.
1309      *
1310      * @treatAsPrivate implementation detail
1311      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1312      */
1313     @Deprecated // SB-dependency: RT-21247 has been filed to track this
1314     public /* Do not make this final! */ List<String> impl_getAllParentStylesheets() {
1315 
1316         List<String> list = null;
1317         final Parent myParent = getParent();
1318         if (myParent != null) {
1319 
1320             //
1321             // recurse so that stylesheets of Parents closest to the root are
1322             // added to the list first. The ensures that declarations for
1323             // stylesheets further down the tree (closer to the leaf) have
1324             // a higer ordinal in the cascade.
1325             //
1326             list = myParent.impl_getAllParentStylesheets();
1327         }
1328 
1329         if (stylesheets != null && stylesheets.isEmpty() == false) {
1330             if (list == null) {
1331                 list = new ArrayList<String>(stylesheets.size());
1332             }
1333             for (int n=0,nMax=stylesheets.size(); n<nMax; n++) {
1334                 list.add(stylesheets.get(n));
1335             }
1336         }
1337 
1338         return list;
1339 
1340     }
1341 
1342     /**
1343      * @treatAsPrivate implementation detail
1344      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1345      */
1346     @Deprecated
1347     @Override protected void impl_processCSS() {
1348 
1349         // Nothing to do...
1350         if (cssFlag == CssFlags.CLEAN) return;
1351 
1352         // RT-29254 - If DIRTY_BRANCH, pass control to Node#processCSS. This avoids calling impl_processCSS on
1353         // this node and all of its children when css doesn't need updated, recalculated, or reapplied.
1354         if (cssFlag == CssFlags.DIRTY_BRANCH) {
1355             super.processCSS();
1356             return;
1357         }
1358 
1359         // Let the super implementation handle CSS for this node
1360         super.impl_processCSS();
1361 
1362         // avoid the following call to children.toArray if there are no children
1363         if (children.isEmpty()) return;
1364 
1365         //
1366         // RT-33103
1367         //
1368         // It is possible for a child to be removed from children in the middle of
1369         // the following loop. Iterating over the children may result in an IndexOutOfBoundsException.
1370         // So a copy is made and the copy is iterated over.
1371         //
1372         // Note that we don't want the fail-fast feature of an iterator, not to mention the general iterator overhead.
1373         //
1374         final Node[] childArray = children.toArray(new Node[children.size()]);
1375 
1376         // For each child, process CSS
1377         for (int i=0; i<childArray.length; i++) {
1378 
1379             final Node child = childArray[i];
1380 
1381             //  If a child no longer has this as its parent, then it is skipped.
1382             final Parent childParent = child.getParent();
1383             if (childParent == null || childParent != this) continue;
1384 
1385             // If the parent styles are being updated, recalculated or
1386             // reapplied, then make sure the children get the same treatment.
1387             // Unless the child is already more dirty than this parent (RT-29074).
1388             if(CssFlags.UPDATE.compareTo(child.cssFlag) > 0) {
1389                 child.cssFlag = CssFlags.UPDATE;
1390             }
1391             child.impl_processCSS();
1392         }
1393     }
1394 
1395     /***********************************************************************
1396      *                               Misc                                  *
1397      *                                                                     *
1398      *  Initialization and other functions                                 *
1399      *                                                                     *
1400      **********************************************************************/
1401     {
1402         // To initialize the class helper at the begining each constructor of this class
1403         ParentHelper.initHelper(this);
1404     }
1405 
1406     /**
1407      * Constructs a new {@code Parent}.
1408      */
1409     protected Parent() {
1410         layoutFlag = LayoutFlags.NEEDS_LAYOUT;
1411         setAccessibleRole(AccessibleRole.PARENT);
1412     }
1413 
1414     private NGNode doCreatePeer() {
1415         return new NGGroup();
1416     }
1417 
1418     @Override
1419     void nodeResolvedOrientationChanged() {
1420         for (int i = 0, max = children.size(); i < max; ++i) {
1421             children.get(i).parentResolvedOrientationInvalidated();
1422         }
1423     }
1424 
1425     /***************************************************************************
1426      *                                                                         *
1427      *                         Bounds Computations                             *
1428      *                                                                         *
1429      *  This code originated in GroupBoundsHelper (part of javafx-sg-common)   *
1430      *  but has been ported here to the FX side since we cannot rely on the PG *
1431      *  side for computing the bounds (due to the decoupling of the two        *
1432      *  scenegraphs for threading and other purposes).                         *
1433      *                                                                         *
1434      *  Unfortunately, we cannot simply reuse GroupBoundsHelper without some  *
1435      *  major (and hacky) modification due to the fact that GroupBoundsHelper  *
1436      *  relies on PG state and we need to do similar things here that rely on  *
1437      *  core scenegraph state. Unfortunately, that means we made a port.       *
1438      *                                                                         *
1439      **************************************************************************/
1440 
1441     private BaseBounds tmp = new RectBounds();
1442 
1443     /**
1444      * The cached bounds for the Group. If the cachedBounds are invalid
1445      * then we have no history of what the bounds are, or were.
1446      */
1447     private BaseBounds cachedBounds = new RectBounds();
1448 
1449     /**
1450      * Indicates that the cachedBounds is invalid (or old) and need to be recomputed.
1451      * If cachedBoundsInvalid is true and dirtyChildrenCount is non-zero,
1452      * then when we recompute the cachedBounds we can consider the
1453      * values in cachedBounds to represent the last valid bounds for the group.
1454      * This is useful for several fast paths.
1455      */
1456     private boolean cachedBoundsInvalid;
1457 
1458     /**
1459      * The number of dirty children which bounds haven't been incorporated
1460      * into the cached bounds yet. Can be used even when dirtyChildren is null.
1461      */
1462     private int dirtyChildrenCount;
1463 
1464     /**
1465      * This set is used to track all of the children of this group which are
1466      * dirty. It is only used in cases where the number of children is > some
1467      * value (currently 10). For very wide trees, this can provide a very
1468      * important speed boost. For the sake of memory consumption, this is
1469      * null unless the number of children ever crosses the threshold where
1470      * it will be activated.
1471      */
1472     private ArrayList<Node> dirtyChildren;
1473 
1474     private Node top;
1475     private Node left;
1476     private Node bottom;
1477     private Node right;
1478     private Node near;
1479     private Node far;
1480 
1481     /**
1482      * @treatAsPrivate implementation detail
1483      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1484      */
1485     @Deprecated
1486     @Override public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
1487         // If we have no children, our bounds are invalid
1488         if (children.isEmpty()) {
1489             return bounds.makeEmpty();
1490         }
1491 
1492         if (tx.isTranslateOrIdentity()) {
1493             // this is a transform which is only doing translations, or nothing
1494             // at all (no scales, rotates, or shears)
1495             // so in this case we can easily use the cached bounds
1496             if (cachedBoundsInvalid) {
1497                 recomputeBounds();
1498 
1499                 if (dirtyChildren != null) {
1500                     dirtyChildren.clear();
1501                 }
1502                 cachedBoundsInvalid = false;
1503                 dirtyChildrenCount = 0;
1504             }
1505             if (!tx.isIdentity()) {
1506                 bounds = bounds.deriveWithNewBounds((float)(cachedBounds.getMinX() + tx.getMxt()),
1507                                  (float)(cachedBounds.getMinY() + tx.getMyt()),
1508                                  (float)(cachedBounds.getMinZ() + tx.getMzt()),
1509                                  (float)(cachedBounds.getMaxX() + tx.getMxt()),
1510                                  (float)(cachedBounds.getMaxY() + tx.getMyt()),
1511                                  (float)(cachedBounds.getMaxZ() + tx.getMzt()));
1512             } else {
1513                 bounds = bounds.deriveWithNewBounds(cachedBounds);
1514             }
1515 
1516             return bounds;
1517         } else {
1518             // there is a scale, shear, or rotation happening, so need to
1519             // do the full transform!
1520             double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, minZ = Double.MAX_VALUE;
1521             double maxX = Double.MIN_VALUE, maxY = Double.MIN_VALUE, maxZ = Double.MIN_VALUE;
1522             boolean first = true;
1523             for (int i=0, max=children.size(); i<max; i++) {
1524                 final Node node = children.get(i);
1525                 if (node.isVisible()) {
1526                     bounds = getChildTransformedBounds(node, tx, bounds);
1527                     // if the bounds of the child are invalid, we don't want
1528                     // to use those in the remaining computations.
1529                     if (bounds.isEmpty()) continue;
1530                     if (first) {
1531                         minX = bounds.getMinX();
1532                         minY = bounds.getMinY();
1533                         minZ = bounds.getMinZ();
1534                         maxX = bounds.getMaxX();
1535                         maxY = bounds.getMaxY();
1536                         maxZ = bounds.getMaxZ();
1537                         first = false;
1538                     } else {
1539                         minX = Math.min(bounds.getMinX(), minX);
1540                         minY = Math.min(bounds.getMinY(), minY);
1541                         minZ = Math.min(bounds.getMinZ(), minZ);
1542                         maxX = Math.max(bounds.getMaxX(), maxX);
1543                         maxY = Math.max(bounds.getMaxY(), maxY);
1544                         maxZ = Math.max(bounds.getMaxZ(), maxZ);
1545                     }
1546                 }
1547             }
1548             // if "first" is still true, then we didn't have any children with
1549             // non-empty bounds and thus we must return an empty bounds,
1550             // otherwise we have non-empty bounds so go for it.
1551             if (first)
1552                 bounds.makeEmpty();
1553             else
1554                 bounds = bounds.deriveWithNewBounds((float)minX, (float)minY, (float)minZ,
1555                         (float)maxX, (float)maxY, (float)maxZ);
1556 
1557             return bounds;
1558         }
1559     }
1560 
1561     private void setChildDirty(final Node node, final boolean dirty) {
1562         if (node.boundsChanged == dirty) {
1563             return;
1564         }
1565 
1566         node.boundsChanged = dirty;
1567         if (dirty) {
1568             if (dirtyChildren != null) {
1569                 dirtyChildren.add(node);
1570             }
1571             ++dirtyChildrenCount;
1572         } else {
1573             if (dirtyChildren != null) {
1574                 dirtyChildren.remove(node);
1575             }
1576             --dirtyChildrenCount;
1577         }
1578     }
1579 
1580     private void childIncluded(final Node node) {
1581         // assert node.isVisible();
1582         cachedBoundsInvalid = true;
1583         setChildDirty(node, true);
1584     }
1585 
1586     // This is called when either the child is actually removed, OR IF IT IS
1587     // TOGGLED TO BE INVISIBLE. This is because in both cases it needs to be
1588     // cleared from the state which manages bounds.
1589     private void childExcluded(final Node node) {
1590         if (node == left) {
1591             left = null;
1592             cachedBoundsInvalid = true;
1593         }
1594         if (node == top) {
1595             top = null;
1596             cachedBoundsInvalid = true;
1597         }
1598         if (node == near) {
1599             near = null;
1600             cachedBoundsInvalid = true;
1601         }
1602         if (node == right) {
1603             right = null;
1604             cachedBoundsInvalid = true;
1605         }
1606         if (node == bottom) {
1607             bottom = null;
1608             cachedBoundsInvalid = true;
1609         }
1610         if (node == far) {
1611             far = null;
1612             cachedBoundsInvalid = true;
1613         }
1614 
1615         setChildDirty(node, false);
1616     }
1617 
1618     /**
1619      * Recomputes the bounds from scratch and saves the cached bounds.
1620      */
1621     private void recomputeBounds() {
1622         // fast path for case of no children
1623         if (children.isEmpty()) {
1624             cachedBounds.makeEmpty();
1625             return;
1626         }
1627 
1628         // fast path for case of 1 child
1629         if (children.size() == 1) {
1630             Node node = children.get(0);
1631             node.boundsChanged = false;
1632             if (node.isVisible()) {
1633                 cachedBounds = getChildTransformedBounds(node, BaseTransform.IDENTITY_TRANSFORM, cachedBounds);
1634                 top = left = bottom = right = near = far = node;
1635             } else {
1636                 cachedBounds.makeEmpty();
1637                 // no need to null edge nodes here, it was done in childExcluded
1638                 // top = left = bottom = right = near = far = null;
1639             }
1640             return;
1641         }
1642 
1643         if ((dirtyChildrenCount == 0) ||
1644                 !updateCachedBounds(dirtyChildren != null
1645                                         ? dirtyChildren : children,
1646                                     dirtyChildrenCount)) {
1647             // failed to update cached bounds, recreate them
1648             createCachedBounds(children);
1649         }
1650     }
1651 
1652     private final int LEFT_INVALID = 1;
1653     private final int TOP_INVALID = 1 << 1;
1654     private final int NEAR_INVALID = 1 << 2;
1655     private final int RIGHT_INVALID = 1 << 3;
1656     private final int BOTTOM_INVALID = 1 << 4;
1657     private final int FAR_INVALID = 1 << 5;
1658 
1659     private boolean updateCachedBounds(final List<Node> dirtyNodes,
1660                                        int remainingDirtyNodes) {
1661         // fast path for untransformed bounds calculation
1662         if (cachedBounds.isEmpty()) {
1663             createCachedBounds(dirtyNodes);
1664             return true;
1665         }
1666 
1667         int invalidEdges = 0;
1668 
1669         if ((left == null) || left.boundsChanged) {
1670             invalidEdges |= LEFT_INVALID;
1671         }
1672         if ((top == null) || top.boundsChanged) {
1673             invalidEdges |= TOP_INVALID;
1674         }
1675         if ((near == null) || near.boundsChanged) {
1676             invalidEdges |= NEAR_INVALID;
1677         }
1678         if ((right == null) || right.boundsChanged) {
1679             invalidEdges |= RIGHT_INVALID;
1680         }
1681         if ((bottom == null) || bottom.boundsChanged) {
1682             invalidEdges |= BOTTOM_INVALID;
1683         }
1684         if ((far == null) || far.boundsChanged) {
1685             invalidEdges |= FAR_INVALID;
1686         }
1687 
1688         // These indicate the bounds of the Group as computed by this
1689         // function
1690         float minX = cachedBounds.getMinX();
1691         float minY = cachedBounds.getMinY();
1692         float minZ = cachedBounds.getMinZ();
1693         float maxX = cachedBounds.getMaxX();
1694         float maxY = cachedBounds.getMaxY();
1695         float maxZ = cachedBounds.getMaxZ();
1696 
1697         // this checks the newly added nodes first, so if dirtyNodes is the
1698         // whole children list, we can end early
1699         for (int i = dirtyNodes.size() - 1; remainingDirtyNodes > 0; --i) {
1700             final Node node = dirtyNodes.get(i);
1701             if (node.boundsChanged) {
1702                 // assert node.isVisible();
1703                 node.boundsChanged = false;
1704                 --remainingDirtyNodes;
1705                 tmp = getChildTransformedBounds(node, BaseTransform.IDENTITY_TRANSFORM, tmp);
1706                 if (!tmp.isEmpty()) {
1707                     float tmpx = tmp.getMinX();
1708                     float tmpy = tmp.getMinY();
1709                     float tmpz = tmp.getMinZ();
1710                     float tmpx2 = tmp.getMaxX();
1711                     float tmpy2 = tmp.getMaxY();
1712                     float tmpz2 = tmp.getMaxZ();
1713 
1714                     // If this node forms an edge, then we will set it to be the
1715                     // node for this edge and update the min/max values
1716                     if (tmpx <= minX) {
1717                         minX = tmpx;
1718                         left = node;
1719                         invalidEdges &= ~LEFT_INVALID;
1720                     }
1721                     if (tmpy <= minY) {
1722                         minY = tmpy;
1723                         top = node;
1724                         invalidEdges &= ~TOP_INVALID;
1725                     }
1726                     if (tmpz <= minZ) {
1727                         minZ = tmpz;
1728                         near = node;
1729                         invalidEdges &= ~NEAR_INVALID;
1730                     }
1731                     if (tmpx2 >= maxX) {
1732                         maxX = tmpx2;
1733                         right = node;
1734                         invalidEdges &= ~RIGHT_INVALID;
1735                     }
1736                     if (tmpy2 >= maxY) {
1737                         maxY = tmpy2;
1738                         bottom = node;
1739                         invalidEdges &= ~BOTTOM_INVALID;
1740                     }
1741                     if (tmpz2 >= maxZ) {
1742                         maxZ = tmpz2;
1743                         far = node;
1744                         invalidEdges &= ~FAR_INVALID;
1745                     }
1746                 }
1747             }
1748         }
1749 
1750         if (invalidEdges != 0) {
1751             // failed to validate some edges
1752             return false;
1753         }
1754 
1755         cachedBounds = cachedBounds.deriveWithNewBounds(minX, minY, minZ,
1756                                                         maxX, maxY, maxZ);
1757         return true;
1758     }
1759 
1760     private void createCachedBounds(final List<Node> fromNodes) {
1761         // These indicate the bounds of the Group as computed by this function
1762         float minX, minY, minZ;
1763         float maxX, maxY, maxZ;
1764 
1765         final int nodeCount = fromNodes.size();
1766         int i;
1767 
1768         // handle first visible non-empty node
1769         for (i = 0; i < nodeCount; ++i) {
1770             final Node node = fromNodes.get(i);
1771             node.boundsChanged = false;
1772             if (node.isVisible()) {
1773                 tmp = node.getTransformedBounds(
1774                                tmp, BaseTransform.IDENTITY_TRANSFORM);
1775                 if (!tmp.isEmpty()) {
1776                     left = top = near = right = bottom = far = node;
1777                     break;
1778                 }
1779             }
1780         }
1781 
1782         if (i == nodeCount) {
1783             left = top = near = right = bottom = far = null;
1784             cachedBounds.makeEmpty();
1785             return;
1786         }
1787 
1788         minX = tmp.getMinX();
1789         minY = tmp.getMinY();
1790         minZ = tmp.getMinZ();
1791         maxX = tmp.getMaxX();
1792         maxY = tmp.getMaxY();
1793         maxZ = tmp.getMaxZ();
1794 
1795         // handle remaining visible non-empty nodes
1796         for (++i; i < nodeCount; ++i) {
1797             final Node node = fromNodes.get(i);
1798             node.boundsChanged = false;
1799             if (node.isVisible()) {
1800                 tmp = node.getTransformedBounds(
1801                                tmp, BaseTransform.IDENTITY_TRANSFORM);
1802                 if (!tmp.isEmpty()) {
1803                     final float tmpx = tmp.getMinX();
1804                     final float tmpy = tmp.getMinY();
1805                     final float tmpz = tmp.getMinZ();
1806                     final float tmpx2 = tmp.getMaxX();
1807                     final float tmpy2 = tmp.getMaxY();
1808                     final float tmpz2 = tmp.getMaxZ();
1809 
1810                     if (tmpx < minX) { minX = tmpx; left = node; }
1811                     if (tmpy < minY) { minY = tmpy; top = node; }
1812                     if (tmpz < minZ) { minZ = tmpz; near = node; }
1813                     if (tmpx2 > maxX) { maxX = tmpx2; right = node; }
1814                     if (tmpy2 > maxY) { maxY = tmpy2; bottom = node; }
1815                     if (tmpz2 > maxZ) { maxZ = tmpz2; far = node; }
1816                 }
1817             }
1818         }
1819 
1820         cachedBounds = cachedBounds.deriveWithNewBounds(minX, minY, minZ,
1821                                                         maxX, maxY, maxZ);
1822     }
1823 
1824     @Override protected void updateBounds() {
1825         for (int i=0, max=children.size(); i<max; i++) {
1826             children.get(i).updateBounds();
1827         }
1828         super.updateBounds();
1829     }
1830 
1831     // Note: this marks the currently processed child in terms of transformed bounds. In rare situations like
1832     // in RT-37879, it might happen that the child bounds will be marked as invalid. Due to optimizations,
1833     // the invalidation must *always* be propagated to the parent, because the parent with some transformation
1834     // calls child's getTransformedBounds non-idenitity transform and the child's transformed bounds are thus not validated.
1835     // This does not apply to the call itself however, because the call will yield the correct result even if something
1836     // was invalidated during the computation. We can safely ignore such invalidations from that Node in this case
1837     private Node currentlyProcessedChild;
1838 
1839     private BaseBounds getChildTransformedBounds(Node node, BaseTransform tx, BaseBounds bounds) {
1840         currentlyProcessedChild = node;
1841         bounds = node.getTransformedBounds(bounds, tx);
1842         currentlyProcessedChild = null;
1843         return bounds;
1844     }
1845 
1846     /**
1847      * Called by Node whenever its bounds have changed.
1848      */
1849     void childBoundsChanged(Node node) {
1850         // See comment above at "currentlyProcessedChild" field
1851         if (node == currentlyProcessedChild) {
1852             return;
1853         }
1854 
1855         cachedBoundsInvalid = true;
1856 
1857         // mark the node such that the parent knows that the child's bounds
1858         // are not in sync with this parent. In this way, when the bounds
1859         // need to be computed, we'll come back and figure out the new bounds
1860         // for all the children which have boundsChanged set to true
1861         setChildDirty(node, true);
1862 
1863         // go ahead and indicate that the geom has changed for this parent,
1864         // even though once we figure it all out it may be that the bounds
1865         // have not changed
1866         impl_geomChanged();
1867     }
1868 
1869     /**
1870      * Called by node whenever the visibility of the node changes.
1871      */
1872     void childVisibilityChanged(Node node) {
1873         if (node.isVisible()) {
1874             childIncluded(node);
1875         } else {
1876             childExcluded(node);
1877         }
1878 
1879         impl_geomChanged();
1880     }
1881 
1882     /**
1883      * @treatAsPrivate implementation detail
1884      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1885      */
1886     @Deprecated
1887     @Override
1888     protected boolean impl_computeContains(double localX, double localY) {
1889         final Point2D tempPt = TempState.getInstance().point;
1890         for (int i=0, max=children.size(); i<max; i++) {
1891             final Node node = children.get(i);
1892             tempPt.x = (float)localX;
1893             tempPt.y = (float)localY;
1894             try {
1895                 node.parentToLocal(tempPt);
1896             } catch (NoninvertibleTransformException e) {
1897                 continue;
1898             }
1899             if (node.contains(tempPt.x, tempPt.y)) {
1900                 return true;
1901             }
1902         }
1903         return false;
1904     }
1905 
1906     /**
1907      * @treatAsPrivate implementation detail
1908      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1909      */
1910     @Deprecated
1911     public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) {
1912         return alg.processContainerNode(this, ctx);
1913     }
1914 
1915     @Override
1916     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
1917         switch (attribute) {
1918             case CHILDREN: return getChildrenUnmodifiable();
1919             default: return super.queryAccessibleAttribute(attribute, parameters);
1920         }
1921     }
1922 
1923     void releaseAccessible() {
1924         for (int i=0, max=children.size(); i<max; i++) {
1925             final Node node = children.get(i);
1926             node.releaseAccessible();
1927         }
1928         super.releaseAccessible();
1929     }
1930 
1931     /**
1932      * Note: The only user of this method is in unit test: Parent_structure_sync_Test.
1933      */
1934     List<Node> test_getRemoved() {
1935         return removed;
1936     }
1937 
1938     /**
1939      * Note: The only user of this method is in unit test:
1940      * Parent_viewOrderChildren_sync_Test.
1941      */
1942     List<Node> test_getViewOrderChildren() {
1943         return viewOrderChildren;
1944     }
1945 }