       1 /*
       2  * Copyright (c) 2010, 2012, 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 com.sun.javafx.css;
      27
      28 import java.io.IOException;
      29 import java.net.MalformedURLException;
      30 import java.net.URI;
      31 import java.net.URL;
      32 import java.io.FilePermission;
      33 import java.security.AccessControlContext;
      34 import java.security.AccessController;
      35 import java.security.PermissionCollection;
      36 import java.security.ProtectionDomain;
      37 import java.util.ArrayList;
      38 import java.util.Collections;
      39 import java.util.HashMap;
      40 import java.util.Iterator;
      41 import java.util.List;
      42 import java.util.Map;
      43 import java.util.jar.JarEntry;
      44 import java.util.jar.JarFile;
      45
      46 import javafx.collections.FXCollections;
      47 import javafx.scene.Node;
      48 import javafx.scene.Scene;
      49
      50 import com.sun.javafx.css.parser.CSSParser;
      51 import com.sun.javafx.css.StyleHelper.StyleCacheBucket;
      52 import com.sun.javafx.css.StyleHelper.StyleCacheKey;
      53 import com.sun.javafx.logging.PlatformLogger;
      54 import java.io.FileNotFoundException;
      55 import java.lang.ref.Reference;
      56 import java.lang.ref.WeakReference;
      57 import java.security.PrivilegedAction;
      58 import java.security.PrivilegedExceptionAction;
      59 import java.security.PrivilegedActionException;
      60 import java.util.*;
      61 import java.util.Map.Entry;
      62 import javafx.collections.*;
      63 import javafx.scene.Parent;
      64 import javafx.stage.Window;
      65
      66 /**
      67  * Contains the stylesheet state for a single scene. This includes both the
      68  * Stylesheets defined on the Scene itself as well as a map of stylesheets for
      69  * "style"s defined on the Node itself. These containers are kept in the
      70  * containerMap, key'd by the Scene to which they belong. <p> One of the key
      71  * responsibilities of the StylesheetContainer is to create and maintain an
      72  * admittedly elaborate series of caches so as to minimize the amount of time it
      73  * takes to match a Node to its eventual StyleHelper, and to reuse the
      74  * StyleHelper as much as possible. <p> Initially, the cache is empty. It is
      75  * recreated whenever the userStylesheets on the container change, or whenever
      76  * the userAgentStylesheet changes. The cache is built up as nodes are looked
      77  * for, and thus there is some overhead associated with the first lookup but
      78  * which is then not repeated for subsequent lookups. <p> The cache system used
      79  * is a two level cache. The first level cache simply maps the
      80  * classname/id/styleclass combination of the request node to a 2nd level cache.
      81  * If the node has "styles" specified then we still use this 2nd level cache,
      82  * but must combine its rules with the rules specified in "styles" and perform
      83  * more work to cascade properly. <p> The 2nd level cache contains a data
      84  * structure called the Cache. The Cache contains an ordered sequence of Rules,
      85  * a Long, and a Map. The ordered sequence of rules are the rules that *may*
      86  * match a node with the given classname, id, and style class. For example,
      87  * rules which may apply are any rule where the simple selector of the rule
      88  * contains a reference to the id, style class, or classname of the Node, or a
      89  * compound selector who's "descendant" part is a simple selector which contains
      90  * a reference to the id, style class, or classname of the Node. <p> During
      91  * lookup, we will iterate over all the potential rules and discover if they
      92  * apply to this particular node. If so, then we toggle a bit position in the
      93  * Long corresponding to the position of the rule that matched. This long then
      94  * becomes our key into the final map. <p> Once we have established our key, we
      95  * will visit the map and look for an existing StyleHelper. If we find a
      96  * StyleHelper, then we will return it. If not, then we will take the Rules that
      97  * matched and construct a new StyleHelper from their various parts. <p> This
      98  * system, while elaborate, also provides for numerous fast paths and sharing of
      99  * data structures which should dramatically reduce the memory and runtime
     100  * performance overhead associated with CSS by reducing the matching overhead
     101  * and caching as much as possible. We make no attempt to use weak references
     102  * here, so if memory issues result one work around would be to toggle the root
     103  * user agent stylesheet or stylesheets on the scene to cause the cache to be
     104  * flushed.
     105  */
     106
     107 abstract public class StyleManager<T> {
     108
     109     protected static PlatformLogger LOGGER = com.sun.javafx.Logging.getCSSLogger();
     110
     111     /**
     112      * Return the StyleManager for the given Scene, or null if the scene
     113      * is no longer referenced.
     114      */
     115     public static <T> StyleManager getStyleManager(T key) {
     116         final Reference<StyleManager> styleManagerRef =
     117                 key != null ? managerReferenceMap.get(key) : null;
     118         return styleManagerRef != null ? styleManagerRef.get() : null;
     119     }
     120
     121     public static StyleManager createStyleManager(Scene scene) {
     122         return new SceneStyleManager(scene);
     123     }
     124
     125     public static StyleManager createStyleManager(Parent parent, StyleManager ancestorStyleManager) {
     126         return new ParentStyleManager(parent, ancestorStyleManager);
     127     }
     128
     129     /**
     130      *
     131      * @param owner
     132      */
     133     protected StyleManager(T owner) {
     134
     135         if (owner == null) throw new IllegalArgumentException("scene cannot be null");
     136
     137         this.owner = owner;
     138
     139         cacheMap = new HashMap<Key, Cache>();
     140
     141         referencedStylesheets = new ArrayList<Stylesheet>();
     142         if (defaultUserAgentStylesheet != null) {
     143             referencedStylesheets.add(defaultUserAgentStylesheet);
     144         }
     145
     146         styleCache = new HashMap<StyleCacheKey, StyleCacheBucket>();
     147
     148         smapCount = 0;
     149
     150         // A Reference is needed here since Scene is both in the key and value
     151         managerReferenceMap.put(owner, new WeakReference<StyleManager>(this));
     152
     153     }
     154
     155     /*
     156      * The scene or parent that created this StyleManager.
     157      */
     158     protected final T owner;
     159     T getOwner() { return owner; } // for testing
     160
     161     /*
     162      * Stylesheets that are referenced by this Scene. The parsed stylesheets
     163      * are held in the StylesheetContainer and could be looked up from there,
     164      * but that lookup would be frequent so we hang onto the parsed stylesheets
     165      */
     166     protected final List<Stylesheet> referencedStylesheets;
     167     // for testing
     168     List<Stylesheet> getReferencedStylesheets() { return referencedStylesheets; }
     169
     170
     171     /*
     172      * Scene does not have a inline style, but an applet might. This is
     173      * the applet's stylesheet from a 'style' attribute.
     174      */
     175     protected Stylesheet inlineStylesheet;
     176     // for testing
     177     Stylesheet getInlineStylesheet() { return inlineStylesheet; }
     178
     179     /**
     180      * The map of Caches, key'd by a combination of class name, style class, and
     181      * id.
     182      */
     183     protected final Map<Key, Cache> cacheMap;
     184     // for testing
     185     Map<Key, Cache> getCacheMap() { return cacheMap; }
     186
     187     /*
     188      * The StyleHelper's cached values are relevant only for a given scene.
     189      * Since a StylesheetContainer is created for a Scene with stylesheets,
     190      * it makes sense that the container should own the valueCache. This
     191      * way, each scene gets its own valueCache.
     192      */
     193     private final Map<StyleCacheKey, StyleCacheBucket> styleCache;
     194     // for testing
     195     Map<StyleCacheKey, StyleCacheBucket> getStyleCache() { return styleCache; }
     196
     197     /*
     198      * A simple counter used to generate a unique id for a StyleMap.
     199      * This unique id is used by StyleHelper in figuring out which
     200      * style cache to use.
     201      */
     202     private int smapCount;
     203
     204     /**
     205      * A map from Scene => StyleManager. This provides us a way to find
     206      * the StyleManager which is owned by a given Scene without having to
     207      * add a getStyleManager public API to Scene. This map is used primarily
     208      * for getting the StyleManager of the owning Scene for a popup.
     209      */
     210     protected static final Map<Object, Reference<StyleManager>> managerReferenceMap =
     211             new WeakHashMap<Object, Reference<StyleManager>>();
     212
     213    /**
     214      * This stylesheet represents the "default" set of styles for the entire
     215      * platform. This is typically only set by the UI Controls module, and
     216      * otherwise is generally null. Whenever this variable changes (via the
     217      * setDefaultUserAgentStylesheet function call) then we will end up clearing
     218      * all of the caches.
     219      */
     220     protected static Stylesheet defaultUserAgentStylesheet;
     221
     222
     223     /**
     224      * User agent stylesheets from Control.getUserAgentStylesheet.
     225      */
     226     protected static final Map<String,Stylesheet> userAgentStylesheetMap =
     227             new HashMap<String,Stylesheet>();
     228
     229
     230     /*
     231      * A container for stylesheets and the Parents or Scenes that use them.
     232      * If a stylesheet is removed, then all other Parents or Scenes
     233      * that use that stylesheet should get new styles if the
     234      * stylesheet is added back in since the stylesheet may have been
     235      * removed and re-added because it was edited (typical of SceneBuilder).
     236      * This container provides the hooks to get back to those Parents or Scenes.
     237      *
     238      * StylesheetContainer<Parent> are created and added to parentStylesheetMap
     239      * in the method gatherParentStylesheets.
     240      *
     241      * StylesheetContainer<Scene> are created and added to sceneStylesheetMap in
     242      * the method updateStylesheets
     243      */
     244     protected static class StylesheetContainer<T> {
     245         // the stylesheet url
     246         final String fname;
     247         // the parsed stylesheet so we don't reparse for every parent that uses it
     248         final Stylesheet stylesheet;
     249         // the parents or scenes that use this stylesheet. Typically, this list
     250         //  should be very small.
     251         final RefList<T> users;
     252         // the keys for finding Cache entries that use this stylesheet.
     253         // This list should also be fairly small
     254         final RefList<Key> keys;
     255
     256         StylesheetContainer(String fname, Stylesheet stylesheet) {
     257             this.fname = fname;
     258             this.stylesheet = stylesheet;
     259             this.users = new RefList<T>();
     260             this.keys = new RefList<Key>();
     261         }
     262     }
     263
     264     /*
     265      * A list that holds references. Used by StylesheetContainer.
     266      * We only ever add to this list, or the whole container gets thrown
     267      * away.
     268      */
     269     protected static class RefList<K> {
     270
     271         final List<Reference<K>> list = new ArrayList<Reference<K>>();
     272
     273         void add(K key) {
     274
     275             for (int n=list.size()-1; 0<=n; --n) {
     276                 final Reference<K> ref = list.get(n);
     277                 final K k = ref.get();
     278                 if (k == null) {
     279                     // stale reference, remove it.
     280                     list.remove(n);
     281                 } else {
     282                     // already have it, bail
     283                     if (k == key) return;
     284                 }
     285             }
     286             // not found, add it.
     287             list.add(new WeakReference<K>(key));
     288         }
     289
     290     }
     291
     292     public abstract void updateStylesheets();
     293
     294     public void stylesheetsChanged(ListChangeListener.Change<String> c) {
     295         // for now, just call updateStylesheets
     296         updateStylesheets();
     297     }
     298
     299     /**
     300      * Set a stylesheet for the scene from the given stylesheet string. This
     301      * stylesheet comes after user agent stylesheets but before author
     302      * stylesheets and applies only to this scene.
     303      */
     304     public static void setInlineStylesheet(Scene scene, Stylesheet stylesheet) {
     305
     306         if (scene == null) return;
     307
     308         StyleManager styleManager = StyleManager.getStyleManager(scene);
     309         if (styleManager != null) {
     310             styleManager.inlineStylesheet = stylesheet;
     311             styleManager.updateStylesheets();
     312         }
     313
     314     }
     315
     316
     317     ////////////////////////////////////////////////////////////////////////////
     318     //
     319     // Stylesheet loading
     320     //
     321     ////////////////////////////////////////////////////////////////////////////
     322     private static URL getURL(String urlStr) {
     323         try {
     324             return new URL(urlStr);
     325         } catch (MalformedURLException malf) {
     326             // This may be a relative URL, so try resolving
     327             // it using the application classloader
     328             ClassLoader cl = Thread.currentThread().getContextClassLoader();
     329             if (cl != null) {
     330                 String path = (urlStr != null && urlStr.startsWith("/"))
     331                         ? urlStr.substring(1) : urlStr;
     332                 return cl.getResource(path);
     333             }
     334             return null;
     335         }
     336     }
     337
     338     protected static Stylesheet loadStylesheet(final String fname) {
     339         try {
     340             return loadStylesheetUnPrivileged(fname);
     341         } catch (java.security.AccessControlException ace) {
     342             if (LOGGER.isLoggable(PlatformLogger.INFO)) {
     343                 LOGGER.info("Could not load the stylesheet, trying with FilePermissions : " + fname);
     344             }
     345
     346             /*
     347             ** we got an access control exception, so
     348             ** we could be running from an applet/jnlp/or with a security manager.
     349             ** we'll allow the app to read a css file from our runtime jar,
     350             ** and give it one more chance.
     351             */
     352
     353             /*
     354             ** check that there are enough chars after the !/ to have a valid .css or .bss file name
     355             */
     356             if ((fname.length() < 7) && (fname.indexOf("!/") < fname.length()-7)) {
     357                 return null;
     358             }
     359
     360             /*
     361             **
     362             ** first check that it's actually looking for the same runtime jar
     363             ** that we're running from, and not some other file.
     364             */
     365             try {
     366                 URI requestedFileUrI = new URI(fname);
     367
     368                 /*
     369                 ** is the requested file in a jar
     370                 */
     371                 if ("jar".equals(requestedFileUrI.getScheme())) {
     372                     /*
     373                     ** let's check that the css file is being requested from our
     374                     ** runtime jar
     375                     */
     376                     URI styleManagerJarURI = AccessController.doPrivileged(new PrivilegedExceptionAction<URI>() {
     377                             public URI run() throws java.net.URISyntaxException, java.security.PrivilegedActionException {
     378                             return StyleManager.class.getProtectionDomain().getCodeSource().getLocation().toURI();
     379                         }
     380                     });
     381
     382                     final String styleManagerJarPath = styleManagerJarURI.getSchemeSpecificPart();
     383                     String requestedFilePath = requestedFileUrI.getSchemeSpecificPart();
     384                     String requestedFileJarPart = requestedFilePath.substring(requestedFilePath.indexOf('/'), requestedFilePath.indexOf("!/"));
     385                     /*
     386                     ** it's the correct jar, check it's a file access
     387                     ** strip off the leading jar
     388                     */
     389                     if (styleManagerJarPath.equals(requestedFileJarPart.toString())) {
     390                         /*
     391                         ** strip off the leading "jar",
     392                         ** the css file name is past the last '!'
     393                         */
     394                         String requestedFileJarPathNoLeadingSlash = fname.substring(fname.indexOf("!/")+2);
     395                         /*
     396                         ** check that it's looking for a css file in the runtime jar
     397                         */
     398                         if (fname.endsWith(".css") || fname.endsWith(".bss")) {
     399                             /*
     400                             ** set up a read permission for the jar
     401                             */
     402                             FilePermission perm = new FilePermission(styleManagerJarPath, "read");
     403
     404                             PermissionCollection perms = perm.newPermissionCollection();
     405                             perms.add(perm);
     406                             AccessControlContext permsAcc = new AccessControlContext(
     407                                 new ProtectionDomain[] {
     408                                     new ProtectionDomain(null, perms)
     409                                 });
     410                             /*
     411                             ** check that the jar file exists, and that we're allowed to
     412                             ** read it.
     413                             */
     414                             JarFile jar = null;
     415                             try {
     416                                 jar = AccessController.doPrivileged(new PrivilegedExceptionAction<JarFile>() {
     417                                         public JarFile run() throws FileNotFoundException, IOException {
     418                                             return new JarFile(styleManagerJarPath);
     419                                         }
     420                                     }, permsAcc);
     421                             } catch (PrivilegedActionException pae) {
     422                                 /*
     423                                 ** we got either a FileNotFoundException or an IOException
     424                                 ** in the privileged read. Return the same error as we
     425                                 ** would have returned if the css file hadn't of existed.
     426                                 */
     427                                 return null;
     428                             }
     429                             if (jar != null) {
     430                                 /*
     431                                 ** check that the file is in the jar
     432                                 */
     433                                 JarEntry entry = jar.getJarEntry(requestedFileJarPathNoLeadingSlash);
     434                                 if (entry != null) {
     435                                     /*
     436                                     ** allow read access to the jar
     437                                     */
     438                                     return AccessController.doPrivileged(
     439                                         new PrivilegedAction<Stylesheet>() {
     440                                             @Override public Stylesheet run() {
     441                                                 return loadStylesheetUnPrivileged(fname);
     442                                             }}, permsAcc);
     443                                 }
     444                             }
     445                         }
     446                     }
     447                 }
     448                 /*
     449                 ** no matter what happen, we return the same error that would
     450                 ** be returned if the css file hadn't of existed.
     451                 ** That way there in no information leaked.
     452                 */
     453                 return null;
     454             }
     455             /*
     456             ** no matter what happen, we return the same error that would
     457             ** be returned if the css file hadn't of existed.
     458             ** That way there in no information leaked.
     459             */
     460             catch (java.net.URISyntaxException e) {
     461                 return null;
     462             }
     463             catch (java.security.PrivilegedActionException e) {
     464                 return null;
     465             }
     466        }
     467     }
     468
     469
     470     private static Stylesheet loadStylesheetUnPrivileged(final String fname) {
     471
     472         Boolean parse = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
     473             @Override public Boolean run() {
     474
     475                 final String bss = System.getProperty("binary.css");
     476                 // binary.css is true by default.
     477                 // parse only if the file is not a .bss
     478                 // and binary.css is set to false
     479                 return (!fname.endsWith(".bss") && bss != null) ?
     480                     !Boolean.valueOf(bss) : Boolean.FALSE;
     481             }
     482         });
     483
     484         try {
     485             final String ext = (parse) ? (".css") : (".bss");
     486
     487             final String name =
     488                 (fname.endsWith(".css") || fname.endsWith(".bss")) ?
     489                     fname.substring(0, fname.length() - 4) : fname;
     490
     491             java.net.URL url = getURL(name+ext);
     492             if (url == null && (parse = !parse)) {
     493                 // If we failed to get the URL for the .bss file,
     494                 // fall back to the .css file.
     495                 // Note that 'parse' is toggled in the test.
     496                 url = getURL(name+".css");
     497             }
     498
     499             Stylesheet stylesheet = null;
     500             if ((url != null) && !parse) {
     501                 stylesheet = Stylesheet.loadBinary(url);
     502
     503                 if (stylesheet == null && (parse = !parse)) {
     504                     // If we failed to load the .bss file,
     505                     // fall back to the .css file.
     506                     // Note that 'parse' is toggled in the test.
     507                     url = getURL(name+".css");
     508                 }
     509             }
     510
     511             // either we failed to load the .bss file, or parse
     512             // was set to true.
     513             if ((url != null) && parse) {
     514                 stylesheet = CSSParser.getInstance().parse(url);
     515             }
     516
     517             if (stylesheet == null) {
     518                 if (errors != null) {
     519                     CssError error =
     520                         new CssError(
     521                             "Resource \""+fname+"\" not found."
     522                         );
     523                     errors.add(error);
     524                 }
     525                 if (LOGGER.isLoggable(PlatformLogger.WARNING)) {
     526                     LOGGER.warning(
     527                         String.format("Resource \"%s\" not found.", fname)
     528                     );
     529                 }
     530             }
     531             return stylesheet;
     532
     533         } catch (FileNotFoundException fnfe) {
     534             if (errors != null) {
     535                 CssError error =
     536                     new CssError(
     537                         "Stylesheet \""+fname+"\" not found."
     538                     );
     539                 errors.add(error);
     540             }
     541             if (LOGGER.isLoggable(PlatformLogger.INFO)) {
     542                 LOGGER.info("Could not find stylesheet: " + fname);//, fnfe);
     543             }
     544         } catch (IOException ioe) {
     545                 if (errors != null) {
     546                     CssError error =
     547                         new CssError(
     548                             "Could not load stylesheet: " + fname
     549                         );
     550                     errors.add(error);
     551                 }
     552             if (LOGGER.isLoggable(PlatformLogger.INFO)) {
     553                 LOGGER.info("Could not load stylesheet: " + fname);//, ioe);
     554             }
     555         }
     556         return null;
     557     }
     558
     559     ////////////////////////////////////////////////////////////////////////////
     560     //
     561     // User Agent stylesheet handling
     562     //
     563     ////////////////////////////////////////////////////////////////////////////
     564
     565     /**
     566      * Add a user agent stylesheet, possibly overriding styles in the default
     567      * user agent stylesheet.
     568      *
     569      * @param fname The file URL, either relative or absolute, as a String.
     570      */
     571     public static void addUserAgentStylesheet(String fname) {
     572         addUserAgentStylesheet(null, fname);
     573     }
     574
     575     /**
     576      * Add a user agent stylesheet, possibly overriding styles in the default
     577      * user agent stylesheet.
     578      * @param scene Only used in CssError for tracking back to the scene that loaded the stylesheet
     579      * @param fname  The file URL, either relative or absolute, as a String.
     580      */
     581     // For RT-20643
     582     public static void addUserAgentStylesheet(Scene scene, String fname) {
     583
     584         // nothing to add
     585         if (fname == null ||  fname.trim().isEmpty()) return;
     586
     587         if (userAgentStylesheetMap.containsKey(fname) == false) {
     588
     589             // RT-20643
     590             CssError.setCurrentScene(scene);
     591
     592             Stylesheet ua_stylesheet = loadStylesheet(fname);
     593             ua_stylesheet.setOrigin(Stylesheet.Origin.USER_AGENT);
     594             userAgentStylesheetMap.put(fname, ua_stylesheet);
     595
     596             if (ua_stylesheet != null) {
     597                 userAgentStylesheetsChanged();
     598             }
     599
     600             // RT-20643
     601             CssError.setCurrentScene(null);
     602         }
     603
     604     }
     605
     606     /**
     607      * Set the default user agent stylesheet.
     608      *
     609      * @param fname The file URL, either relative or absolute, as a String.
     610      */
     611     public static void setDefaultUserAgentStylesheet(String fname) {
     612         setDefaultUserAgentStylesheet(null, fname);
     613     }
     614
     615     /**
     616      * Set the default user agent stylesheet
     617      * @param scene Only used in CssError for tracking back to the scene that loaded the stylesheet
     618      * @param fname  The file URL, either relative or absolute, as a String.
     619      */
     620     // For RT-20643
     621     public static void setDefaultUserAgentStylesheet(Scene scene, String fname) {
     622
     623         if (fname == null || fname.trim().isEmpty())
     624             throw new IllegalArgumentException("null arg fname");
     625
     626         // RT-20643
     627         CssError.setCurrentScene(scene);
     628
     629         Stylesheet ua_stylesheet = loadStylesheet(fname);
     630         if (ua_stylesheet != null) {
     631             ua_stylesheet.setOrigin(Stylesheet.Origin.USER_AGENT);
     632             setDefaultUserAgentStylesheet(ua_stylesheet);
     633         }
     634
     635         // RT-20643
     636         CssError.setCurrentScene(null);
     637
     638     }
     639
     640     /**
     641      * Set the user agent stylesheet. This is the base default stylesheet for
     642      * the platform
     643      */
     644     public static void setDefaultUserAgentStylesheet(Stylesheet stylesheet) {
     645         defaultUserAgentStylesheet = stylesheet;
     646         if (defaultUserAgentStylesheet != null) {
     647             defaultUserAgentStylesheet.setOrigin(Stylesheet.Origin.USER_AGENT);
     648         }
     649         userAgentStylesheetsChanged();
     650     }
     651
     652     /*
     653      * If the userAgentStylesheets change, then all scenes are updated.
     654      */
     655     private static void userAgentStylesheetsChanged() {
     656
     657         final Iterator<Window> windows = Window.impl_getWindows();
     658         while (windows.hasNext()) {
     659             final Window window = windows.next();
     660             final Scene scene = window.getScene();
     661             if (scene == null) continue;
     662
     663             final Reference<StyleManager> styleManagerRef = managerReferenceMap.get(scene);
     664             final StyleManager styleManager = styleManagerRef.get();
     665             if (styleManager == null) continue;
     666
     667             styleManager.updateStylesheets();
     668             scene.getRoot().impl_reapplyCSS();
     669         }
     670
     671     }
     672
     673     ////////////////////////////////////////////////////////////////////////////
     674     //
     675     // Pseudo-class state bit map handling
     676     //
     677     ////////////////////////////////////////////////////////////////////////////
     678
     679     private static final Map<String,Long> pseudoclassMasks = new HashMap<String,Long>();
     680
     681     public static long getPseudoclassMask(String pclass) {
     682         Long mask = pseudoclassMasks.get(pclass);
     683         if (mask == null) {
     684             final int exp = pseudoclassMasks.size();
     685             mask = Long.valueOf(1L << exp); // same as Math.pow(2,exp)
     686             pseudoclassMasks.put(pclass,mask);
     687         }
     688         return mask.longValue();
     689     }
     690
     691     static long getPseudoclassMask(List<String> pseudoclasses) {
     692         long mask = 0;
     693
     694         final int max = pseudoclasses != null ? pseudoclasses.size() : -1;
     695         for (int n=0; n<max; n++) {
     696             long m = getPseudoclassMask(pseudoclasses.get(n));
     697             mask = mask | m;
     698         }
     699         return mask;
     700     }
     701
     702     public static List<String> getPseudoclassStrings(long mask) {
     703         if (mask == 0) return Collections.EMPTY_LIST;
     704
     705         Map<Long,String> stringMap = new HashMap<Long,String>();
     706         for (Entry<String,Long> entry : pseudoclassMasks.entrySet()) {
     707             stringMap.put(entry.getValue(), entry.getKey());
     708         }
     709         List<String> strings = new ArrayList<String>();
     710         for (long exp=0; exp < Long.SIZE; exp++) {
     711             long key = (1L << exp) & mask;
     712             if (key != 0) {
     713                 String value = stringMap.get(key);
     714                 if (value != null) strings.add(value);
     715             }
     716         }
     717         return strings;
     718     }
     719
     720     protected final void clearCache() {
     721         styleCache.clear();
     722         cacheMap.clear();
     723         smapCount = 0;
     724     }
     725
     726     private long nextSmapId() {
     727         return ++smapCount;
     728     }
     729
     730     abstract protected Key getKey(Node node);
     731
     732     /**
     733      * Returns a StyleHelper for the given Node, or null if there are no styles
     734      * for this node.
     735      */
     736     public final StyleHelper getStyleHelper(Node node) {
     737
     738         final Key key = getKey(node);
     739         Cache cache = cacheMap.get(key);
     740
     741         // If the cache is null, then we need to create a new Cache and
     742         // add it to the cache map
     743         if (cache == null) {
     744             // Construct the list of Rules that could possibly apply
     745             final List<Rule> rules = new ArrayList<Rule>();
     746
     747             /*
     748              * A Set of all the pseudoclass states which, if they change, need
     749              * to cause the Node to be set to UPDATE its CSS styles on the next
     750              * pulse. For a full explanation, see the StyleHelper's
     751              * mayImpactChildren variable.
     752              */
     753             long pseudoclassStateMask = 0;
     754
     755             // If the node can have children, look at the ancestors
     756             // in a compound selector for pseudoclassBits
     757             final boolean canHaveChildren = node instanceof Parent;
     758
     759             // It may be that there are no pseudoclassBits in the ancestor
     760             // selectors, but if one of the ancestor selectors might apply,
     761             // then the selector impacts its children.
     762             boolean impactsChildren = false;
     763
     764             //
     765             final String className = node.getClass().getName();
     766             final String id = node.getId();
     767             final List<String> styleClass = node.getStyleClass();
     768
     769             List<Stylesheet> stylesheetsToProcess = referencedStylesheets;
     770
     771             for (int i = 0, imax = stylesheetsToProcess.size(); i < imax; i++) {
     772                 final Stylesheet ss = stylesheetsToProcess.get(i);
     773
     774                 final List<Rule> stylesheetRules = ss != null ? ss.getRules() : null;
     775                 if (stylesheetRules == null || stylesheetRules.isEmpty()) {
     776                     continue;
     777                 }
     778
     779                 for (int j = 0, jmax = stylesheetRules.size(); j < jmax; j++) {
     780                     Rule rule = stylesheetRules.get(j);
     781                     boolean mightApply = rule.mightApply(className, id, styleClass);
     782                     if (mightApply) {
     783                         rules.add(rule);
     784                     }
     785
     786                     //
     787                     // The following logic used to be in a separate routine,
     788                     // but the need to flag compound selectors with no
     789                     // pseudoclass state that might impact the node's
     790                     // children encouraged moving it here.
     791                     //
     792
     793                     // The following loop creates the pseudoclassStateMask.
     794                     // For a leaf node, the loop will just gather the
     795                     // pseudoclass state bits. For a parent node, this loop
     796                     // looks at the ancestor selectors to see if the parent
     797                     // matches. If so, then the pseudoclass state from that
     798                     // parent will affect the styling of its child nodes.
     799                     // For example, in the selector A:foo B { }, whenever the
     800                     // "foo" pseudoclass of A changes it must notify child
     801                     // nodes of the change so that if one of them happens to
     802                     // be a B it can update itself accordingly. Since this
     803                     // can be quite expensive, we want to make sure we
     804                     // support this capability only for nodes that need it.
     805                     // This function determines whether this support is
     806                     // necessary (setting impactsChildren to true),
     807                     // and what pseudoclasses on the parent (A) that matter.
     808
     809                     // The node has to have rules that apply or be a Parent
     810                     // in order for the pseudoclass state to matter.
     811                     if (!mightApply && !canHaveChildren) {
     812                         continue;
     813                     }
     814
     815                     // For each selector, rule, stylesheet look for whether this Node
     816                     // is referenced in the ancestor part of the selector, and whether or
     817                     // not it also has pseudoclasses specified
     818                     final int smax = rule.selectors != null ? rule.selectors.size() : 0;
     819                     for (int s = 0; s < smax; s++) {
     820                         final Selector selector = rule.selectors.get(s);
     821                         if (selector instanceof CompoundSelector) {
     822
     823                             final CompoundSelector cs = (CompoundSelector) selector;
     824                             final List<SimpleSelector> csSelectors = cs.getSelectors();
     825
     826                             // if mightApply is true, then the right-most selector
     827                             // was matched and we just need the pseudoclass state
     828                             // from that selector.
     829                             if (mightApply) {
     830                                 final SimpleSelector simple =
     831                                         csSelectors.get(csSelectors.size() - 1);
     832                                 pseudoclassStateMask = pseudoclassStateMask
     833                                         | getPseudoclassMask(simple.getPseudoclasses());
     834
     835
     836                                 // Otherwise, the rule did not match but we need to check
     837                                 // to see if this node matches one of the ancestor selectors.
     838                                 // This only matters for nodes that can have children. You
     839                                 // wouldn't find a Rectangle, for example, as an ancestor
     840                                 // in a compound selector (you might, but the selector
     841                                 // wouldn't match anything)
     842                             } else if (canHaveChildren) {
     843                                 // only check the ancestor selectors. If we are here,
     844                                 // then we know mightApply is false, meaning the rule
     845                                 // does not apply to this node because this node does
     846                                 // not match the right-most selector.
     847                                 for (int sctr = 0, max_sctr = csSelectors.size() - 1; sctr < max_sctr; sctr++) {
     848                                     final SimpleSelector simple = csSelectors.get(sctr);
     849                                     if (simple.mightApply(className, id, styleClass)) {
     850                                         pseudoclassStateMask = pseudoclassStateMask
     851                                                 | getPseudoclassMask(simple.getPseudoclasses());
     852                                         impactsChildren = true;
     853                                     }
     854                                 }
     855                             }
     856                             // Not a compound selector. If the selector might apply,
     857                             // then save off the pseudoclass state. StyleHelper
     858                             // checks this to see if a pseudoclass matters or not.
     859                         } else if (mightApply) {
     860                             SimpleSelector simple = (SimpleSelector) selector;
     861                             pseudoclassStateMask = pseudoclassStateMask
     862                                     | getPseudoclassMask(simple.getPseudoclasses());
     863                         }
     864                     }
     865                 }
     866             }
     867
     868             //
     869             // regardless of whether or not the cache is shared, a Cache
     870             // object is still needed in order to do the lookup.
     871             //
     872             final Key newKey = key.dup();
     873
     874             cache = new Cache(rules, pseudoclassStateMask, impactsChildren);
     875             cacheMap.put(newKey, cache);
     876
     877         }
     878
     879         // the key is an instance variable and so we need to null the
     880         // key.styleClass to prevent holding a hard reference to the
     881         // styleClass (and its Node)
     882         key.styleClass = null;
     883
     884         // Return the style helper looked up by the cache. The cache will
     885         // create a style helper if necessary (and possible), so we don't
     886         // have to worry about that part.
     887         StyleMap smap = cache.getStyleMap(this, node);
     888         if (smap == null) {
     889             return null;
     890         }
     891
     892         StyleHelper helper =
     893                 StyleHelper.create(
     894                 node,
     895                 smap.map,
     896                 styleCache,
     897                 cache.pseudoclassStateMask,
     898                 smap.uniqueId);
     899
     900         return helper;
     901     }
     902
     903     ////////////////////////////////////////////////////////////////////////////
     904     //
     905     // CssError reporting
     906     //
     907     ////////////////////////////////////////////////////////////////////////////
     908
     909     private static ObservableList<CssError> errors = null;
     910     /**
     911      * Errors that may have occurred during css processing.
     912      * This list is null until errorsProperty() is called.
     913      * @return
     914      */
     915     public static ObservableList<CssError> errorsProperty() {
     916         if (errors == null) {
     917             errors = FXCollections.observableArrayList();
     918         }
     919         return errors;
     920     }
     921
     922     /**
     923      * Errors that may have occurred during css processing.
     924      * This list is null until errorsProperty() is called and is used
     925      * internally to figure out whether or  not anyone is interested in
     926      * receiving CssError.
     927      * Not meant for general use - call errorsProperty() instead.
     928      * @return
     929      */
     930     public static ObservableList<CssError> getErrors() {
     931         return errors;
     932     }
     933
     934     ////////////////////////////////////////////////////////////////////////////
     935     //
     936     // Inner classes
     937     //
     938     ////////////////////////////////////////////////////////////////////////////
     939
     940     //
     941     // Used by StyleHelper. The key uniquely identifies this style map.
     942     // These keys are used by StyleHelper in creating its StyleCacheKey.
     943     // The StyleCacheKey is used to lookup calculated values in cache.
     944     //
     945     static class StyleMap {
     946         final long uniqueId; // unique per container
     947         final Map<String, List<CascadingStyle>> map;
     948         private StyleMap(long key, Map<String, List<CascadingStyle>> map) {
     949             this.uniqueId = key;
     950             this.map  = map;
     951         }
     952
     953     }
     954
     955     /**
     956      * Creates and caches maps of styles, reusing them as often as practical.
     957      */
     958     protected static class Cache {
     959
     960         // this must be initialized to the appropriate possible rules when
     961         // the helper cache is created by the StylesheetContainer
     962         private final List<Rule> rules;
     963         private final long pseudoclassStateMask;
     964         private final boolean impactsChildren;
     965         private final Map<Long, StyleMap> cache;
     966
     967         Cache(List<Rule> rules, long pseudoclassStateMask, boolean impactsChildren) {
     968             this.rules = rules;
     969             this.pseudoclassStateMask = pseudoclassStateMask;
     970             this.impactsChildren = impactsChildren;
     971             this.cache = new HashMap<Long, StyleMap>();
     972         }
     973
     974         private StyleMap getStyleMap(StyleManager owner, Node node) {
     975
     976             // If this set of rules (which may be empty) impacts children,
     977             // then this node will get a StyleHelper.
     978             if (!impactsChildren) {
     979
     980                 // Since this set of rules does not impact children, this node
     981                 // will only get a StyleHelper if there are styles that might
     982                 // apply. There are styles that might apply if there are rules
     983                 // or if the node has an in-line style.
     984                 final String nodeStyle = node.getStyle();
     985                 final boolean hasStyle = nodeStyle != null && !nodeStyle.isEmpty();
     986
     987                 if (rules.isEmpty() && pseudoclassStateMask == 0 && hasStyle == false) {
     988                     boolean hasInheritAsDefault = false;
     989                     // TODO This is questionable as what happens when a node has no style helper and inherits styles
     990                     final List<StyleableProperty> styleables = node.impl_getStyleableProperties();
     991                     final int max = styleables != null ? styleables.size() : 0;
     992                     for (int i = 0; i < max; i++) {
     993                         if (styleables.get(i).isInherits()) {
     994                             hasInheritAsDefault = true;
     995                             break;
     996                         }
     997                     }
     998                     // if we have no rules and no inherited properties we don't need a StyleHelper
     999                     if (! hasInheritAsDefault) return null;
    1000                 }
    1001             }
    1002
    1003             // To lookup from the cache, we construct a key from a Long
    1004             // where the rules that apply to this particular node are
    1005             // represented by bits on the Long.
    1006             long key = 0;
    1007             long mask = 1;
    1008             for (int i = 0, imax = rules.size(); i < imax; i++) {
    1009                 Rule rule = rules.get(i);
    1010                 if (rule.applies(node)) {
    1011                     key = key | mask;
    1012                 }
    1013                 mask = mask << 1;
    1014             }
    1015
    1016             final Long keyObj = Long.valueOf(key);
    1017             if (cache.containsKey(keyObj)) {
    1018                 final StyleMap styleMap = cache.get(keyObj);
    1019                 return styleMap;
    1020             }
    1021
    1022             // We need to create a new StyleHelper, add it to the cache,
    1023             // and then return it.
    1024             final List<CascadingStyle> styles = getStyles(node);
    1025             final Map<String, List<CascadingStyle>> smap =
    1026                 new HashMap<String, List<CascadingStyle>>();
    1027             final int max = styles != null ? styles.size() : 0;
    1028             for (int i=0; i<max; i++) {
    1029                 final CascadingStyle style = styles.get(i);
    1030                 final String property = style.getProperty();
    1031                 // This is carefully written to use the minimal amount of hashing.
    1032                 List<CascadingStyle> list = smap.get(property);
    1033                 if (list == null) {
    1034                     list = new ArrayList<CascadingStyle>(5);
    1035                     smap.put(property, list);
    1036                 }
    1037                 list.add(style);
    1038             }
    1039
    1040             final StyleMap styleMap = new StyleMap(owner.nextSmapId(), smap);
    1041             cache.put(keyObj, styleMap);
    1042             return styleMap;
    1043         }
    1044
    1045         /**
    1046          * Looks up all the styles for this Node. This function takes no advantage
    1047          * of any caches but simply does a complete fresh lookup. The styles
    1048          * returned are sorted such that the most specific style is first.
    1049          */
    1050         private List<CascadingStyle> getStyles(Node node) {
    1051             // The priority of stylesheets from the CSS spec is
    1052             // (1) user agent declarations
    1053             // (2) user normal declarations
    1054             // (3) author normal declarations
    1055             // (4) author important declarations
    1056             // (5) user important declarations
    1057             // we have the correct order here for 1,2,3 and the 4 & 5
    1058             // we handle when sorting the styles
    1059
    1060             if (rules == null || rules.isEmpty()) return null;
    1061
    1062             List<CascadingStyle> styles = new ArrayList<CascadingStyle>(rules.size());
    1063                 // Order in which the declaration of a Style appears (see below)
    1064                 int ordinal = 0;
    1065                 // For each declaration in the matching rules, create a Style object
    1066                 //final List<Style> styles = new ArrayList<Style>();
    1067                 for (int i = 0, imax = rules.size(); i < imax; i++) {
    1068                     final Rule rule = rules.get(i);
    1069                     List<Match> matches = rule.matches(node);
    1070                     for (int j = 0, jmax = matches.size(); j < jmax; j++) {
    1071                         final Match match = matches.get(j);
    1072                         if (match == null) continue;
    1073                         for (int k = 0, kmax = rule.declarations.size(); k < kmax; k++) {
    1074                             final Declaration decl = rule.declarations.get(k);
    1075
    1076                             final CascadingStyle s = new CascadingStyle(
    1077                                 new Style(match.selector, decl),
    1078                                 match.pseudoclasses,
    1079                                 match.specificity,
    1080                                 // ordinal increments at declaration level since
    1081                                 // there may be more than one declaration for the
    1082                                 // same attribute within a rule or within a stylesheet
    1083                                 ordinal++
    1084                             );
    1085
    1086                         styles.add(s);
    1087                         }
    1088                     }
    1089                 }
    1090
    1091                 // return sorted styles
    1092             Collections.sort(styles);
    1093             return styles;
    1094             }
    1095
    1096         }
    1097
    1098     /**
    1099      * The key used in the cacheMap of the StylesheetContainer
    1100      */
    1101     static class Key {
    1102
    1103         public Key() {
    1104         }
    1105
    1106         protected Key dup() {
    1107
    1108             Key key = new Key();
    1109             key.className  = className;
    1110             key.id = id;
    1111             final int nElements = styleClass.size();
    1112             key.styleClass = new ArrayList<String>(nElements);
    1113             for (int n = 0; n < nElements; n++) {
    1114                 key.styleClass.add(styleClass.get(n));
    1115             }
    1116
    1117             return key;
    1118         }
    1119
    1120         // note that the class name here is the *full* class name, such as
    1121         // javafx.scene.control.Button. We only bother parsing this down to the
    1122         // last part when doing matching against selectors, and so want to avoid
    1123         // having to do a bunch of preliminary parsing in places where it isn't
    1124         // necessary.
    1125         protected String className;
    1126         protected String id;
    1127         protected List<String> styleClass;
    1128
    1129         @Override
    1130         public boolean equals(Object o) {
    1131             if (this == o) return true;
    1132             if (o instanceof Key) {
    1133                 Key other = (Key)o;
    1134                 boolean eq =
    1135                         className.equals(other.className)
    1136                     && (   (id == null && other.id == null)
    1137                         || (id != null && id.equals(other.id))
    1138                        )
    1139                     && (   (styleClass == null && other.styleClass == null)
    1140                         || (styleClass != null && styleClass.containsAll(other.styleClass))
    1141                        );
    1142                 return eq;
    1143             }
    1144             return false;
    1145         }
    1146
    1147         @Override
    1148         public int hashCode() {
    1149             int hash = className.hashCode();
    1150             hash = 31 * (hash + ((id == null || id.isEmpty()) ? 1231 : id.hashCode()));
    1151             hash = 31 * (hash + ((styleClass == null || styleClass.isEmpty()) ? 1237 : styleClass.hashCode()));
    1152             return hash;
    1153         }
    1154
    1155         @Override
    1156         public String toString() {
    1157             return "Key ["+className+", "+String.valueOf(id)+", "+String.valueOf(styleClass)+"]";
    1158         }
    1159
    1160     }
    1161
    1162 }