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