1 /*
   2  * Copyright (c) 2010, 2015, 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.application;
  27 
  28 import com.sun.javafx.PlatformUtil;
  29 import com.sun.javafx.css.StyleManager;
  30 import com.sun.javafx.runtime.SystemProperties;
  31 
  32 import java.lang.reflect.InvocationTargetException;
  33 import java.lang.reflect.Method;
  34 import java.security.AccessControlContext;
  35 import java.util.ArrayList;
  36 import java.util.List;
  37 import java.util.Set;
  38 import java.util.concurrent.CopyOnWriteArraySet;
  39 import java.util.concurrent.CountDownLatch;
  40 import java.util.concurrent.atomic.AtomicBoolean;
  41 import java.util.concurrent.atomic.AtomicInteger;
  42 
  43 import javafx.application.Application;
  44 import javafx.application.ConditionalFeature;
  45 
  46 import com.sun.javafx.tk.TKListener;
  47 import com.sun.javafx.tk.TKStage;
  48 import com.sun.javafx.tk.Toolkit;
  49 import com.sun.javafx.util.ModuleHelper;
  50 import javafx.beans.property.BooleanProperty;
  51 import javafx.beans.property.SimpleBooleanProperty;
  52 import javafx.scene.Scene;
  53 
  54 import java.security.AccessController;
  55 import java.security.AllPermission;
  56 import java.security.PrivilegedAction;
  57 
  58 public class PlatformImpl {
  59 
  60     private static AtomicBoolean initialized = new AtomicBoolean(false);
  61     private static AtomicBoolean platformExit = new AtomicBoolean(false);
  62     private static AtomicBoolean toolkitExit = new AtomicBoolean(false);
  63     private static CountDownLatch startupLatch = new CountDownLatch(1);
  64     private static AtomicBoolean listenersRegistered = new AtomicBoolean(false);
  65     private static TKListener toolkitListener = null;
  66     private static volatile boolean implicitExit = true;
  67     private static boolean taskbarApplication = true;
  68     private static boolean contextual2DNavigation;
  69     private static AtomicInteger pendingRunnables = new AtomicInteger(0);
  70     private static AtomicInteger numWindows = new AtomicInteger(0);
  71     private static volatile boolean firstWindowShown = false;
  72     private static volatile boolean lastWindowClosed = false;
  73     private static AtomicBoolean reallyIdle = new AtomicBoolean(false);
  74     private static Set<FinishListener> finishListeners =
  75             new CopyOnWriteArraySet<FinishListener>();
  76     private final static Object runLaterLock = new Object();
  77     private static Boolean isGraphicsSupported;
  78     private static Boolean isControlsSupported;
  79     private static Boolean isMediaSupported;
  80     private static Boolean isWebSupported;
  81     private static Boolean isSWTSupported;
  82     private static Boolean isSwingSupported;
  83     private static Boolean isFXMLSupported;
  84     private static Boolean hasTwoLevelFocus;
  85     private static Boolean hasVirtualKeyboard;
  86     private static Boolean hasTouch;
  87     private static Boolean hasMultiTouch;
  88     private static Boolean hasPointer;
  89     private static boolean isThreadMerged = false;
  90     private static BooleanProperty accessibilityActive = new SimpleBooleanProperty();
  91 
  92     /**
  93      * Set a flag indicating whether this application should show up in the
  94      * task bar. The default value is true.
  95      *
  96      * @param taskbarApplication the new value of this attribute
  97      */
  98     public static void setTaskbarApplication(boolean taskbarApplication) {
  99         PlatformImpl.taskbarApplication = taskbarApplication;
 100     }
 101 
 102     /**
 103      * Returns the current value of the taskBarApplication flag.
 104      *
 105      * @return the current state of the flag.
 106      */
 107     public static boolean isTaskbarApplication() {
 108         return taskbarApplication;
 109     }
 110 
 111     /**
 112      * Sets the name of the this application based on the Application class.
 113      * This method is called by the launcher or by the deploy code, and is not
 114      * called from the FX Application Thread, so we need to do it in a runLater.
 115      * We do not need to wait for the result since it will complete before the
 116      * Application start() method is called regardless.
 117      *
 118      * @param appClass the Application class.
 119      */
 120     public static void setApplicationName(final Class appClass) {
 121         runLater(() -> com.sun.glass.ui.Application.GetApplication().setName(appClass.getName()));
 122     }
 123 
 124     /**
 125      * Return whether or not focus navigation between controls is context-
 126      * sensitive.
 127      * @return true if the context-sensitive algorithm for focus navigation is
 128      * used
 129      */
 130      public static boolean isContextual2DNavigation() {
 131          return contextual2DNavigation;
 132      }
 133 
 134     /**
 135      * This method is invoked typically on the main thread. At this point,
 136      * the JavaFX Application Thread has not been started. Any attempt
 137      * to call startup more than once results in all subsequent calls turning into
 138      * nothing more than a runLater call with the provided Runnable being called.
 139      * @param r
 140      */
 141     public static void startup(final Runnable r) {
 142         startup(r, false);
 143     }
 144 
 145     /**
 146      * This method is invoked typically on the main thread. At this point,
 147      * the JavaFX Application Thread has not been started. If preventDuplicateCalls
 148      * is true, calling this method multiple times will result in an
 149      * IllegalStateException. If it is false, calling this method multiple times
 150      * will result in all subsequent calls turning into
 151      * nothing more than a runLater call with the provided Runnable being called.
 152      * @param r
 153      * @param preventDuplicateCalls
 154      */
 155     public static void startup(final Runnable r, boolean preventDuplicateCalls) {
 156 
 157         // NOTE: if we ever support re-launching an application and/or
 158         // launching a second application in the same VM/classloader
 159         // this will need to be changed.
 160         if (platformExit.get()) {
 161             throw new IllegalStateException("Platform.exit has been called");
 162         }
 163 
 164         if (initialized.getAndSet(true)) {
 165             if (preventDuplicateCalls) {
 166                 throw new IllegalStateException("Toolkit already initialized");
 167             }
 168 
 169             // If we've already initialized, just put the runnable on the queue.
 170             runLater(r);
 171             return;
 172         }
 173 
 174         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 175             contextual2DNavigation = Boolean.getBoolean(
 176                     "com.sun.javafx.isContextual2DNavigation");
 177             String s = System.getProperty("com.sun.javafx.twoLevelFocus");
 178             if (s != null) {
 179                 hasTwoLevelFocus = Boolean.valueOf(s);
 180             }
 181             s = System.getProperty("com.sun.javafx.virtualKeyboard");
 182             if (s != null) {
 183                 if (s.equalsIgnoreCase("none")) {
 184                     hasVirtualKeyboard = false;
 185                 } else if (s.equalsIgnoreCase("javafx")) {
 186                     hasVirtualKeyboard = true;
 187                 } else if (s.equalsIgnoreCase("native")) {
 188                     hasVirtualKeyboard = true;
 189                 }
 190             }
 191             s = System.getProperty("com.sun.javafx.touch");
 192             if (s != null) {
 193                 hasTouch = Boolean.valueOf(s);
 194             }
 195             s = System.getProperty("com.sun.javafx.multiTouch");
 196             if (s != null) {
 197                 hasMultiTouch = Boolean.valueOf(s);
 198             }
 199             s = System.getProperty("com.sun.javafx.pointer");
 200             if (s != null) {
 201                 hasPointer = Boolean.valueOf(s);
 202             }
 203             s = System.getProperty("javafx.embed.singleThread");
 204             if (s != null) {
 205                 isThreadMerged = Boolean.valueOf(s);
 206             }
 207             return null;
 208         });
 209 
 210         if (!taskbarApplication) {
 211             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 212                 System.setProperty("glass.taskbarApplication", "false");
 213                 return null;
 214             });
 215         }
 216 
 217         // Create Toolkit listener and register it with the Toolkit.
 218         // Call notifyFinishListeners when we get notified.
 219         toolkitListener = new TKListener() {
 220             @Override public void changedTopLevelWindows(List<TKStage> windows) {
 221                 numWindows.set(windows.size());
 222                 checkIdle();
 223             }
 224 
 225             @Override
 226             public void exitedLastNestedLoop() {
 227                 checkIdle();
 228             }
 229         };
 230         Toolkit.getToolkit().addTkListener(toolkitListener);
 231 
 232         Toolkit.getToolkit().startup(() -> {
 233             startupLatch.countDown();
 234             r.run();
 235         });
 236 
 237         //Initialize the thread merging mechanism
 238         if (isThreadMerged) {
 239             installFwEventQueue();
 240         }
 241     }
 242 
 243     private static void installFwEventQueue() {
 244         invokeSwingFXUtilsMethod("installFwEventQueue");
 245     }
 246 
 247     private static void removeFwEventQueue() {
 248         invokeSwingFXUtilsMethod("removeFwEventQueue");
 249     }
 250 
 251     private static void invokeSwingFXUtilsMethod(final String methodName) {
 252         //Use reflection in case we are running compact profile
 253         try {
 254             Class swingFXUtilsClass = Class.forName("javafx.embed.swing.SwingFXUtils");
 255             Object thisModule = ModuleHelper.getModule(PlatformImpl.class);
 256             Object targetModule = ModuleHelper.getModule(swingFXUtilsClass);
 257             ModuleHelper.addReads(thisModule, targetModule);
 258 
 259             Method installFwEventQueue = swingFXUtilsClass.getDeclaredMethod(methodName);
 260             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 261                 installFwEventQueue.setAccessible(true);
 262                 return null;
 263             });
 264 
 265             waitForStart();
 266             installFwEventQueue.invoke(null);
 267 
 268         } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
 269             throw new RuntimeException("Property javafx.embed.singleThread is not supported");
 270         } catch (InvocationTargetException e) {
 271             throw new RuntimeException(e);
 272         }
 273     }
 274 
 275     private static void waitForStart() {
 276         // If the startup runnable has not yet been called, then wait it.
 277         // Note that we check the count before calling await() to avoid
 278         // the try/catch which is unnecessary after startup.
 279         if (startupLatch.getCount() > 0) {
 280             try {
 281                 startupLatch.await();
 282             } catch (InterruptedException ex) {
 283                 ex.printStackTrace();
 284             }
 285         }
 286     }
 287 
 288     public static boolean isFxApplicationThread() {
 289         return Toolkit.getToolkit().isFxUserThread();
 290     }
 291 
 292     public static void runLater(final Runnable r) {
 293         runLater(r, false);
 294     }
 295 
 296     private static void runLater(final Runnable r, boolean exiting) {
 297         if (!initialized.get()) {
 298             throw new IllegalStateException("Toolkit not initialized");
 299         }
 300 
 301         pendingRunnables.incrementAndGet();
 302         waitForStart();
 303 
 304         if (SystemProperties.isDebug()) {
 305             Toolkit.getToolkit().pauseCurrentThread();
 306         }
 307 
 308         synchronized (runLaterLock) {
 309             if (!exiting && toolkitExit.get()) {
 310                 // Don't schedule a runnable after we have exited the toolkit
 311                 pendingRunnables.decrementAndGet();
 312                 return;
 313             }
 314 
 315             final AccessControlContext acc = AccessController.getContext();
 316             // Don't catch exceptions, they are handled by Toolkit.defer()
 317             Toolkit.getToolkit().defer(() -> {
 318                 try {
 319                     AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 320                         r.run();
 321                         return null;
 322                     }, acc);
 323                 } finally {
 324                     pendingRunnables.decrementAndGet();
 325                     checkIdle();
 326                 }
 327             });
 328         }
 329     }
 330 
 331     public static void runAndWait(final Runnable r) {
 332         runAndWait(r, false);
 333     }
 334 
 335     private static void runAndWait(final Runnable r, boolean exiting) {
 336         if (SystemProperties.isDebug()) {
 337             Toolkit.getToolkit().pauseCurrentThread();
 338         }
 339 
 340         if (isFxApplicationThread()) {
 341              try {
 342                  r.run();
 343              } catch (Throwable t) {
 344                  System.err.println("Exception in runnable");
 345                  t.printStackTrace();
 346              }
 347         } else {
 348             final CountDownLatch doneLatch = new CountDownLatch(1);
 349             runLater(() -> {
 350                 try {
 351                     r.run();
 352                 } finally {
 353                     doneLatch.countDown();
 354                 }
 355             }, exiting);
 356 
 357             if (!exiting && toolkitExit.get()) {
 358                 throw new IllegalStateException("Toolkit has exited");
 359             }
 360 
 361             try {
 362                 doneLatch.await();
 363             } catch (InterruptedException ex) {
 364                 ex.printStackTrace();
 365             }
 366         }
 367     }
 368 
 369     public static void setImplicitExit(boolean implicitExit) {
 370         PlatformImpl.implicitExit = implicitExit;
 371         checkIdle();
 372     }
 373 
 374     public static boolean isImplicitExit() {
 375         return implicitExit;
 376     }
 377 
 378     public static void addListener(FinishListener l) {
 379         listenersRegistered.set(true);
 380         finishListeners.add(l);
 381     }
 382 
 383     public static void removeListener(FinishListener l) {
 384         finishListeners.remove(l);
 385         listenersRegistered.set(!finishListeners.isEmpty());
 386         if (!listenersRegistered.get()) {
 387             checkIdle();
 388         }
 389     }
 390 
 391     private static void notifyFinishListeners(boolean exitCalled) {
 392         // Notify listeners if any are registered, else exit directly
 393         if (listenersRegistered.get()) {
 394             for (FinishListener l : finishListeners) {
 395                 if (exitCalled) {
 396                     l.exitCalled();
 397                 } else {
 398                     l.idle(implicitExit);
 399                 }
 400             }
 401         } else if (implicitExit || platformExit.get()) {
 402             tkExit();
 403         }
 404     }
 405 
 406     // Check for idle, meaning the last top-level window has been closed and
 407     // there are no pending Runnables waiting to be run.
 408     private static void checkIdle() {
 409         // If we aren't initialized yet, then this method is a no-op.
 410         if (!initialized.get()) {
 411             return;
 412         }
 413 
 414         if (!isFxApplicationThread()) {
 415             // Add a dummy runnable to the runLater queue, which will then call
 416             // checkIdle() on the FX application thread.
 417             runLater(() -> {
 418             });
 419             return;
 420         }
 421 
 422         boolean doNotify = false;
 423 
 424         synchronized (PlatformImpl.class) {
 425             int numWin = numWindows.get();
 426             if (numWin > 0) {
 427                 firstWindowShown = true;
 428                 lastWindowClosed = false;
 429                 reallyIdle.set(false);
 430             } else if (numWin == 0 && firstWindowShown) {
 431                 lastWindowClosed = true;
 432             }
 433 
 434             // In case there is an event in process, allow for it to show
 435             // another window. If no new window is shown before all pending
 436             // runnables (including this one) are done and there is no running
 437             // nested loops, then we will shutdown.
 438             if (lastWindowClosed && pendingRunnables.get() == 0
 439                     && (toolkitExit.get() || !Toolkit.getToolkit().isNestedLoopRunning())) {
 440 //                System.err.println("Last window closed and no pending runnables");
 441                 if (reallyIdle.getAndSet(true)) {
 442 //                    System.err.println("Really idle now");
 443                     doNotify = true;
 444                     lastWindowClosed = false;
 445                 } else {
 446 //                    System.err.println("Queuing up a dummy idle check runnable");
 447                     runLater(() -> {
 448 //                            System.err.println("Dummy runnable");
 449                     });
 450                 }
 451             }
 452         }
 453 
 454         if (doNotify) {
 455             notifyFinishListeners(false);
 456         }
 457     }
 458 
 459     // package scope method for testing
 460     private static final CountDownLatch platformExitLatch = new CountDownLatch(1);
 461     static CountDownLatch test_getPlatformExitLatch() {
 462         return platformExitLatch;
 463     }
 464 
 465     public static void tkExit() {
 466         if (toolkitExit.getAndSet(true)) {
 467             return;
 468         }
 469 
 470         if (initialized.get()) {
 471             // Always call toolkit exit on FX app thread
 472 //            System.err.println("PlatformImpl.tkExit: scheduling Toolkit.exit");
 473             PlatformImpl.runAndWait(() -> {
 474 //                System.err.println("PlatformImpl.tkExit: calling Toolkit.exit");
 475                 Toolkit.getToolkit().exit();
 476             }, true);
 477 
 478             if (isThreadMerged) {
 479                 removeFwEventQueue();
 480             }
 481 
 482             Toolkit.getToolkit().removeTkListener(toolkitListener);
 483             toolkitListener = null;
 484             platformExitLatch.countDown();
 485         }
 486     }
 487 
 488     public static BooleanProperty accessibilityActiveProperty() {
 489         return accessibilityActive;
 490     }
 491 
 492     public static void exit() {
 493         platformExit.set(true);
 494         notifyFinishListeners(true);
 495     }
 496 
 497     private static Boolean checkForClass(String classname) {
 498         try {
 499             Class.forName(classname, false, PlatformImpl.class.getClassLoader());
 500             return Boolean.TRUE;
 501         } catch (ClassNotFoundException cnfe) {
 502             return Boolean.FALSE;
 503         }
 504     }
 505 
 506     public static boolean isSupported(ConditionalFeature feature) {
 507         final boolean supported = isSupportedImpl(feature);
 508         if (supported && (feature == ConditionalFeature.TRANSPARENT_WINDOW)) {
 509             // some features require the application to have the corresponding
 510             // permissions, if the application doesn't have them, the platform
 511             // will behave as if the feature wasn't supported
 512             final SecurityManager securityManager =
 513                     System.getSecurityManager();
 514             if (securityManager != null) {
 515                 try {
 516                     securityManager.checkPermission(new AllPermission());
 517                 } catch (final SecurityException e) {
 518                     return false;
 519                 }
 520             }
 521 
 522             return true;
 523         }
 524 
 525         return supported;
 526    }
 527 
 528     public static interface FinishListener {
 529         public void idle(boolean implicitExit);
 530         public void exitCalled();
 531     }
 532 
 533     /**
 534      * Set the platform user agent stylesheet to the default.
 535      */
 536     public static void setDefaultPlatformUserAgentStylesheet() {
 537         setPlatformUserAgentStylesheet(Application.STYLESHEET_MODENA);
 538     }
 539 
 540     private static boolean isModena = false;
 541     private static boolean isCaspian = false;
 542 
 543     /**
 544      * Current Platform User Agent Stylesheet is Modena.
 545      *
 546      * Note: Please think hard before using this as we really want to avoid special cases in the platform for specific
 547      * themes. This was added to allow tempory work arounds in the platform for bugs.
 548      *
 549      * @return true if using modena stylesheet
 550      */
 551     public static boolean isModena() {
 552         return isModena;
 553     }
 554 
 555     /**
 556      * Current Platform User Agent Stylesheet is Caspian.
 557      *
 558      * Note: Please think hard before using this as we really want to avoid special cases in the platform for specific
 559      * themes. This was added to allow tempory work arounds in the platform for bugs.
 560      *
 561      * @return true if using caspian stylesheet
 562      */
 563     public static boolean isCaspian() {
 564         return isCaspian;
 565     }
 566 
 567     /**
 568      * Set the platform user agent stylesheet to the given URL. This method has special handling for platform theme
 569      * name constants.
 570      */
 571     public static void setPlatformUserAgentStylesheet(final String stylesheetUrl) {
 572         if (isFxApplicationThread()) {
 573             _setPlatformUserAgentStylesheet(stylesheetUrl);
 574         } else {
 575             runLater(() -> _setPlatformUserAgentStylesheet(stylesheetUrl));
 576         }
 577     }
 578 
 579     private static String accessibilityTheme;
 580     public static boolean setAccessibilityTheme(String platformTheme) {
 581 
 582         if (accessibilityTheme != null) {
 583             StyleManager.getInstance().removeUserAgentStylesheet(accessibilityTheme);
 584             accessibilityTheme = null;
 585         }
 586 
 587         _setAccessibilityTheme(platformTheme);
 588 
 589         if (accessibilityTheme != null) {
 590             StyleManager.getInstance().addUserAgentStylesheet(accessibilityTheme);
 591             return true;
 592         }
 593         return false;
 594 
 595     }
 596 
 597     private static void _setAccessibilityTheme(String platformTheme) {
 598 
 599         // check to see if there is an override to enable a high-contrast theme
 600         final String userTheme = AccessController.doPrivileged(
 601                 (PrivilegedAction<String>) () -> System.getProperty("com.sun.javafx.highContrastTheme"));
 602 
 603         if (isCaspian()) {
 604             if (platformTheme != null || userTheme != null) {
 605                 // caspian has only one high contrast theme, use it regardless of the user or platform theme.
 606                 accessibilityTheme = "com/sun/javafx/scene/control/skin/caspian/highcontrast.css";
 607             }
 608         } else if (isModena()) {
 609             // User-defined property takes precedence
 610             if (userTheme != null) {
 611                 switch (userTheme.toUpperCase()) {
 612                     case "BLACKONWHITE":
 613                         accessibilityTheme = "com/sun/javafx/scene/control/skin/modena/blackOnWhite.css";
 614                         break;
 615                     case "WHITEONBLACK":
 616                         accessibilityTheme = "com/sun/javafx/scene/control/skin/modena/whiteOnBlack.css";
 617                         break;
 618                     case "YELLOWONBLACK":
 619                         accessibilityTheme = "com/sun/javafx/scene/control/skin/modena/yellowOnBlack.css";
 620                         break;
 621                     default:
 622                 }
 623             } else {
 624                 if (platformTheme != null) {
 625                     // The following names are Platform specific (Windows 7 and 8)
 626                     switch (platformTheme) {
 627                         case "High Contrast White":
 628                             accessibilityTheme = "com/sun/javafx/scene/control/skin/modena/blackOnWhite.css";
 629                             break;
 630                         case "High Contrast Black":
 631                             accessibilityTheme = "com/sun/javafx/scene/control/skin/modena/whiteOnBlack.css";
 632                             break;
 633                         case "High Contrast #1":
 634                         case "High Contrast #2": //TODO #2 should be green on black
 635                             accessibilityTheme = "com/sun/javafx/scene/control/skin/modena/yellowOnBlack.css";
 636                             break;
 637                         default:
 638                     }
 639                 }
 640             }
 641         }
 642     }
 643 
 644     private static void _setPlatformUserAgentStylesheet(String stylesheetUrl) {
 645         isModena = isCaspian = false;
 646         // check for command line override
 647         final String overrideStylesheetUrl = AccessController.doPrivileged(
 648                 (PrivilegedAction<String>) () -> System.getProperty("javafx.userAgentStylesheetUrl"));
 649 
 650         if (overrideStylesheetUrl != null) {
 651             stylesheetUrl = overrideStylesheetUrl;
 652         }
 653 
 654         final List<String> uaStylesheets = new ArrayList<>();
 655 
 656         // check for named theme constants for modena and caspian
 657         if (Application.STYLESHEET_CASPIAN.equalsIgnoreCase(stylesheetUrl)) {
 658             isCaspian = true;
 659 
 660             uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/caspian.css");
 661 
 662             if (isSupported(ConditionalFeature.INPUT_TOUCH)) {
 663                 uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/embedded.css");
 664                 if (com.sun.javafx.util.Utils.isQVGAScreen()) {
 665                     uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/embedded-qvga.css");
 666                 }
 667                 if (PlatformUtil.isAndroid()) {
 668                     uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/android.css");
 669                 }
 670             }
 671 
 672             if (isSupported(ConditionalFeature.TWO_LEVEL_FOCUS)) {
 673                 uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/two-level-focus.css");
 674             }
 675 
 676             if (isSupported(ConditionalFeature.VIRTUAL_KEYBOARD)) {
 677                 uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/fxvk.css");
 678             }
 679 
 680             if (!isSupported(ConditionalFeature.TRANSPARENT_WINDOW)) {
 681                 uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/caspian-no-transparency.css");
 682             }
 683 
 684         } else if (Application.STYLESHEET_MODENA.equalsIgnoreCase(stylesheetUrl)) {
 685             isModena = true;
 686 
 687             uaStylesheets.add("com/sun/javafx/scene/control/skin/modena/modena.css");
 688 
 689             if (isSupported(ConditionalFeature.INPUT_TOUCH)) {
 690                 uaStylesheets.add("com/sun/javafx/scene/control/skin/modena/touch.css");
 691             }
 692             // when running on embedded add a extra stylesheet to tune performance of modena theme
 693             if (PlatformUtil.isEmbedded()) {
 694                 uaStylesheets.add("com/sun/javafx/scene/control/skin/modena/modena-embedded-performance.css");
 695             }
 696             if (PlatformUtil.isAndroid()) {
 697                 uaStylesheets.add("com/sun/javafx/scene/control/skin/modena/android.css");
 698             }
 699 
 700             if (isSupported(ConditionalFeature.TWO_LEVEL_FOCUS)) {
 701                 uaStylesheets.add("com/sun/javafx/scene/control/skin/modena/two-level-focus.css");
 702             }
 703 
 704             if (isSupported(ConditionalFeature.VIRTUAL_KEYBOARD)) {
 705                 uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/fxvk.css");
 706             }
 707 
 708             if (!isSupported(ConditionalFeature.TRANSPARENT_WINDOW)) {
 709                 uaStylesheets.add("com/sun/javafx/scene/control/skin/modena/modena-no-transparency.css");
 710             }
 711 
 712         } else {
 713             uaStylesheets.add(stylesheetUrl);
 714         }
 715 
 716         // Ensure that accessibility starts right
 717         _setAccessibilityTheme(Toolkit.getToolkit().getThemeName());
 718         if (accessibilityTheme != null) {
 719             uaStylesheets.add(accessibilityTheme);
 720         }
 721 
 722         AccessController.doPrivileged((PrivilegedAction) () -> {
 723             StyleManager.getInstance().setUserAgentStylesheets(uaStylesheets);
 724             return null;
 725         });
 726 
 727     }
 728 
 729     public static void addNoTransparencyStylesheetToScene(final Scene scene) {
 730         if (PlatformImpl.isCaspian()) {
 731             AccessController.doPrivileged((PrivilegedAction) () -> {
 732                 StyleManager.getInstance().addUserAgentStylesheet(scene,
 733                         "com/sun/javafx/scene/control/skin/caspian/caspian-no-transparency.css");
 734                 return null;
 735             });
 736         } else if (PlatformImpl.isModena()) {
 737             AccessController.doPrivileged((PrivilegedAction) () -> {
 738                 StyleManager.getInstance().addUserAgentStylesheet(scene,
 739                         "com/sun/javafx/scene/control/skin/modena/modena-no-transparency.css");
 740                 return null;
 741             });
 742         }
 743     }
 744 
 745     private static boolean isSupportedImpl(ConditionalFeature feature) {
 746         switch (feature) {
 747             case GRAPHICS:
 748                 if (isGraphicsSupported == null) {
 749                     isGraphicsSupported = checkForClass("javafx.stage.Stage");
 750                 }
 751                 return isGraphicsSupported;
 752             case CONTROLS:
 753                 if (isControlsSupported == null) {
 754                     isControlsSupported = checkForClass(
 755                             "javafx.scene.control.Control");
 756                 }
 757                 return isControlsSupported;
 758             case MEDIA:
 759                 if (isMediaSupported == null) {
 760                     isMediaSupported = checkForClass(
 761                             "javafx.scene.media.MediaView");
 762                     if (isMediaSupported && PlatformUtil.isEmbedded()) {
 763                         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 764                             String s = System.getProperty(
 765                                     "com.sun.javafx.experimental.embedded.media",
 766                                     "false");
 767                             isMediaSupported = Boolean.valueOf(s);
 768                             return null;
 769 
 770                         });
 771                     }
 772                 }
 773                 return isMediaSupported;
 774             case WEB:
 775                 if (isWebSupported == null) {
 776                     isWebSupported = checkForClass("javafx.scene.web.WebView");
 777                     if (isWebSupported && PlatformUtil.isEmbedded()) {
 778                         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 779                             String s = System.getProperty(
 780                                     "com.sun.javafx.experimental.embedded.web",
 781                                     "false");
 782                             isWebSupported = Boolean.valueOf(s);
 783                             return null;
 784 
 785                         });
 786                     }
 787                 }
 788                 return isWebSupported;
 789             case SWT:
 790                 if (isSWTSupported == null) {
 791                     isSWTSupported = checkForClass("javafx.embed.swt.FXCanvas");
 792                 }
 793                 return isSWTSupported;
 794             case SWING:
 795                 if (isSwingSupported == null) {
 796                     isSwingSupported =
 797                         // check for JComponent first, it may not be present
 798                         checkForClass("javax.swing.JComponent") &&
 799                         checkForClass("javafx.embed.swing.JFXPanel");
 800                 }
 801                 return isSwingSupported;
 802             case FXML:
 803                 if (isFXMLSupported == null) {
 804                     isFXMLSupported = checkForClass("javafx.fxml.FXMLLoader")
 805                             && checkForClass("javax.xml.stream.XMLInputFactory");
 806                 }
 807                 return isFXMLSupported;
 808             case TWO_LEVEL_FOCUS:
 809                 if (hasTwoLevelFocus == null) {
 810                     return Toolkit.getToolkit().isSupported(feature);
 811                 }
 812                 return hasTwoLevelFocus;
 813             case VIRTUAL_KEYBOARD:
 814                 if (hasVirtualKeyboard == null) {
 815                     return Toolkit.getToolkit().isSupported(feature);
 816                 }
 817                 return hasVirtualKeyboard;
 818             case INPUT_TOUCH:
 819                 if (hasTouch == null) {
 820                     return Toolkit.getToolkit().isSupported(feature);
 821                 }
 822                 return hasTouch;
 823             case INPUT_MULTITOUCH:
 824                 if (hasMultiTouch == null) {
 825                     return Toolkit.getToolkit().isSupported(feature);
 826                 }
 827                 return hasMultiTouch;
 828             case INPUT_POINTER:
 829                 if (hasPointer == null) {
 830                     return Toolkit.getToolkit().isSupported(feature);
 831                 }
 832                 return hasPointer;
 833             default:
 834                 return Toolkit.getToolkit().isSupported(feature);
 835         }
 836     }
 837 }