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