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