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