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 com.sun.javafx.css; 27 28 import com.sun.javafx.scene.NodeHelper; 29 import com.sun.javafx.scene.ParentHelper; 30 import javafx.application.Application; 31 import javafx.collections.FXCollections; 32 import javafx.collections.ListChangeListener.Change; 33 import javafx.collections.ObservableList; 34 import javafx.css.CssParser; 35 import javafx.css.FontFace; 36 import javafx.css.PseudoClass; 37 import javafx.css.Rule; 38 import javafx.css.Selector; 39 import javafx.css.StyleOrigin; 40 import javafx.css.Styleable; 41 import javafx.css.StyleConverter; 42 import javafx.css.Stylesheet; 43 import javafx.scene.Node; 44 import javafx.scene.Parent; 45 import javafx.scene.Scene; 46 import javafx.scene.SubScene; 47 import javafx.scene.image.Image; 48 import javafx.scene.layout.Region; 49 import javafx.scene.text.Font; 50 import javafx.stage.Window; 51 import sun.util.logging.PlatformLogger; 52 import sun.util.logging.PlatformLogger.Level; 53 54 import java.io.FileNotFoundException; 55 import java.io.FilePermission; 56 import java.io.IOException; 57 import java.io.InputStream; 58 import java.lang.ref.Reference; 59 import java.lang.ref.WeakReference; 60 import java.lang.reflect.InvocationTargetException; 61 import java.lang.reflect.Method; 62 import java.net.MalformedURLException; 63 import java.net.URI; 64 import java.net.URISyntaxException; 65 import java.net.URL; 66 import java.security.AccessControlContext; 67 import java.security.AccessController; 68 import java.security.DigestInputStream; 69 import java.security.MessageDigest; 70 import java.security.NoSuchAlgorithmException; 71 import java.security.PermissionCollection; 72 import java.security.PrivilegedAction; 73 import java.security.PrivilegedActionException; 74 import java.security.PrivilegedExceptionAction; 75 import java.security.ProtectionDomain; 76 import java.util.ArrayList; 77 import java.util.Arrays; 78 import java.util.Collections; 79 import java.util.HashMap; 80 import java.util.Iterator; 81 import java.util.List; 82 import java.util.Map; 83 import java.util.Map.Entry; 84 import java.util.Set; 85 import java.util.WeakHashMap; 86 import java.util.jar.JarEntry; 87 import java.util.jar.JarFile; 88 89 /** 90 * Contains the stylesheet state for a single scene. This includes both the 91 * Stylesheets defined on the Scene itself as well as a map of stylesheets for 92 * "style"s defined on the Node itself. These containers are kept in the 93 * containerMap, key'd by the Scene to which they belong. <p> One of the key 94 * responsibilities of the StylesheetContainer is to create and maintain an 95 * admittedly elaborate series of caches so as to minimize the amount of time it 96 * takes to match a Node to its eventual StyleHelper, and to reuse the 97 * StyleHelper as much as possible. <p> Initially, the cache is empty. It is 98 * recreated whenever the userStylesheets on the container change, or whenever 99 * the userAgentStylesheet changes. The cache is built up as nodes are looked 100 * for, and thus there is some overhead associated with the first lookup but 101 * which is then not repeated for subsequent lookups. <p> The cache system used 102 * is a two level cache. The first level cache simply maps the 103 * classname/id/styleclass combination of the request node to a 2nd level cache. 104 * If the node has "styles" specified then we still use this 2nd level cache, 105 * but must combine its selectors with the selectors specified in "styles" and perform 106 * more work to cascade properly. <p> The 2nd level cache contains a data 107 * structure called the Cache. The Cache contains an ordered sequence of Rules, 108 * a Long, and a Map. The ordered sequence of selectors are the selectors that *may* 109 * match a node with the given classname, id, and style class. For example, 110 * selectors which may apply are any selector where the simple selector of the selector 111 * contains a reference to the id, style class, or classname of the Node, or a 112 * compound selector who's "descendant" part is a simple selector which contains 113 * a reference to the id, style class, or classname of the Node. <p> During 114 * lookup, we will iterate over all the potential selectors and discover if they 115 * apply to this particular node. If so, then we toggle a bit position in the 116 * Long corresponding to the position of the selector that matched. This long then 117 * becomes our key into the final map. <p> Once we have established our key, we 118 * will visit the map and look for an existing StyleHelper. If we find a 119 * StyleHelper, then we will return it. If not, then we will take the Rules that 120 * matched and construct a new StyleHelper from their various parts. <p> This 121 * system, while elaborate, also provides for numerous fast paths and sharing of 122 * data structures which should dramatically reduce the memory and runtime 123 * performance overhead associated with CSS by reducing the matching overhead 124 * and caching as much as possible. We make no attempt to use weak references 125 * here, so if memory issues result one work around would be to toggle the root 126 * user agent stylesheet or stylesheets on the scene to cause the cache to be 127 * flushed. 128 */ 129 130 final public class StyleManager { 131 132 /** 133 * Global lock object for the StyleManager. StyleManager is a singleton, 134 * which loads CSS stylesheets and manages style caches for all scenes. 135 * It needs to be thread-safe since a Node or Scene can be constructed and 136 * load its stylesheets on an arbitrary thread, meaning that multiple Scenes 137 * can load or apply their stylesheets concurrently. The global lock is used 138 * to serialize access to the various state in the StyleManager. 139 */ 140 private static final Object styleLock = new Object(); 141 142 private static PlatformLogger LOGGER; 143 private static PlatformLogger getLogger() { 144 if (LOGGER == null) { 145 LOGGER = com.sun.javafx.util.Logging.getCSSLogger(); 146 } 147 return LOGGER; 148 } 149 150 private static class InstanceHolder { 151 final static StyleManager INSTANCE = new StyleManager(); 152 } 153 /** 154 * Return the StyleManager instance. 155 */ 156 public static StyleManager getInstance() { 157 return InstanceHolder.INSTANCE; 158 } 159 160 private StyleManager() { 161 } 162 163 /** 164 * A map from a parent to its style cache. The parent is either a Scene root, or a 165 * Parent with author stylesheets. If a Scene or Parent is removed from the scene, 166 * it's cache is annihilated. 167 */ 168 // public for testing 169 public static final Map<Parent, CacheContainer> cacheContainerMap = new WeakHashMap<>(); 170 171 // package for testing 172 CacheContainer getCacheContainer(Styleable styleable, SubScene subScene) { 173 174 if (styleable == null && subScene == null) return null; 175 176 Parent root = null; 177 178 if (subScene != null) { 179 root = subScene.getRoot(); 180 181 } else if (styleable instanceof Node) { 182 183 Node node = (Node)styleable; 184 Scene scene = node.getScene(); 185 if (scene != null) root = scene.getRoot(); 186 187 } else if (styleable instanceof Window) { 188 // this catches the PopupWindow case 189 Scene scene = ((Window)styleable).getScene(); 190 if (scene != null) root = scene.getRoot(); 191 } 192 // todo: what other Styleables need to be handled here? 193 194 if (root == null) return null; 195 196 synchronized (styleLock) { 197 CacheContainer container = cacheContainerMap.get(root); 198 if (container == null) { 199 container = new CacheContainer(); 200 cacheContainerMap.put(root, container); 201 } 202 203 return container; 204 } 205 } 206 207 /** 208 * StyleHelper uses this cache but it lives here so it can be cleared 209 * when style-sheets change. 210 */ 211 public StyleCache getSharedCache(Styleable styleable, SubScene subScene, StyleCache.Key key) { 212 213 CacheContainer container = getCacheContainer(styleable, subScene); 214 if (container == null) return null; 215 216 Map<StyleCache.Key,StyleCache> styleCache = container.getStyleCache(); 217 if (styleCache == null) return null; 218 219 StyleCache sharedCache = styleCache.get(key); 220 if (sharedCache == null) { 221 sharedCache = new StyleCache(); 222 styleCache.put(new StyleCache.Key(key), sharedCache); 223 } 224 225 return sharedCache; 226 } 227 228 public StyleMap getStyleMap(Styleable styleable, SubScene subScene, int smapId) { 229 230 if (smapId == -1) return StyleMap.EMPTY_MAP; 231 232 CacheContainer container = getCacheContainer(styleable, subScene); 233 if (container == null) return StyleMap.EMPTY_MAP; 234 235 return container.getStyleMap(smapId); 236 } 237 238 /** 239 * A list of user-agent stylesheets from Scene or SubScene. 240 * The order of the entries in this list does not matter since a Scene or 241 * SubScene will only have zero or one user-agent stylesheets. 242 */ 243 // public for testing 244 public final List<StylesheetContainer> userAgentStylesheetContainers = new ArrayList<>(); 245 /** 246 * A list of user-agent stylesheet urls from calling setDefaultUserAgentStylesheet and 247 * addUserAgentStylesheet. The order of entries this list matters. The zeroth element is 248 * _the_ platform default. 249 */ 250 // public for testing 251 public final List<StylesheetContainer> platformUserAgentStylesheetContainers = new ArrayList<>(); 252 // public for testing 253 public boolean hasDefaultUserAgentStylesheet = false; 254 255 //////////////////////////////////////////////////////////////////////////// 256 // 257 // stylesheet handling 258 // 259 //////////////////////////////////////////////////////////////////////////// 260 261 /* 262 * A container for stylesheets and the Parents or Scenes that use them. 263 * If a stylesheet is removed, then all other Parents or Scenes 264 * that use that stylesheet should get new styles if the 265 * stylesheet is added back in since the stylesheet may have been 266 * removed and re-added because it was edited (typical of SceneBuilder). 267 * This container provides the hooks to get back to those Parents or Scenes. 268 * 269 * StylesheetContainer<Parent> are created and added to stylesheetContainerMap 270 * in the method gatherParentStylesheets. 271 * 272 * StylesheetContainer<Scene> are created and added to sceneStylesheetMap in 273 * the method updateStylesheets 274 */ 275 // package for testing 276 static class StylesheetContainer { 277 278 // the stylesheet uri 279 final String fname; 280 // the parsed stylesheet so we don't reparse for every parent that uses it 281 final Stylesheet stylesheet; 282 // the parents or scenes that use this stylesheet. Typically, this list 283 // should be very small. 284 final SelectorPartitioning selectorPartitioning; 285 286 // who uses this stylesheet? 287 final RefList<Parent> parentUsers; 288 289 // RT-24516 -- cache images coming from this stylesheet. 290 // This just holds a hard reference to the image. 291 final List<Image> imageCache; 292 293 final int hash; 294 final byte[] checksum; 295 boolean checksumInvalid = false; 296 297 StylesheetContainer(String fname, Stylesheet stylesheet) { 298 this(fname, stylesheet, stylesheet != null ? calculateCheckSum(stylesheet.getUrl()) : new byte[0]); 299 } 300 301 StylesheetContainer(String fname, Stylesheet stylesheet, byte[] checksum) { 302 303 this.fname = fname; 304 hash = (fname != null) ? fname.hashCode() : 127; 305 306 this.stylesheet = stylesheet; 307 if (stylesheet != null) { 308 selectorPartitioning = new SelectorPartitioning(); 309 final List<Rule> rules = stylesheet.getRules(); 310 final int rMax = rules == null || rules.isEmpty() ? 0 : rules.size(); 311 for (int r=0; r<rMax; r++) { 312 313 final Rule rule = rules.get(r); 314 // final List<Selector> selectors = rule.getUnobservedSelectorList(); 315 final List<Selector> selectors = rule.getSelectors(); 316 final int sMax = selectors == null || selectors.isEmpty() ? 0 : selectors.size(); 317 for (int s=0; s < sMax; s++) { 318 319 final Selector selector = selectors.get(s); 320 selectorPartitioning.partition(selector); 321 322 } 323 } 324 325 } else { 326 selectorPartitioning = null; 327 } 328 329 this.parentUsers = new RefList<Parent>(); 330 331 // this just holds a hard reference to the image 332 this.imageCache = new ArrayList<Image>(); 333 334 this.checksum = checksum; 335 } 336 337 void invalidateChecksum() { 338 // if checksum is byte[0], then it is forever valid. 339 checksumInvalid = checksum.length > 0; 340 } 341 @Override 342 public int hashCode() { 343 return hash; 344 } 345 346 @Override 347 public boolean equals(Object obj) { 348 if (obj == null) { 349 return false; 350 } 351 if (getClass() != obj.getClass()) { 352 return false; 353 } 354 final StylesheetContainer other = (StylesheetContainer) obj; 355 if ((this.fname == null) ? (other.fname != null) : !this.fname.equals(other.fname)) { 356 return false; 357 } 358 return true; 359 } 360 361 @Override public String toString() { 362 return fname; 363 } 364 365 } 366 367 /* 368 * A list that holds references. Used by StylesheetContainer. 369 */ 370 // package for testing 371 static class RefList<K> { 372 373 final List<Reference<K>> list = new ArrayList<Reference<K>>(); 374 375 void add(K key) { 376 377 for (int n=list.size()-1; 0<=n; --n) { 378 final Reference<K> ref = list.get(n); 379 final K k = ref.get(); 380 if (k == null) { 381 // stale reference, remove it. 382 list.remove(n); 383 } else { 384 // already have it, bail 385 if (k == key) { 386 return; 387 } 388 } 389 } 390 // not found, add it. 391 list.add(new WeakReference<K>(key)); 392 } 393 394 void remove(K key) { 395 396 for (int n=list.size()-1; 0<=n; --n) { 397 final Reference<K> ref = list.get(n); 398 final K k = ref.get(); 399 if (k == null) { 400 // stale reference, remove it. 401 list.remove(n); 402 } else { 403 // already have it, bail 404 if (k == key) { 405 list.remove(n); 406 return; 407 } 408 } 409 } 410 } 411 412 // for unit testing 413 boolean contains(K key) { 414 for (int n=list.size()-1; 0<=n; --n) { 415 final Reference<K> ref = list.get(n); 416 final K k = ref.get(); 417 if (k == key) { 418 return true; 419 } 420 } 421 return false; 422 } 423 } 424 425 /** 426 * A map from String => Stylesheet. If a stylesheet for the 427 * given URL has already been loaded then we'll simply reuse the stylesheet 428 * rather than loading a duplicate. 429 * This list is for author stylesheets and not for user-agent stylesheets. User-agent 430 * stylesheets are either platformUserAgentStylesheetContainers or userAgentStylesheetContainers 431 */ 432 // public for unit testing 433 public final Map<String,StylesheetContainer> stylesheetContainerMap = new HashMap<>(); 434 435 436 /** 437 * called from Window when the scene is closed. 438 */ 439 public void forget(final Scene scene) { 440 441 if (scene == null) return; 442 443 forget(scene.getRoot()); 444 445 synchronized (styleLock) { 446 // 447 // if this scene has user-agent stylesheets, clean up the userAgentStylesheetContainers list 448 // 449 String sceneUserAgentStylesheet = null; 450 if ((scene.getUserAgentStylesheet() != null) && 451 (!(sceneUserAgentStylesheet = scene.getUserAgentStylesheet().trim()).isEmpty())) { 452 453 for(int n=userAgentStylesheetContainers.size()-1; 0<=n; --n) { 454 StylesheetContainer container = userAgentStylesheetContainers.get(n); 455 if (sceneUserAgentStylesheet.equals(container.fname)) { 456 container.parentUsers.remove(scene.getRoot()); 457 if (container.parentUsers.list.size() == 0) { 458 userAgentStylesheetContainers.remove(n); 459 } 460 } 461 } 462 } 463 464 // 465 // remove any parents belonging to this scene from the stylesheetContainerMap 466 // 467 Set<Entry<String,StylesheetContainer>> stylesheetContainers = stylesheetContainerMap.entrySet(); 468 Iterator<Entry<String,StylesheetContainer>> iter = stylesheetContainers.iterator(); 469 470 while(iter.hasNext()) { 471 472 Entry<String,StylesheetContainer> entry = iter.next(); 473 StylesheetContainer container = entry.getValue(); 474 475 Iterator<Reference<Parent>> parentIter = container.parentUsers.list.iterator(); 476 while (parentIter.hasNext()) { 477 478 Reference<Parent> ref = parentIter.next(); 479 Parent _parent = ref.get(); 480 481 if (_parent == null || _parent.getScene() == scene || _parent.getScene() == null) { 482 ref.clear(); 483 parentIter.remove(); 484 } 485 } 486 487 if (container.parentUsers.list.isEmpty()) { 488 iter.remove(); 489 } 490 } 491 } 492 } 493 494 /** 495 * called from Scene's stylesheets property's onChanged method 496 */ 497 public void stylesheetsChanged(Scene scene, Change<String> c) { 498 499 synchronized (styleLock) { 500 // Clear the cache so the cache will be rebuilt. 501 Set<Entry<Parent,CacheContainer>> entrySet = cacheContainerMap.entrySet(); 502 for(Entry<Parent,CacheContainer> entry : entrySet) { 503 Parent parent = entry.getKey(); 504 CacheContainer container = entry.getValue(); 505 if (parent.getScene() == scene) { 506 container.clearCache(); 507 } 508 509 } 510 511 c.reset(); 512 while(c.next()) { 513 if (c.wasRemoved()) { 514 for (String fname : c.getRemoved()) { 515 stylesheetRemoved(scene, fname); 516 517 StylesheetContainer stylesheetContainer = stylesheetContainerMap.get(fname); 518 if (stylesheetContainer != null) { 519 stylesheetContainer.invalidateChecksum(); 520 } 521 522 } 523 } 524 } 525 } 526 } 527 528 private void stylesheetRemoved(Scene scene, String fname) { 529 stylesheetRemoved(scene.getRoot(), fname); 530 } 531 532 /** 533 * Called from Parent's scenesChanged method when the Parent's scene is set to null. 534 * @param parent The Parent being removed from the scene-graph 535 */ 536 public void forget(Parent parent) { 537 538 if (parent == null) return; 539 540 synchronized (styleLock) { 541 // RT-34863 - clean up CSS cache when Parent is removed from scene-graph 542 CacheContainer removedContainer = cacheContainerMap.remove(parent); 543 if (removedContainer != null) { 544 removedContainer.clearCache(); 545 } 546 547 final List<String> stylesheets = parent.getStylesheets(); 548 if (stylesheets != null && !stylesheets.isEmpty()) { 549 for (String fname : stylesheets) { 550 stylesheetRemoved(parent, fname); 551 } 552 } 553 554 Iterator<Entry<String,StylesheetContainer>> containerIterator = stylesheetContainerMap.entrySet().iterator(); 555 while (containerIterator.hasNext()) { 556 Entry<String,StylesheetContainer> entry = containerIterator.next(); 557 StylesheetContainer container = entry.getValue(); 558 container.parentUsers.remove(parent); 559 if (container.parentUsers.list.isEmpty()) { 560 561 containerIterator.remove(); 562 563 if (container.selectorPartitioning != null) { 564 container.selectorPartitioning.reset(); 565 } 566 567 568 // clean up image cache by removing images from the cache that 569 // might have come from this stylesheet 570 final String fname = container.fname; 571 cleanUpImageCache(fname); 572 } 573 } 574 575 // Do not iterate over children since this method will be called on each from Parent#scenesChanged 576 } 577 } 578 579 /** 580 * called from Parent's stylesheets property's onChanged method 581 */ 582 public void stylesheetsChanged(Parent parent, Change<String> c) { 583 synchronized (styleLock) { 584 c.reset(); 585 while(c.next()) { 586 if (c.wasRemoved()) { 587 for (String fname : c.getRemoved()) { 588 stylesheetRemoved(parent, fname); 589 590 StylesheetContainer stylesheetContainer = stylesheetContainerMap.get(fname); 591 if (stylesheetContainer != null) { 592 stylesheetContainer.invalidateChecksum(); 593 } 594 } 595 } 596 } 597 } 598 } 599 600 private void stylesheetRemoved(Parent parent, String fname) { 601 602 synchronized (styleLock) { 603 StylesheetContainer stylesheetContainer = stylesheetContainerMap.get(fname); 604 605 if (stylesheetContainer == null) return; 606 607 stylesheetContainer.parentUsers.remove(parent); 608 609 if (stylesheetContainer.parentUsers.list.isEmpty()) { 610 removeStylesheetContainer(stylesheetContainer); 611 } 612 } 613 } 614 615 /** 616 * called from Window when the scene is closed. 617 */ 618 public void forget(final SubScene subScene) { 619 620 if (subScene == null) return; 621 final Parent subSceneRoot = subScene.getRoot(); 622 623 if (subSceneRoot == null) return; 624 forget(subSceneRoot); 625 626 synchronized (styleLock) { 627 // 628 // if this scene has user-agent stylesheets, clean up the userAgentStylesheetContainers list 629 // 630 String sceneUserAgentStylesheet = null; 631 if ((subScene.getUserAgentStylesheet() != null) && 632 (!(sceneUserAgentStylesheet = subScene.getUserAgentStylesheet().trim()).isEmpty())) { 633 634 Iterator<StylesheetContainer> iterator = userAgentStylesheetContainers.iterator(); 635 while(iterator.hasNext()) { 636 StylesheetContainer container = iterator.next(); 637 if (sceneUserAgentStylesheet.equals(container.fname)) { 638 container.parentUsers.remove(subScene.getRoot()); 639 if (container.parentUsers.list.size() == 0) { 640 iterator.remove(); 641 } 642 } 643 } 644 } 645 646 // 647 // remove any parents belonging to this SubScene from the stylesheetContainerMap 648 // 649 // copy the list to avoid concurrent mod. 650 List<StylesheetContainer> stylesheetContainers = new ArrayList<>(stylesheetContainerMap.values()); 651 652 Iterator<StylesheetContainer> iter = stylesheetContainers.iterator(); 653 654 while(iter.hasNext()) { 655 656 StylesheetContainer container = iter.next(); 657 658 Iterator<Reference<Parent>> parentIter = container.parentUsers.list.iterator(); 659 while (parentIter.hasNext()) { 660 661 final Reference<Parent> ref = parentIter.next(); 662 final Parent _parent = ref.get(); 663 664 if (_parent != null) { 665 // if this stylesheet refererent is a child of this subscene, nuke it. 666 Parent p = _parent; 667 while (p != null) { 668 if (subSceneRoot == p.getParent()) { 669 ref.clear(); 670 parentIter.remove(); 671 forget(_parent); // _parent, not p! 672 break; 673 } 674 p = p.getParent(); 675 } 676 } 677 } 678 679 // forget(_parent) will remove the container if the parentUser's list is empty 680 // if (container.parentUsers.list.isEmpty()) { 681 // iter.remove(); 682 // } 683 } 684 } 685 686 } 687 688 private void removeStylesheetContainer(StylesheetContainer stylesheetContainer) { 689 690 if (stylesheetContainer == null) return; 691 692 synchronized (styleLock) { 693 final String fname = stylesheetContainer.fname; 694 695 stylesheetContainerMap.remove(fname); 696 697 if (stylesheetContainer.selectorPartitioning != null) { 698 stylesheetContainer.selectorPartitioning.reset(); 699 } 700 701 // if container has no references, then remove it 702 for(Entry<Parent,CacheContainer> entry : cacheContainerMap.entrySet()) { 703 704 CacheContainer container = entry.getValue(); 705 if (container == null || container.cacheMap == null || container.cacheMap.isEmpty()) { 706 continue; 707 } 708 709 List<List<String>> entriesToRemove = new ArrayList<>(); 710 711 for (Entry<List<String>, Map<Key,Cache>> cacheMapEntry : container.cacheMap.entrySet()) { 712 List<String> cacheMapKey = cacheMapEntry.getKey(); 713 if (cacheMapKey != null ? cacheMapKey.contains(fname) : fname == null) { 714 entriesToRemove.add(cacheMapKey); 715 } 716 } 717 718 if (!entriesToRemove.isEmpty()) { 719 for (List<String> cacheMapKey : entriesToRemove) { 720 Map<Key,Cache> cacheEntry = container.cacheMap.remove(cacheMapKey); 721 if (cacheEntry != null) { 722 cacheEntry.clear(); 723 } 724 } 725 } 726 } 727 728 // clean up image cache by removing images from the cache that 729 // might have come from this stylesheet 730 cleanUpImageCache(fname); 731 732 final List<Reference<Parent>> parentList = stylesheetContainer.parentUsers.list; 733 734 for (int n=parentList.size()-1; 0<=n; --n) { 735 736 final Reference<Parent> ref = parentList.remove(n); 737 final Parent parent = ref.get(); 738 ref.clear(); 739 if (parent == null || parent.getScene() == null) { 740 continue; 741 } 742 743 // 744 // tell parent it needs to reapply css 745 // No harm is done if parent is in a scene that has had 746 // NodeHelper.reapplyCSS called on the root. 747 // 748 NodeHelper.reapplyCSS(parent); 749 } 750 } 751 } 752 753 //////////////////////////////////////////////////////////////////////////// 754 // 755 // Image caching 756 // 757 //////////////////////////////////////////////////////////////////////////// 758 759 private final Map<String,Image> imageCache = new HashMap<String,Image>(); 760 761 public Image getCachedImage(String url) { 762 763 synchronized (styleLock) { 764 Image image = null; 765 if (imageCache.containsKey(url)) { 766 767 image = imageCache.get(url); 768 769 } else { 770 771 try { 772 773 image = new Image(url); 774 775 // RT-31865 776 if (image.isError()) { 777 778 final PlatformLogger logger = getLogger(); 779 if (logger != null && logger.isLoggable(Level.WARNING)) { 780 logger.warning("Error loading image: " + url); 781 } 782 783 image = null; 784 } 785 786 imageCache.put(url, image); 787 788 } catch (IllegalArgumentException iae) { 789 // url was empty! 790 final PlatformLogger logger = getLogger(); 791 if (logger != null && logger.isLoggable(Level.WARNING)) { 792 logger.warning(iae.getLocalizedMessage()); 793 } 794 795 } catch (NullPointerException npe) { 796 // url was null! 797 final PlatformLogger logger = getLogger(); 798 if (logger != null && logger.isLoggable(Level.WARNING)) { 799 logger.warning(npe.getLocalizedMessage()); 800 } 801 } 802 } 803 804 return image; 805 } 806 } 807 808 private void cleanUpImageCache(String imgFname) { 809 810 synchronized (styleLock) { 811 if (imgFname == null && imageCache.isEmpty()) return; 812 813 final String fname = imgFname.trim(); 814 if (fname.isEmpty()) return; 815 816 int len = fname.lastIndexOf('/'); 817 final String path = (len > 0) ? fname.substring(0,len) : fname; 818 final int plen = path.length(); 819 820 final String[] entriesToRemove = new String[imageCache.size()]; 821 int count = 0; 822 823 final Set<Entry<String, Image>> entrySet = imageCache.entrySet(); 824 for(Entry<String, Image> entry : entrySet) { 825 826 final String key = entry.getKey(); 827 len = key.lastIndexOf('/'); 828 final String kpath = (len > 0) ? key.substring(0, len) : key; 829 final int klen = kpath.length(); 830 831 // if the longer path begins with the shorter path, 832 // then assume the image came from this path. 833 boolean match = (klen > plen) ? kpath.startsWith(path) : path.startsWith(kpath); 834 if (match) entriesToRemove[count++] = key; 835 } 836 837 for (int n=0; n<count; n++) { 838 Image img = imageCache.remove(entriesToRemove[n]); 839 } 840 } 841 } 842 843 //////////////////////////////////////////////////////////////////////////// 844 // 845 // Stylesheet loading 846 // 847 //////////////////////////////////////////////////////////////////////////// 848 849 850 private static final String skinPrefix = "com/sun/javafx/scene/control/skin/"; 851 private static final String skinUtilsClassName = "com.sun.javafx.scene.control.skin.Utils"; 852 853 private static URL getURL(final String str) { 854 855 // Note: this code is duplicated, more or less, in URLConverter 856 857 if (str == null || str.trim().isEmpty()) return null; 858 859 try { 860 861 URI uri = new URI(str.trim()); 862 863 // if url doesn't have a scheme 864 if (uri.isAbsolute() == false) { 865 866 // FIXME: JIGSAW -- move this into a utility method, since it will 867 // likely be needed elsewhere (e.g., in URLConverter) 868 if (str.startsWith(skinPrefix) && 869 (str.endsWith(".css") || str.endsWith(".bss"))) { 870 871 try { 872 ClassLoader cl = StyleManager.class.getClassLoader(); 873 Class<?> clz = Class.forName(skinUtilsClassName, true, cl); 874 Method m_getResource = clz.getMethod("getResource", String.class); 875 return (URL)m_getResource.invoke(null, str.substring(skinPrefix.length())); 876 } catch (ClassNotFoundException 877 | NoSuchMethodException 878 | IllegalAccessException 879 | InvocationTargetException ex) { 880 ex.printStackTrace(); 881 return null; 882 } 883 } 884 885 final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); 886 final String path = uri.getPath(); 887 888 URL resource = null; 889 890 // FIXME: JIGSAW -- The following will only find resources not in a module 891 if (path.startsWith("/")) { 892 resource = contextClassLoader.getResource(path.substring(1)); 893 } else { 894 resource = contextClassLoader.getResource(path); 895 } 896 897 return resource; 898 } 899 900 // else, url does have a scheme 901 return uri.toURL(); 902 903 } catch (MalformedURLException malf) { 904 // Do not log exception here - caller will handle null return. 905 // For example, we might be looking for a .bss that doesn't exist 906 return null; 907 } catch (URISyntaxException urise) { 908 return null; 909 } 910 } 911 912 // Calculate checksum for stylesheet file. Return byte[0] if checksum could not be calculated. 913 static byte[] calculateCheckSum(String fname) { 914 915 if (fname == null || fname.isEmpty()) return new byte[0]; 916 917 try { 918 final URL url = getURL(fname); 919 920 // We only care about stylesheets from file: URLs. 921 if (url != null && "file".equals(url.getProtocol())) { 922 923 // not looking for security, just a checksum. MD5 should be faster than SHA 924 try (final InputStream stream = url.openStream(); 925 final DigestInputStream dis = new DigestInputStream(stream, MessageDigest.getInstance("MD5")); ) { 926 dis.getMessageDigest().reset(); 927 while (dis.read() != -1) { /* empty loop body is intentional */ } 928 return dis.getMessageDigest().digest(); 929 } 930 931 } 932 933 } catch (IllegalArgumentException | NoSuchAlgorithmException | IOException | SecurityException e) { 934 // IOException also covers MalformedURLException 935 // SecurityException means some untrusted applet 936 937 // Fall through... 938 } 939 return new byte[0]; 940 } 941 942 public static Stylesheet loadStylesheet(final String fname) { 943 try { 944 return loadStylesheetUnPrivileged(fname); 945 } catch (java.security.AccessControlException ace) { 946 947 // FIXME: JIGSAW -- we no longer are in a jar file, so this code path 948 // is obsolete and needs to be redone or eliminated. Fortunately, I 949 // don't think it is actually needed. 950 System.err.println("WARNING: security exception trying to load: " + fname); 951 952 /* 953 ** we got an access control exception, so 954 ** we could be running from an applet/jnlp/or with a security manager. 955 ** we'll allow the app to read a css file from our runtime jar, 956 ** and give it one more chance. 957 */ 958 959 /* 960 ** check that there are enough chars after the !/ to have a valid .css or .bss file name 961 */ 962 if ((fname.length() < 7) && (fname.indexOf("!/") < fname.length()-7)) { 963 return null; 964 } 965 966 /* 967 ** 968 ** first check that it's actually looking for the same runtime jar 969 ** that we're running from, and not some other file. 970 */ 971 try { 972 URI requestedFileUrI = new URI(fname); 973 974 /* 975 ** is the requested file in a jar 976 */ 977 if ("jar".equals(requestedFileUrI.getScheme())) { 978 /* 979 ** let's check that the css file is being requested from our 980 ** runtime jar 981 */ 982 URI styleManagerJarURI = AccessController.doPrivileged((PrivilegedExceptionAction<URI>) () -> StyleManager.class.getProtectionDomain().getCodeSource().getLocation().toURI()); 983 984 final String styleManagerJarPath = styleManagerJarURI.getSchemeSpecificPart(); 985 String requestedFilePath = requestedFileUrI.getSchemeSpecificPart(); 986 String requestedFileJarPart = requestedFilePath.substring(requestedFilePath.indexOf('/'), requestedFilePath.indexOf("!/")); 987 /* 988 ** it's the correct jar, check it's a file access 989 ** strip off the leading jar 990 */ 991 if (styleManagerJarPath.equals(requestedFileJarPart)) { 992 /* 993 ** strip off the leading "jar", 994 ** the css file name is past the last '!' 995 */ 996 String requestedFileJarPathNoLeadingSlash = fname.substring(fname.indexOf("!/")+2); 997 /* 998 ** check that it's looking for a css file in the runtime jar 999 */ 1000 if (fname.endsWith(".css") || fname.endsWith(".bss")) { 1001 /* 1002 ** set up a read permission for the jar 1003 */ 1004 FilePermission perm = new FilePermission(styleManagerJarPath, "read"); 1005 1006 PermissionCollection perms = perm.newPermissionCollection(); 1007 perms.add(perm); 1008 AccessControlContext permsAcc = new AccessControlContext( 1009 new ProtectionDomain[] { 1010 new ProtectionDomain(null, perms) 1011 }); 1012 /* 1013 ** check that the jar file exists, and that we're allowed to 1014 ** read it. 1015 */ 1016 JarFile jar = null; 1017 try { 1018 jar = AccessController.doPrivileged((PrivilegedExceptionAction<JarFile>) () -> new JarFile(styleManagerJarPath), permsAcc); 1019 } catch (PrivilegedActionException pae) { 1020 /* 1021 ** we got either a FileNotFoundException or an IOException 1022 ** in the privileged read. Return the same error as we 1023 ** would have returned if the css file hadn't of existed. 1024 */ 1025 return null; 1026 } 1027 if (jar != null) { 1028 /* 1029 ** check that the file is in the jar 1030 */ 1031 JarEntry entry = jar.getJarEntry(requestedFileJarPathNoLeadingSlash); 1032 if (entry != null) { 1033 /* 1034 ** allow read access to the jar 1035 */ 1036 return AccessController.doPrivileged( 1037 (PrivilegedAction<Stylesheet>) () -> loadStylesheetUnPrivileged(fname), permsAcc); 1038 } 1039 } 1040 } 1041 } 1042 } 1043 /* 1044 ** no matter what happen, we return the same error that would 1045 ** be returned if the css file hadn't of existed. 1046 ** That way there in no information leaked. 1047 */ 1048 return null; 1049 } 1050 /* 1051 ** no matter what happen, we return the same error that would 1052 ** be returned if the css file hadn't of existed. 1053 ** That way there in no information leaked. 1054 */ 1055 catch (java.net.URISyntaxException e) { 1056 return null; 1057 } 1058 catch (java.security.PrivilegedActionException e) { 1059 return null; 1060 } 1061 } 1062 } 1063 1064 1065 private static Stylesheet loadStylesheetUnPrivileged(final String fname) { 1066 1067 synchronized (styleLock) { 1068 Boolean parse = AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> { 1069 1070 final String bss = System.getProperty("binary.css"); 1071 // binary.css is true by default. 1072 // parse only if the file is not a .bss 1073 // and binary.css is set to false 1074 return (!fname.endsWith(".bss") && bss != null) ? 1075 !Boolean.valueOf(bss) : Boolean.FALSE; 1076 }); 1077 1078 try { 1079 final String ext = (parse) ? (".css") : (".bss"); 1080 java.net.URL url = null; 1081 Stylesheet stylesheet = null; 1082 // check if url has extension, if not then just url as is and always parse as css text 1083 if (!(fname.endsWith(".css") || fname.endsWith(".bss"))) { 1084 url = getURL(fname); 1085 parse = true; 1086 } else { 1087 final String name = fname.substring(0, fname.length() - 4); 1088 1089 url = getURL(name+ext); 1090 if (url == null && (parse = !parse)) { 1091 // If we failed to get the URL for the .bss file, 1092 // fall back to the .css file. 1093 // Note that 'parse' is toggled in the test. 1094 url = getURL(name+".css"); 1095 } 1096 1097 if ((url != null) && !parse) { 1098 1099 try { 1100 // RT-36332: if loadBinary throws an IOException, make sure to try .css 1101 stylesheet = Stylesheet.loadBinary(url); 1102 } catch (IOException ioe) { 1103 stylesheet = null; 1104 } 1105 1106 if (stylesheet == null && (parse = !parse)) { 1107 // If we failed to load the .bss file, 1108 // fall back to the .css file. 1109 // Note that 'parse' is toggled in the test. 1110 url = getURL(fname); 1111 } 1112 } 1113 } 1114 1115 // either we failed to load the .bss file, or parse 1116 // was set to true. 1117 if ((url != null) && parse) { 1118 stylesheet = new CssParser().parse(url); 1119 } 1120 1121 if (stylesheet == null) { 1122 if (errors != null) { 1123 CssParser.ParseError error = 1124 new CssParser.ParseError( 1125 "Resource \""+fname+"\" not found." 1126 ); 1127 errors.add(error); 1128 } 1129 if (getLogger().isLoggable(Level.WARNING)) { 1130 getLogger().warning( 1131 String.format("Resource \"%s\" not found.", fname) 1132 ); 1133 } 1134 } 1135 1136 // load any fonts from @font-face 1137 if (stylesheet != null) { 1138 faceLoop: for(FontFace fontFace: stylesheet.getFontFaces()) { 1139 if (fontFace instanceof FontFaceImpl) { 1140 for(FontFaceImpl.FontFaceSrc src: ((FontFaceImpl)fontFace).getSources()) { 1141 if (src.getType() == FontFaceImpl.FontFaceSrcType.URL) { 1142 Font loadedFont = Font.loadFont(src.getSrc(),10); 1143 if (loadedFont == null) { 1144 getLogger().info("Could not load @font-face font [" + src.getSrc() + "]"); 1145 } 1146 continue faceLoop; 1147 } 1148 } 1149 } 1150 } 1151 } 1152 1153 return stylesheet; 1154 1155 } catch (FileNotFoundException fnfe) { 1156 if (errors != null) { 1157 CssParser.ParseError error = 1158 new CssParser.ParseError( 1159 "Stylesheet \""+fname+"\" not found." 1160 ); 1161 errors.add(error); 1162 } 1163 if (getLogger().isLoggable(Level.INFO)) { 1164 getLogger().info("Could not find stylesheet: " + fname);//, fnfe); 1165 } 1166 } catch (IOException ioe) { 1167 if (errors != null) { 1168 CssParser.ParseError error = 1169 new CssParser.ParseError( 1170 "Could not load stylesheet: " + fname 1171 ); 1172 errors.add(error); 1173 } 1174 if (getLogger().isLoggable(Level.INFO)) { 1175 getLogger().info("Could not load stylesheet: " + fname);//, ioe); 1176 } 1177 } 1178 return null; 1179 } 1180 } 1181 1182 //////////////////////////////////////////////////////////////////////////// 1183 // 1184 // User Agent stylesheet handling 1185 // 1186 //////////////////////////////////////////////////////////////////////////// 1187 1188 1189 /** 1190 * Set a bunch of user agent stylesheets all at once. The order of the stylesheets in the list 1191 * is the order of their styles in the cascade. Passing null, an empty list, or a list full of empty 1192 * strings does nothing. 1193 * 1194 * @param urls The list of stylesheet URLs as Strings. 1195 */ 1196 public void setUserAgentStylesheets(List<String> urls) { 1197 1198 if (urls == null || urls.size() == 0) return; 1199 1200 synchronized (styleLock) { 1201 // Avoid resetting user agent stylesheets if they haven't changed. 1202 if (urls.size() == platformUserAgentStylesheetContainers.size()) { 1203 boolean isSame = true; 1204 for (int n=0, nMax=urls.size(); n < nMax && isSame; n++) { 1205 1206 final String url = urls.get(n); 1207 final String fname = (url != null) ? url.trim() : null; 1208 1209 if (fname == null || fname.isEmpty()) break; 1210 1211 StylesheetContainer container = platformUserAgentStylesheetContainers.get(n); 1212 // assignment in this conditional is intentional! 1213 if(isSame = fname.equals(container.fname)) { 1214 // don't use fname in calculateCheckSum since it is just the key to 1215 // find the StylesheetContainer. Rather, use the URL of the 1216 // stylesheet that was already loaded. For example, we could have 1217 // fname = "com/sun/javafx/scene/control/skin/modena/modena.css, but 1218 // the stylesheet URL could be jar:file://some/path/!com/sun/javafx/scene/control/skin/modena/modena.bss 1219 String stylesheetUrl = container.stylesheet.getUrl(); 1220 byte[] checksum = calculateCheckSum(stylesheetUrl); 1221 isSame = Arrays.equals(checksum, container.checksum); 1222 } 1223 } 1224 if (isSame) return; 1225 } 1226 1227 boolean modified = false; 1228 1229 for (int n=0, nMax=urls.size(); n < nMax; n++) { 1230 1231 final String url = urls.get(n); 1232 final String fname = (url != null) ? url.trim() : null; 1233 1234 if (fname == null || fname.isEmpty()) continue; 1235 1236 if (!modified) { 1237 // we have at least one non null or non-empty url 1238 platformUserAgentStylesheetContainers.clear(); 1239 modified = true; 1240 } 1241 1242 if (n==0) { 1243 _setDefaultUserAgentStylesheet(fname); 1244 } else { 1245 _addUserAgentStylesheet(fname); 1246 } 1247 } 1248 1249 if (modified) { 1250 userAgentStylesheetsChanged(); 1251 } 1252 } 1253 } 1254 1255 /** 1256 * Add a user agent stylesheet, possibly overriding styles in the default 1257 * user agent stylesheet. 1258 * 1259 * @param fname The file URL, either relative or absolute, as a String. 1260 */ 1261 public void addUserAgentStylesheet(String fname) { 1262 addUserAgentStylesheet(null, fname); 1263 } 1264 1265 /** 1266 * Add a user agent stylesheet, possibly overriding styles in the default 1267 * user agent stylesheet. 1268 * @param scene Only used in CssError for tracking back to the scene that loaded the stylesheet 1269 * @param url The file URL, either relative or absolute, as a String. 1270 */ 1271 // For RT-20643 1272 public void addUserAgentStylesheet(Scene scene, String url) { 1273 1274 final String fname = (url != null) ? url.trim() : null; 1275 if (fname == null || fname.isEmpty()) { 1276 return; 1277 } 1278 1279 synchronized (styleLock) { 1280 if (_addUserAgentStylesheet(fname)) { 1281 userAgentStylesheetsChanged(); 1282 } 1283 } 1284 } 1285 1286 // fname is assumed to be non null and non empty 1287 private boolean _addUserAgentStylesheet(String fname) { 1288 1289 synchronized (styleLock) { 1290 // if we already have this stylesheet, bail 1291 for (int n=0, nMax= platformUserAgentStylesheetContainers.size(); n < nMax; n++) { 1292 StylesheetContainer container = platformUserAgentStylesheetContainers.get(n); 1293 if (fname.equals(container.fname)) { 1294 return false; 1295 } 1296 } 1297 1298 final Stylesheet ua_stylesheet = loadStylesheet(fname); 1299 1300 if (ua_stylesheet == null) return false; 1301 1302 ua_stylesheet.setOrigin(StyleOrigin.USER_AGENT); 1303 platformUserAgentStylesheetContainers.add(new StylesheetContainer(fname, ua_stylesheet)); 1304 return true; 1305 } 1306 } 1307 1308 /** 1309 * Add a user agent stylesheet, possibly overriding styles in the default 1310 * user agent stylesheet. 1311 * @param scene Only used in CssError for tracking back to the scene that loaded the stylesheet 1312 * @param ua_stylesheet The stylesheet to add as a user-agent stylesheet 1313 */ 1314 public void addUserAgentStylesheet(Scene scene, Stylesheet ua_stylesheet) { 1315 1316 if (ua_stylesheet == null ) { 1317 throw new IllegalArgumentException("null arg ua_stylesheet"); 1318 } 1319 1320 // null url is ok, just means that it is a stylesheet not loaded from a file 1321 String url = ua_stylesheet.getUrl(); 1322 final String fname = url != null ? url.trim() : ""; 1323 1324 synchronized (styleLock) { 1325 // if we already have this stylesheet, bail 1326 for (int n=0, nMax= platformUserAgentStylesheetContainers.size(); n < nMax; n++) { 1327 StylesheetContainer container = platformUserAgentStylesheetContainers.get(n); 1328 if (fname.equals(container.fname)) { 1329 return; 1330 } 1331 } 1332 1333 platformUserAgentStylesheetContainers.add(new StylesheetContainer(fname, ua_stylesheet)); 1334 1335 if (ua_stylesheet != null) { 1336 ua_stylesheet.setOrigin(StyleOrigin.USER_AGENT); 1337 } 1338 userAgentStylesheetsChanged(); 1339 } 1340 } 1341 1342 /** 1343 * Set the default user agent stylesheet. 1344 * 1345 * @param fname The file URL, either relative or absolute, as a String. 1346 */ 1347 public void setDefaultUserAgentStylesheet(String fname) { 1348 setDefaultUserAgentStylesheet(null, fname); 1349 } 1350 1351 /** 1352 * Set the default user agent stylesheet 1353 * @param scene Only used in CssError for tracking back to the scene that loaded the stylesheet 1354 * @param url The file URL, either relative or absolute, as a String. 1355 */ 1356 // For RT-20643 1357 public void setDefaultUserAgentStylesheet(Scene scene, String url) { 1358 1359 final String fname = (url != null) ? url.trim() : null; 1360 if (fname == null || fname.isEmpty()) { 1361 return; 1362 } 1363 1364 synchronized (styleLock) { 1365 if(_setDefaultUserAgentStylesheet(fname)) { 1366 userAgentStylesheetsChanged(); 1367 } 1368 } 1369 } 1370 1371 // fname is expected to be non null and non empty 1372 private boolean _setDefaultUserAgentStylesheet(String fname) { 1373 1374 synchronized (styleLock) { 1375 // if we already have this stylesheet, make sure it is the first element 1376 for (int n=0, nMax= platformUserAgentStylesheetContainers.size(); n < nMax; n++) { 1377 StylesheetContainer container = platformUserAgentStylesheetContainers.get(n); 1378 if (fname.equals(container.fname)) { 1379 if (n > 0) { 1380 platformUserAgentStylesheetContainers.remove(n); 1381 if (hasDefaultUserAgentStylesheet) { 1382 platformUserAgentStylesheetContainers.set(0, container); 1383 } else { 1384 platformUserAgentStylesheetContainers.add(0, container); 1385 } 1386 } 1387 // return true only if platformUserAgentStylesheetContainers was modified 1388 return n > 0; 1389 } 1390 } 1391 1392 final Stylesheet ua_stylesheet = loadStylesheet(fname); 1393 1394 if (ua_stylesheet == null) return false; 1395 1396 ua_stylesheet.setOrigin(StyleOrigin.USER_AGENT); 1397 final StylesheetContainer sc = new StylesheetContainer(fname, ua_stylesheet); 1398 1399 if (platformUserAgentStylesheetContainers.size() == 0) { 1400 platformUserAgentStylesheetContainers.add(sc); 1401 } 1402 else if (hasDefaultUserAgentStylesheet) { 1403 platformUserAgentStylesheetContainers.set(0,sc); 1404 } 1405 else { 1406 platformUserAgentStylesheetContainers.add(0,sc); 1407 } 1408 hasDefaultUserAgentStylesheet = true; 1409 1410 return true; 1411 } 1412 } 1413 1414 /** 1415 * Removes the specified stylesheet from the application default user agent 1416 * stylesheet list. 1417 * @param url The file URL, either relative or absolute, as a String. 1418 */ 1419 public void removeUserAgentStylesheet(String url) { 1420 1421 final String fname = (url != null) ? url.trim() : null; 1422 if (fname == null || fname.isEmpty()) { 1423 return; 1424 } 1425 1426 synchronized (styleLock) { 1427 // if we already have this stylesheet, remove it! 1428 boolean removed = false; 1429 for (int n = platformUserAgentStylesheetContainers.size() - 1; n >= 0; n--) { 1430 // don't remove the platform default user agent stylesheet 1431 if (fname.equals(Application.getUserAgentStylesheet())) { 1432 continue; 1433 } 1434 1435 StylesheetContainer container = platformUserAgentStylesheetContainers.get(n); 1436 if (fname.equals(container.fname)) { 1437 platformUserAgentStylesheetContainers.remove(n); 1438 removed = true; 1439 } 1440 } 1441 1442 if (removed) { 1443 userAgentStylesheetsChanged(); 1444 } 1445 } 1446 } 1447 1448 /** 1449 * Set the user agent stylesheet. This is the base default stylesheet for 1450 * the platform 1451 */ 1452 public void setDefaultUserAgentStylesheet(Stylesheet ua_stylesheet) { 1453 if (ua_stylesheet == null ) { 1454 return; 1455 } 1456 1457 // null url is ok, just means that it is a stylesheet not loaded from a file 1458 String url = ua_stylesheet.getUrl(); 1459 final String fname = url != null ? url.trim() : ""; 1460 1461 synchronized (styleLock) { 1462 // if we already have this stylesheet, make sure it is the first element 1463 for (int n=0, nMax= platformUserAgentStylesheetContainers.size(); n < nMax; n++) { 1464 StylesheetContainer container = platformUserAgentStylesheetContainers.get(n); 1465 if (fname.equals(container.fname)) { 1466 if (n > 0) { 1467 platformUserAgentStylesheetContainers.remove(n); 1468 if (hasDefaultUserAgentStylesheet) { 1469 platformUserAgentStylesheetContainers.set(0, container); 1470 } else { 1471 platformUserAgentStylesheetContainers.add(0, container); 1472 } 1473 } 1474 return; 1475 } 1476 } 1477 1478 StylesheetContainer sc = new StylesheetContainer(fname, ua_stylesheet); 1479 if (platformUserAgentStylesheetContainers.size() == 0) { 1480 platformUserAgentStylesheetContainers.add(sc); 1481 } else if (hasDefaultUserAgentStylesheet) { 1482 platformUserAgentStylesheetContainers.set(0,sc); 1483 } else { 1484 platformUserAgentStylesheetContainers.add(0,sc); 1485 } 1486 hasDefaultUserAgentStylesheet = true; 1487 1488 ua_stylesheet.setOrigin(StyleOrigin.USER_AGENT); 1489 userAgentStylesheetsChanged(); 1490 } 1491 } 1492 1493 /* 1494 * If the userAgentStylesheets change, then all scenes are updated. 1495 */ 1496 private void userAgentStylesheetsChanged() { 1497 1498 List<Parent> parents = new ArrayList<>(); 1499 1500 synchronized (styleLock) { 1501 for (CacheContainer container : cacheContainerMap.values()) { 1502 container.clearCache(); 1503 } 1504 1505 StyleConverter.clearCache(); 1506 1507 for (Parent root : cacheContainerMap.keySet()) { 1508 if (root == null) { 1509 continue; 1510 } 1511 parents.add(root); 1512 } 1513 } 1514 1515 for (Parent root : parents) NodeHelper.reapplyCSS(root); 1516 } 1517 1518 private List<StylesheetContainer> processStylesheets(List<String> stylesheets, Parent parent) { 1519 1520 synchronized (styleLock) { 1521 final List<StylesheetContainer> list = new ArrayList<StylesheetContainer>(); 1522 for (int n = 0, nMax = stylesheets.size(); n < nMax; n++) { 1523 final String fname = stylesheets.get(n); 1524 1525 StylesheetContainer container = null; 1526 if (stylesheetContainerMap.containsKey(fname)) { 1527 container = stylesheetContainerMap.get(fname); 1528 1529 if (!list.contains(container)) { 1530 // minor optimization: if existing checksum in byte[0], then don't bother recalculating 1531 if (container.checksumInvalid) { 1532 final byte[] checksum = calculateCheckSum(fname); 1533 if (!Arrays.equals(checksum, container.checksum)) { 1534 removeStylesheetContainer(container); 1535 1536 // Stylesheet did change. Re-load the stylesheet and update the container map. 1537 Stylesheet stylesheet = loadStylesheet(fname); 1538 container = new StylesheetContainer(fname, stylesheet, checksum); 1539 stylesheetContainerMap.put(fname, container); 1540 } else { 1541 container.checksumInvalid = false; 1542 } 1543 } 1544 list.add(container); 1545 } 1546 1547 // RT-22565: remember that this parent or scene uses this stylesheet. 1548 // Later, if the cache is cleared, the parent or scene is told to 1549 // reapply css. 1550 container.parentUsers.add(parent); 1551 1552 } else { 1553 final Stylesheet stylesheet = loadStylesheet(fname); 1554 // stylesheet may be null which would mean that some IOException 1555 // was thrown while trying to load it. Add it to the 1556 // stylesheetContainerMap anyway as this will prevent further 1557 // attempts to parse the file 1558 container = new StylesheetContainer(fname, stylesheet); 1559 // RT-22565: remember that this parent or scene uses this stylesheet. 1560 // Later, if the cache is cleared, the parent or scene is told to 1561 // reapply css. 1562 container.parentUsers.add(parent); 1563 stylesheetContainerMap.put(fname, container); 1564 1565 list.add(container); 1566 } 1567 } 1568 return list; 1569 } 1570 } 1571 1572 // 1573 // recurse so that stylesheets of Parents closest to the root are 1574 // added to the list first. The ensures that declarations for 1575 // stylesheets further down the tree (closer to the leaf) have 1576 // a higher ordinal in the cascade. 1577 // 1578 private List<StylesheetContainer> gatherParentStylesheets(final Parent parent) { 1579 1580 if (parent == null) { 1581 return Collections.<StylesheetContainer>emptyList(); 1582 } 1583 1584 final List<String> parentStylesheets = ParentHelper.getAllParentStylesheets(parent); 1585 1586 if (parentStylesheets == null || parentStylesheets.isEmpty()) { 1587 return Collections.<StylesheetContainer>emptyList(); 1588 } 1589 1590 synchronized (styleLock) { 1591 return processStylesheets(parentStylesheets, parent); 1592 } 1593 } 1594 1595 // 1596 // 1597 // 1598 private List<StylesheetContainer> gatherSceneStylesheets(final Scene scene) { 1599 1600 if (scene == null) { 1601 return Collections.<StylesheetContainer>emptyList(); 1602 } 1603 1604 final List<String> sceneStylesheets = scene.getStylesheets(); 1605 1606 if (sceneStylesheets == null || sceneStylesheets.isEmpty()) { 1607 return Collections.<StylesheetContainer>emptyList(); 1608 } 1609 1610 synchronized (styleLock) { 1611 return processStylesheets(sceneStylesheets, scene.getRoot()); 1612 } 1613 } 1614 1615 // reuse key to avoid creation of numerous small objects 1616 private Key key = null; 1617 1618 // Stores weak references to regions which return non-null user agent stylesheets 1619 private final WeakHashMap<Region, String> weakRegionUserAgentStylesheetMap = new WeakHashMap<>(); 1620 1621 /** 1622 * Finds matching styles for this Node. 1623 */ 1624 public StyleMap findMatchingStyles(Node node, SubScene subScene, Set<PseudoClass>[] triggerStates) { 1625 1626 final Scene scene = node.getScene(); 1627 if (scene == null) { 1628 return StyleMap.EMPTY_MAP; 1629 } 1630 1631 CacheContainer cacheContainer = getCacheContainer(node, subScene); 1632 if (cacheContainer == null) { 1633 assert false : node.toString(); 1634 return StyleMap.EMPTY_MAP; 1635 } 1636 1637 synchronized (styleLock) { 1638 final Parent parent = 1639 (node instanceof Parent) 1640 ? (Parent) node : node.getParent(); 1641 1642 final List<StylesheetContainer> parentStylesheets = 1643 gatherParentStylesheets(parent); 1644 1645 final boolean hasParentStylesheets = parentStylesheets.isEmpty() == false; 1646 1647 final List<StylesheetContainer> sceneStylesheets = gatherSceneStylesheets(scene); 1648 1649 final boolean hasSceneStylesheets = sceneStylesheets.isEmpty() == false; 1650 1651 final String inlineStyle = node.getStyle(); 1652 final boolean hasInlineStyles = inlineStyle != null && inlineStyle.trim().isEmpty() == false; 1653 1654 final String sceneUserAgentStylesheet = scene.getUserAgentStylesheet(); 1655 final boolean hasSceneUserAgentStylesheet = 1656 sceneUserAgentStylesheet != null && sceneUserAgentStylesheet.trim().isEmpty() == false; 1657 1658 final String subSceneUserAgentStylesheet = 1659 (subScene != null) ? subScene.getUserAgentStylesheet() : null; 1660 final boolean hasSubSceneUserAgentStylesheet = 1661 subSceneUserAgentStylesheet != null && subSceneUserAgentStylesheet.trim().isEmpty() == false; 1662 1663 String regionUserAgentStylesheet = null; 1664 // is this node in a region that has its own stylesheet? 1665 Node region = node; 1666 while (region != null) { 1667 if (region instanceof Region) { 1668 regionUserAgentStylesheet = weakRegionUserAgentStylesheetMap.computeIfAbsent( 1669 (Region)region, Region::getUserAgentStylesheet); 1670 1671 if (regionUserAgentStylesheet != null) { 1672 // We want 'region' to be the node that has the user agent stylesheet. 1673 // 'region' is used below - look for if (hasRegionUserAgentStylesheet) block 1674 break; 1675 } 1676 } 1677 region = region.getParent(); 1678 } 1679 1680 1681 final boolean hasRegionUserAgentStylesheet = 1682 regionUserAgentStylesheet != null && regionUserAgentStylesheet.trim().isEmpty() == false; 1683 1684 // 1685 // Are there any stylesheets at all? 1686 // If not, then there is nothing to match and the 1687 // resulting StyleMap is going to end up empty 1688 // 1689 if (hasInlineStyles == false 1690 && hasParentStylesheets == false 1691 && hasSceneStylesheets == false 1692 && hasSceneUserAgentStylesheet == false 1693 && hasSubSceneUserAgentStylesheet == false 1694 && hasRegionUserAgentStylesheet == false 1695 && platformUserAgentStylesheetContainers.isEmpty()) { 1696 return StyleMap.EMPTY_MAP; 1697 } 1698 1699 final String cname = node.getTypeSelector(); 1700 final String id = node.getId(); 1701 final List<String> styleClasses = node.getStyleClass(); 1702 1703 if (key == null) { 1704 key = new Key(); 1705 } 1706 1707 key.className = cname; 1708 key.id = id; 1709 for(int n=0, nMax=styleClasses.size(); n<nMax; n++) { 1710 1711 final String styleClass = styleClasses.get(n); 1712 if (styleClass == null || styleClass.isEmpty()) continue; 1713 1714 key.styleClasses.add(StyleClassSet.getStyleClass(styleClass)); 1715 } 1716 1717 Map<Key, Cache> cacheMap = cacheContainer.getCacheMap(parentStylesheets,regionUserAgentStylesheet); 1718 Cache cache = cacheMap.get(key); 1719 1720 if (cache != null) { 1721 // key will be reused, so clear the styleClasses for next use 1722 key.styleClasses.clear(); 1723 1724 } else { 1725 1726 // If the cache is null, then we need to create a new Cache and 1727 // add it to the cache map 1728 1729 // Construct the list of Selectors that could possibly apply 1730 final List<Selector> selectorData = new ArrayList<>(); 1731 1732 // User agent stylesheets have lowest precedence and go first 1733 if (hasSubSceneUserAgentStylesheet || hasSceneUserAgentStylesheet) { 1734 1735 // if has both, use SubScene 1736 final String uaFileName = hasSubSceneUserAgentStylesheet ? 1737 subScene.getUserAgentStylesheet().trim() : 1738 scene.getUserAgentStylesheet().trim(); 1739 1740 1741 StylesheetContainer container = null; 1742 for (int n=0, nMax=userAgentStylesheetContainers.size(); n<nMax; n++) { 1743 container = userAgentStylesheetContainers.get(n); 1744 if (uaFileName.equals(container.fname)) { 1745 break; 1746 } 1747 container = null; 1748 } 1749 1750 if (container == null) { 1751 Stylesheet stylesheet = loadStylesheet(uaFileName); 1752 if (stylesheet != null) { 1753 stylesheet.setOrigin(StyleOrigin.USER_AGENT); 1754 } 1755 container = new StylesheetContainer(uaFileName, stylesheet); 1756 userAgentStylesheetContainers.add(container); 1757 } 1758 1759 if (container.selectorPartitioning != null) { 1760 1761 final Parent root = hasSubSceneUserAgentStylesheet ? subScene.getRoot() : scene.getRoot(); 1762 container.parentUsers.add(root); 1763 1764 final List<Selector> matchingRules = 1765 container.selectorPartitioning.match(id, cname, key.styleClasses); 1766 selectorData.addAll(matchingRules); 1767 } 1768 1769 } else if (platformUserAgentStylesheetContainers.isEmpty() == false) { 1770 for(int n=0, nMax= platformUserAgentStylesheetContainers.size(); n<nMax; n++) { 1771 final StylesheetContainer container = platformUserAgentStylesheetContainers.get(n); 1772 if (container != null && container.selectorPartitioning != null) { 1773 final List<Selector> matchingRules = 1774 container.selectorPartitioning.match(id, cname, key.styleClasses); 1775 selectorData.addAll(matchingRules); 1776 } 1777 } 1778 } 1779 1780 if (hasRegionUserAgentStylesheet) { 1781 // Unfortunate duplication of code from previous block. No time to refactor. 1782 StylesheetContainer container = null; 1783 for (int n=0, nMax=userAgentStylesheetContainers.size(); n<nMax; n++) { 1784 container = userAgentStylesheetContainers.get(n); 1785 if (regionUserAgentStylesheet.equals(container.fname)) { 1786 break; 1787 } 1788 container = null; 1789 } 1790 1791 if (container == null) { 1792 Stylesheet stylesheet = loadStylesheet(regionUserAgentStylesheet); 1793 if (stylesheet != null) { 1794 stylesheet.setOrigin(StyleOrigin.USER_AGENT); 1795 } 1796 container = new StylesheetContainer(regionUserAgentStylesheet, stylesheet); 1797 userAgentStylesheetContainers.add(container); 1798 } 1799 1800 if (container.selectorPartitioning != null) { 1801 1802 // Depending on RefList add method not allowing duplicates. 1803 container.parentUsers.add((Parent)region); 1804 1805 final List<Selector> matchingRules = 1806 container.selectorPartitioning.match(id, cname, key.styleClasses); 1807 selectorData.addAll(matchingRules); 1808 } 1809 1810 } 1811 1812 // Scene stylesheets come next since declarations from 1813 // parent stylesheets should take precedence. 1814 if (sceneStylesheets.isEmpty() == false) { 1815 for(int n=0, nMax=sceneStylesheets.size(); n<nMax; n++) { 1816 final StylesheetContainer container = sceneStylesheets.get(n); 1817 if (container != null && container.selectorPartitioning != null) { 1818 final List<Selector> matchingRules = 1819 container.selectorPartitioning.match(id, cname, key.styleClasses); 1820 selectorData.addAll(matchingRules); 1821 } 1822 } 1823 } 1824 1825 // lastly, parent stylesheets 1826 if (hasParentStylesheets) { 1827 final int nMax = parentStylesheets == null ? 0 : parentStylesheets.size(); 1828 for(int n=0; n<nMax; n++) { 1829 final StylesheetContainer container = parentStylesheets.get(n); 1830 if (container.selectorPartitioning != null) { 1831 final List<Selector> matchingRules = 1832 container.selectorPartitioning.match(id, cname, key.styleClasses); 1833 selectorData.addAll(matchingRules); 1834 } 1835 } 1836 } 1837 1838 // create a new Cache from these selectors. 1839 cache = new Cache(selectorData); 1840 cacheMap.put(key, cache); 1841 1842 // cause a new Key to be created the next time this method is called 1843 key = null; 1844 } 1845 1846 // 1847 // Create a style helper for this node from the styles that match. 1848 // 1849 StyleMap smap = cache.getStyleMap(cacheContainer, node, triggerStates, hasInlineStyles); 1850 1851 return smap; 1852 } 1853 } 1854 1855 //////////////////////////////////////////////////////////////////////////// 1856 // 1857 // CssError reporting 1858 // 1859 //////////////////////////////////////////////////////////////////////////// 1860 1861 private static ObservableList<CssParser.ParseError> errors = null; 1862 /** 1863 * Errors that may have occurred during css processing. 1864 * This list is null until errorsProperty() is called. 1865 * 1866 * NOTE: this is not thread-safe, and cannot readily be made so given the 1867 * nature of the API. 1868 * Currently it is only used by SceneBuilder. If a public API is ever 1869 * needed, then a new API should be designed. 1870 * 1871 * @return 1872 */ 1873 public static ObservableList<CssParser.ParseError> errorsProperty() { 1874 if (errors == null) { 1875 errors = FXCollections.observableArrayList(); 1876 } 1877 return errors; 1878 } 1879 1880 /** 1881 * Errors that may have occurred during css processing. 1882 * This list is null until errorsProperty() is called and is used 1883 * internally to figure out whether or not anyone is interested in 1884 * receiving CssError. 1885 * Not meant for general use - call errorsProperty() instead. 1886 * @return 1887 */ 1888 public static ObservableList<CssParser.ParseError> getErrors() { 1889 return errors; 1890 } 1891 1892 //////////////////////////////////////////////////////////////////////////// 1893 // 1894 // Classes and routines for mapping styles to a Node 1895 // 1896 //////////////////////////////////////////////////////////////////////////// 1897 1898 private static List<String> cacheMapKey; 1899 1900 // Each Scene has its own cache 1901 // package for testing 1902 static class CacheContainer { 1903 1904 private Map<StyleCache.Key,StyleCache> getStyleCache() { 1905 if (styleCache == null) styleCache = new HashMap<StyleCache.Key, StyleCache>(); 1906 return styleCache; 1907 } 1908 1909 private Map<Key,Cache> getCacheMap(List<StylesheetContainer> parentStylesheets, String regionUserAgentStylesheet) { 1910 1911 if (cacheMap == null) { 1912 cacheMap = new HashMap<List<String>,Map<Key,Cache>>(); 1913 } 1914 1915 synchronized (styleLock) { 1916 if ((parentStylesheets == null || parentStylesheets.isEmpty()) && 1917 (regionUserAgentStylesheet == null || regionUserAgentStylesheet.isEmpty())) { 1918 1919 Map<Key,Cache> cmap = cacheMap.get(null); 1920 if (cmap == null) { 1921 cmap = new HashMap<Key,Cache>(); 1922 cacheMap.put(null, cmap); 1923 } 1924 return cmap; 1925 1926 } else { 1927 1928 final int nMax = parentStylesheets.size(); 1929 if (cacheMapKey == null) { 1930 cacheMapKey = new ArrayList<String>(nMax); 1931 } 1932 for (int n=0; n<nMax; n++) { 1933 StylesheetContainer sc = parentStylesheets.get(n); 1934 if (sc == null || sc.fname == null || sc.fname.isEmpty()) continue; 1935 cacheMapKey.add(sc.fname); 1936 } 1937 if (regionUserAgentStylesheet != null) { 1938 cacheMapKey.add(regionUserAgentStylesheet); 1939 } 1940 Map<Key,Cache> cmap = cacheMap.get(cacheMapKey); 1941 if (cmap == null) { 1942 cmap = new HashMap<Key,Cache>(); 1943 cacheMap.put(cacheMapKey, cmap); 1944 // create a new cacheMapKey the next time this method is called 1945 cacheMapKey = null; 1946 } else { 1947 // reuse cacheMapKey, but not the data, the next time this method is called 1948 cacheMapKey.clear(); 1949 } 1950 return cmap; 1951 1952 } 1953 } 1954 1955 } 1956 1957 private List<StyleMap> getStyleMapList() { 1958 if (styleMapList == null) styleMapList = new ArrayList<StyleMap>(); 1959 return styleMapList; 1960 } 1961 1962 private int nextSmapId() { 1963 styleMapId = baseStyleMapId + getStyleMapList().size(); 1964 return styleMapId; 1965 } 1966 1967 private void addStyleMap(StyleMap smap) { 1968 getStyleMapList().add(smap); 1969 } 1970 1971 public StyleMap getStyleMap(int smapId) { 1972 1973 final int correctedId = smapId - baseStyleMapId; 1974 1975 if (0 <= correctedId && correctedId < getStyleMapList().size()) { 1976 return getStyleMapList().get(correctedId); 1977 } 1978 1979 return StyleMap.EMPTY_MAP; 1980 } 1981 1982 private void clearCache() { 1983 1984 if (cacheMap != null) cacheMap.clear(); 1985 if (styleCache != null) styleCache.clear(); 1986 if (styleMapList != null) styleMapList.clear(); 1987 1988 baseStyleMapId = styleMapId; 1989 // 7/8ths is totally arbitrary 1990 if (baseStyleMapId > Integer.MAX_VALUE/8*7) { 1991 baseStyleMapId = styleMapId = 0; 1992 } 1993 } 1994 1995 /** 1996 * Get the mapping of property to style from Node.style for this node. 1997 */ 1998 private Selector getInlineStyleSelector(String inlineStyle) { 1999 2000 // If there are no styles for this property then we can just bail 2001 if ((inlineStyle == null) || inlineStyle.trim().isEmpty()) return null; 2002 2003 if (inlineStylesCache != null && inlineStylesCache.containsKey(inlineStyle)) { 2004 // Value of Map entry may be null! 2005 return inlineStylesCache.get(inlineStyle); 2006 } 2007 2008 // 2009 // inlineStyle wasn't in the inlineStylesCache, or inlineStylesCache was null 2010 // 2011 2012 if (inlineStylesCache == null) { 2013 inlineStylesCache = new HashMap<>(); 2014 } 2015 2016 final Stylesheet inlineStylesheet = 2017 new CssParser().parse("*{"+inlineStyle+"}"); 2018 2019 if (inlineStylesheet != null) { 2020 2021 inlineStylesheet.setOrigin(StyleOrigin.INLINE); 2022 2023 List<Rule> rules = inlineStylesheet.getRules(); 2024 Rule rule = rules != null && !rules.isEmpty() ? rules.get(0) : null; 2025 2026 //List<Selector> selectors = rule != null ? rule.getUnobservedSelectorList() : null; 2027 List<Selector> selectors = rule != null ? rule.getSelectors() : null; 2028 Selector selector = selectors != null && !selectors.isEmpty() ? selectors.get(0) : null; 2029 2030 // selector might be null if parser throws some exception 2031 if (selector != null) { 2032 selector.setOrdinal(-1); 2033 2034 inlineStylesCache.put(inlineStyle, selector); 2035 return selector; 2036 } 2037 // if selector is null, fall through 2038 2039 } 2040 2041 // even if selector is null, put it in cache so we don't 2042 // bother with trying to parse it again. 2043 inlineStylesCache.put(inlineStyle, null); 2044 return null; 2045 2046 } 2047 2048 private Map<StyleCache.Key,StyleCache> styleCache; 2049 2050 private Map<List<String>, Map<Key,Cache>> cacheMap; 2051 2052 private List<StyleMap> styleMapList; 2053 2054 /** 2055 * Cache of parsed, inline styles. The key is Node.style. 2056 * The value is the Selector from the inline stylesheet. 2057 */ 2058 private Map<String,Selector> inlineStylesCache; 2059 2060 /* 2061 * A simple counter used to generate a unique id for a StyleMap. 2062 * This unique id is used by StyleHelper in figuring out which 2063 * style cache to use. 2064 */ 2065 private int styleMapId = 0; 2066 2067 // When the cache is cleared, styleMapId counting begins here. 2068 // If a StyleHelper calls getStyleMap with an id less than the 2069 // baseStyleMapId, then that StyleHelper is working with an old 2070 // cache and is no longer valid. 2071 private int baseStyleMapId = 0; 2072 2073 } 2074 2075 /** 2076 * Creates and caches maps of styles, reusing them as often as practical. 2077 */ 2078 private static class Cache { 2079 2080 private static class Key { 2081 final long[] key; 2082 final String inlineStyle; 2083 2084 Key(long[] key, String inlineStyle) { 2085 this.key = key; 2086 // let inlineStyle be null if it is empty 2087 this.inlineStyle = (inlineStyle != null && inlineStyle.trim().isEmpty() ? null : inlineStyle); 2088 } 2089 2090 @Override public String toString() { 2091 return Arrays.toString(key) + (inlineStyle != null ? "* {" + inlineStyle + "}" : ""); 2092 } 2093 2094 @Override 2095 public int hashCode() { 2096 int hash = 3; 2097 hash = 17 * hash + Arrays.hashCode(this.key); 2098 if (inlineStyle != null) hash = 17 * hash + inlineStyle.hashCode(); 2099 return hash; 2100 } 2101 2102 @Override 2103 public boolean equals(Object obj) { 2104 if (obj == null) { 2105 return false; 2106 } 2107 if (getClass() != obj.getClass()) { 2108 return false; 2109 } 2110 final Key other = (Key) obj; 2111 if (inlineStyle == null ? other.inlineStyle != null : !inlineStyle.equals(other.inlineStyle)) { 2112 return false; 2113 } 2114 if (!Arrays.equals(this.key, other.key)) { 2115 return false; 2116 } 2117 return true; 2118 } 2119 2120 } 2121 2122 // this must be initialized to the appropriate possible selectors when 2123 // the helper cache is created by the StylesheetContainer. Note that 2124 // SelectorPartioning sorts the matched selectors by ordinal, so this 2125 // list of selectors will be in the same order in which the selectors 2126 // appear in the stylesheets. 2127 private final List<Selector> selectors; 2128 private final Map<Key, Integer> cache; 2129 2130 Cache(List<Selector> selectors) { 2131 this.selectors = selectors; 2132 this.cache = new HashMap<Key, Integer>(); 2133 } 2134 2135 private StyleMap getStyleMap(CacheContainer cacheContainer, Node node, Set<PseudoClass>[] triggerStates, boolean hasInlineStyle) { 2136 2137 if ((selectors == null || selectors.isEmpty()) && !hasInlineStyle) { 2138 return StyleMap.EMPTY_MAP; 2139 } 2140 2141 final int selectorDataSize = selectors.size(); 2142 2143 // 2144 // Since the list of selectors is found by matching only the 2145 // rightmost selector, the set of selectors may larger than those 2146 // selectors that actually match the node. The following loop 2147 // whittles the list down to those selectors that apply. 2148 // 2149 // 2150 // To lookup from the cache, we construct a key from a Long 2151 // where the selectors that match this particular node are 2152 // represented by bits on the long[]. 2153 // 2154 long key[] = new long[selectorDataSize/Long.SIZE + 1]; 2155 boolean nothingMatched = true; 2156 2157 for (int s = 0; s < selectorDataSize; s++) { 2158 2159 final Selector sel = selectors.get(s); 2160 2161 // 2162 // This particular flavor of applies takes a PseudoClassState[] 2163 // fills in the pseudo-class states from the selectors where 2164 // they apply to a node. This is an expedient to looking the 2165 // applies loopa second time on the matching selectors. This has to 2166 // be done ahead of the cache lookup since not all nodes that 2167 // have the same set of selectors will have the same node hierarchy. 2168 // 2169 // For example, if I have .foo:hover:focused .bar:selected {...} 2170 // and the "bar" node is 4 away from the root and the foo 2171 // node is two away from the root, pseudoclassBits would be 2172 // [selected, 0, hover:focused, 0] 2173 // Note that the states run from leaf to root. This is how 2174 // the code in StyleHelper expects things. 2175 // Note also that, if the selector does not apply, the triggerStates 2176 // is unchanged. 2177 // 2178 2179 if (sel.applies(node, triggerStates, 0)) { 2180 final int index = s / Long.SIZE; 2181 final long mask = key[index] | 1l << s; 2182 key[index] = mask; 2183 nothingMatched = false; 2184 } 2185 } 2186 2187 // nothing matched! 2188 if (nothingMatched && hasInlineStyle == false) { 2189 return StyleMap.EMPTY_MAP; 2190 } 2191 2192 final String inlineStyle = node.getStyle(); 2193 final Key keyObj = new Key(key, inlineStyle); 2194 2195 if (cache.containsKey(keyObj)) { 2196 Integer styleMapId = cache.get(keyObj); 2197 final StyleMap styleMap = styleMapId != null 2198 ? cacheContainer.getStyleMap(styleMapId.intValue()) 2199 : StyleMap.EMPTY_MAP; 2200 return styleMap; 2201 } 2202 2203 final List<Selector> selectors = new ArrayList<>(); 2204 2205 if (hasInlineStyle) { 2206 Selector selector = cacheContainer.getInlineStyleSelector(inlineStyle); 2207 if (selector != null) selectors.add(selector); 2208 } 2209 2210 for (int k = 0; k<key.length; k++) { 2211 2212 if (key[k] == 0) continue; 2213 2214 final int offset = k * Long.SIZE; 2215 2216 for (int b = 0; b<Long.SIZE; b++) { 2217 2218 // bit at b in key[k] set? 2219 final long mask = 1l << b; 2220 if ((mask & key[k]) != mask) continue; 2221 2222 final Selector pair = this.selectors.get(offset + b); 2223 selectors.add(pair); 2224 } 2225 } 2226 2227 int id = cacheContainer.nextSmapId(); 2228 cache.put(keyObj, Integer.valueOf(id)); 2229 2230 final StyleMap styleMap = new StyleMap(id, selectors); 2231 cacheContainer.addStyleMap(styleMap); 2232 return styleMap; 2233 } 2234 2235 } 2236 2237 /** 2238 * The key used in the cacheMap of the StylesheetContainer 2239 */ 2240 private static class Key { 2241 // note that the class name here is the *full* class name, such as 2242 // javafx.scene.control.Button. We only bother parsing this down to the 2243 // last part when doing matching against selectors, and so want to avoid 2244 // having to do a bunch of preliminary parsing in places where it isn't 2245 // necessary. 2246 String className; 2247 String id; 2248 final StyleClassSet styleClasses; 2249 2250 private Key() { 2251 styleClasses = new StyleClassSet(); 2252 } 2253 2254 @Override 2255 public boolean equals(Object o) { 2256 if (this == o) { 2257 return true; 2258 } 2259 if (o instanceof Key) { 2260 Key other = (Key)o; 2261 2262 if (className == null ? other.className != null : (className.equals(other.className) == false)) { 2263 return false; 2264 } 2265 2266 if (id == null ? other.id != null : (id.equals(other.id) == false)) { 2267 return false; 2268 } 2269 2270 return this.styleClasses.equals(other.styleClasses); 2271 } 2272 return true; 2273 } 2274 2275 @Override 2276 public int hashCode() { 2277 int hash = 7; 2278 hash = 29 * hash + (this.className != null ? this.className.hashCode() : 0); 2279 hash = 29 * hash + (this.id != null ? this.id.hashCode() : 0); 2280 hash = 29 * hash + this.styleClasses.hashCode(); 2281 return hash; 2282 } 2283 2284 } 2285 2286 2287 }