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