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