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