1 /*
   2  * Copyright (c) 1995, 2015, 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 sun.applet;
  27 
  28 import java.applet.*;
  29 import java.awt.*;
  30 import java.awt.event.*;
  31 import java.io.*;
  32 import java.lang.ref.WeakReference;
  33 import java.lang.reflect.InvocationTargetException;
  34 import java.lang.reflect.Method;
  35 import java.net.JarURLConnection;
  36 import java.net.SocketPermission;
  37 import java.net.URL;
  38 import java.security.*;
  39 import java.util.*;
  40 import java.util.Locale;
  41 import sun.awt.AWTAccessor;
  42 import sun.awt.AppContext;
  43 import sun.awt.EmbeddedFrame;
  44 import sun.awt.SunToolkit;
  45 import sun.misc.ManagedLocalsThread;
  46 import sun.misc.MessageUtils;
  47 import sun.misc.PerformanceLogger;
  48 import sun.misc.Queue;
  49 import sun.security.util.SecurityConstants;
  50 
  51 /**
  52  * Applet panel class. The panel manages and manipulates the
  53  * applet as it is being loaded. It forks a separate thread in a new
  54  * thread group to call the applet's init(), start(), stop(), and
  55  * destroy() methods.
  56  *
  57  * @author      Arthur van Hoff
  58  */
  59 @SuppressWarnings("serial") // JDK implementation class
  60 public
  61 abstract class AppletPanel extends Panel implements AppletStub, Runnable {
  62 
  63     /**
  64      * The applet (if loaded).
  65      */
  66     Applet applet;
  67 
  68     /**
  69      * Applet will allow initialization.  Should be
  70      * set to false if loading a serialized applet
  71      * that was pickled in the init=true state.
  72      */
  73     protected boolean doInit = true;
  74 
  75 
  76     /**
  77      * The classloader for the applet.
  78      */
  79     protected AppletClassLoader loader;
  80 
  81     /* applet event ids */
  82     public final static int APPLET_DISPOSE = 0;
  83     public final static int APPLET_LOAD = 1;
  84     public final static int APPLET_INIT = 2;
  85     public final static int APPLET_START = 3;
  86     public final static int APPLET_STOP = 4;
  87     public final static int APPLET_DESTROY = 5;
  88     public final static int APPLET_QUIT = 6;
  89     public final static int APPLET_ERROR = 7;
  90 
  91     /* send to the parent to force relayout */
  92     public final static int APPLET_RESIZE = 51234;
  93 
  94     /* sent to a (distant) parent to indicate that the applet is being
  95      * loaded or as completed loading
  96      */
  97     public final static int APPLET_LOADING = 51235;
  98     public final static int APPLET_LOADING_COMPLETED = 51236;
  99 
 100     /**
 101      * The current status. One of:
 102      *    APPLET_DISPOSE,
 103      *    APPLET_LOAD,
 104      *    APPLET_INIT,
 105      *    APPLET_START,
 106      *    APPLET_STOP,
 107      *    APPLET_DESTROY,
 108      *    APPLET_ERROR.
 109      */
 110     protected int status;
 111 
 112     /**
 113      * The thread for the applet.
 114      */
 115     protected Thread handler;
 116 
 117 
 118     /**
 119      * The initial applet size.
 120      */
 121     Dimension defaultAppletSize = new Dimension(10, 10);
 122 
 123     /**
 124      * The current applet size.
 125      */
 126     Dimension currentAppletSize = new Dimension(10, 10);
 127 
 128     MessageUtils mu = new MessageUtils();
 129 
 130     /**
 131      * The thread to use during applet loading
 132      */
 133 
 134     Thread loaderThread = null;
 135 
 136     /**
 137      * Flag to indicate that a loading has been cancelled
 138      */
 139     boolean loadAbortRequest = false;
 140 
 141     /* abstract classes */
 142     abstract protected String getCode();
 143     abstract protected String getJarFiles();
 144     abstract protected String getSerializedObject();
 145 
 146     @Override
 147     abstract public int    getWidth();
 148     @Override
 149     abstract public int    getHeight();
 150     abstract public boolean hasInitialFocus();
 151 
 152     private static int threadGroupNumber = 0;
 153 
 154     protected void setupAppletAppContext() {
 155         // do nothing
 156     }
 157 
 158     /*
 159      * Creates a thread to run the applet. This method is called
 160      * each time an applet is loaded and reloaded.
 161      */
 162     synchronized void createAppletThread() {
 163         // Create a thread group for the applet, and start a new
 164         // thread to load the applet.
 165         String nm = "applet-" + getCode();
 166         loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey());
 167         loader.grab(); // Keep this puppy around!
 168 
 169         // 4668479: Option to turn off codebase lookup in AppletClassLoader
 170         // during resource requests. [stanley.ho]
 171         String param = getParameter("codebase_lookup");
 172 
 173         if (param != null && param.equals("false"))
 174             loader.setCodebaseLookup(false);
 175         else
 176             loader.setCodebaseLookup(true);
 177 
 178 
 179         ThreadGroup appletGroup = loader.getThreadGroup();
 180         handler = new ManagedLocalsThread(appletGroup, this, "thread " + nm);
 181         // set the context class loader for this thread
 182         AccessController.doPrivileged(new PrivilegedAction<Object>() {
 183                 @Override
 184                 public Object run() {
 185                     handler.setContextClassLoader(loader);
 186                     return null;
 187                 }
 188             });
 189         handler.start();
 190     }
 191 
 192     void joinAppletThread() throws InterruptedException {
 193         if (handler != null) {
 194             handler.join();
 195             handler = null;
 196         }
 197     }
 198 
 199     void release() {
 200         if (loader != null) {
 201             loader.release();
 202             loader = null;
 203         }
 204     }
 205 
 206     /**
 207      * Construct an applet viewer and start the applet.
 208      */
 209     public void init() {
 210         try {
 211             // Get the width (if any)
 212             defaultAppletSize.width = getWidth();
 213             currentAppletSize.width = defaultAppletSize.width;
 214 
 215             // Get the height (if any)
 216             defaultAppletSize.height = getHeight();
 217             currentAppletSize.height = defaultAppletSize.height;
 218 
 219         } catch (NumberFormatException e) {
 220             // Turn on the error flag and let TagAppletPanel
 221             // do the right thing.
 222             status = APPLET_ERROR;
 223             showAppletStatus("badattribute.exception");
 224             showAppletLog("badattribute.exception");
 225             showAppletException(e);
 226         }
 227 
 228         setLayout(new BorderLayout());
 229 
 230         createAppletThread();
 231     }
 232 
 233     /**
 234      * Minimum size
 235      */
 236     @Override
 237     @SuppressWarnings("deprecation")
 238     public Dimension minimumSize() {
 239         return new Dimension(defaultAppletSize.width,
 240                              defaultAppletSize.height);
 241     }
 242 
 243     /**
 244      * Preferred size
 245      */
 246     @Override
 247     @SuppressWarnings("deprecation")
 248     public Dimension preferredSize() {
 249         return new Dimension(currentAppletSize.width,
 250                              currentAppletSize.height);
 251     }
 252 
 253     private AppletListener listeners;
 254 
 255     /**
 256      * AppletEvent Queue
 257      */
 258     private Queue<Integer> queue = null;
 259 
 260 
 261     synchronized public void addAppletListener(AppletListener l) {
 262         listeners = AppletEventMulticaster.add(listeners, l);
 263     }
 264 
 265     synchronized public void removeAppletListener(AppletListener l) {
 266         listeners = AppletEventMulticaster.remove(listeners, l);
 267     }
 268 
 269     /**
 270      * Dispatch event to the listeners..
 271      */
 272     public void dispatchAppletEvent(int id, Object argument) {
 273         //System.out.println("SEND= " + id);
 274         if (listeners != null) {
 275             AppletEvent evt = new AppletEvent(this, id, argument);
 276             listeners.appletStateChanged(evt);
 277         }
 278     }
 279 
 280     /**
 281      * Send an event. Queue it for execution by the handler thread.
 282      */
 283     public void sendEvent(int id) {
 284         synchronized(this) {
 285             if (queue == null) {
 286                 //System.out.println("SEND0= " + id);
 287                 queue = new Queue<>();
 288             }
 289             Integer eventId = Integer.valueOf(id);
 290             queue.enqueue(eventId);
 291             notifyAll();
 292         }
 293         if (id == APPLET_QUIT) {
 294             try {
 295                 joinAppletThread(); // Let the applet event handler exit
 296             } catch (InterruptedException e) {
 297             }
 298 
 299             // AppletClassLoader.release() must be called by a Thread
 300             // not within the applet's ThreadGroup
 301             if (loader == null)
 302                 loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey());
 303             release();
 304         }
 305     }
 306 
 307     /**
 308      * Get an event from the queue.
 309      */
 310     synchronized AppletEvent getNextEvent() throws InterruptedException {
 311         while (queue == null || queue.isEmpty()) {
 312             wait();
 313         }
 314         Integer eventId = queue.dequeue();
 315         return new AppletEvent(this, eventId.intValue(), null);
 316     }
 317 
 318     boolean emptyEventQueue() {
 319         if ((queue == null) || (queue.isEmpty()))
 320             return true;
 321         else
 322             return false;
 323     }
 324 
 325     /**
 326      * This kludge is specific to get over AccessControlException thrown during
 327      * Applet.stop() or destroy() when static thread is suspended.  Set a flag
 328      * in AppletClassLoader to indicate that an
 329      * AccessControlException for RuntimePermission "modifyThread" or
 330      * "modifyThreadGroup" had occurred.
 331      */
 332      private void setExceptionStatus(AccessControlException e) {
 333      Permission p = e.getPermission();
 334      if (p instanceof RuntimePermission) {
 335          if (p.getName().startsWith("modifyThread")) {
 336              if (loader == null)
 337                  loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey());
 338              loader.setExceptionStatus();
 339          }
 340      }
 341      }
 342 
 343     /**
 344      * Execute applet events.
 345      * Here is the state transition diagram
 346      *
 347      * <pre>{@literal
 348      *   Note: (XXX) is the action
 349      *         APPLET_XXX is the state
 350      *  (applet code loaded) --> APPLET_LOAD -- (applet init called)--> APPLET_INIT --
 351      *  (applet start called) --> APPLET_START -- (applet stop called) --> APPLET_STOP --
 352      *  (applet destroyed called) --> APPLET_DESTROY --> (applet gets disposed) -->
 353      *   APPLET_DISPOSE --> ...
 354      * }</pre>
 355      *
 356      * In the legacy lifecycle model. The applet gets loaded, inited and started. So it stays
 357      * in the APPLET_START state unless the applet goes away(refresh page or leave the page).
 358      * So the applet stop method called and the applet enters APPLET_STOP state. Then if the applet
 359      * is revisited, it will call applet start method and enter the APPLET_START state and stay there.
 360      *
 361      * In the modern lifecycle model. When the applet first time visited, it is same as legacy lifecycle
 362      * model. However, when the applet page goes away. It calls applet stop method and enters APPLET_STOP
 363      * state and then applet destroyed method gets called and enters APPLET_DESTROY state.
 364      *
 365      * This code is also called by AppletViewer. In AppletViewer "Restart" menu, the applet is jump from
 366      * APPLET_STOP to APPLET_DESTROY and to APPLET_INIT .
 367      *
 368      * Also, the applet can jump from APPLET_INIT state to APPLET_DESTROY (in Netscape/Mozilla case).
 369      * Same as APPLET_LOAD to
 370      * APPLET_DISPOSE since all of this are triggered by browser.
 371      *
 372      */
 373     @Override
 374     public void run() {
 375 
 376         Thread curThread = Thread.currentThread();
 377         if (curThread == loaderThread) {
 378             // if we are in the loader thread, cause
 379             // loading to occur.  We may exit this with
 380             // status being APPLET_DISPOSE, APPLET_ERROR,
 381             // or APPLET_LOAD
 382             runLoader();
 383             return;
 384         }
 385 
 386         boolean disposed = false;
 387         while (!disposed && !curThread.isInterrupted()) {
 388             AppletEvent evt;
 389             try {
 390                 evt = getNextEvent();
 391             } catch (InterruptedException e) {
 392                 showAppletStatus("bail");
 393                 return;
 394             }
 395 
 396             //showAppletStatus("EVENT = " + evt.getID());
 397             try {
 398                 switch (evt.getID()) {
 399                   case APPLET_LOAD:
 400                       if (!okToLoad()) {
 401                           break;
 402                       }
 403                       // This complexity allows loading of applets to be
 404                       // interruptable.  The actual thread loading runs
 405                       // in a separate thread, so it can be interrupted
 406                       // without harming the applet thread.
 407                       // So that we don't have to worry about
 408                       // concurrency issues, the main applet thread waits
 409                       // until the loader thread terminates.
 410                       // (one way or another).
 411                       if (loaderThread == null) {
 412                           // REMIND: do we want a name?
 413                           //System.out.println("------------------- loading applet");
 414                           setLoaderThread(new ManagedLocalsThread(this));
 415                           loaderThread.start();
 416                           // we get to go to sleep while this runs
 417                           loaderThread.join();
 418                           setLoaderThread(null);
 419                       } else {
 420                           // REMIND: issue an error -- this case should never
 421                           // occur.
 422                       }
 423                       break;
 424 
 425                   case APPLET_INIT:
 426                     // AppletViewer "Restart" will jump from destroy method to
 427                     // init, that is why we need to check status w/ APPLET_DESTROY
 428                       if (status != APPLET_LOAD && status != APPLET_DESTROY) {
 429                           showAppletStatus("notloaded");
 430                           break;
 431                       }
 432                       applet.resize(defaultAppletSize);
 433                       if (doInit) {
 434                           if (PerformanceLogger.loggingEnabled()) {
 435                               PerformanceLogger.setTime("Applet Init");
 436                               PerformanceLogger.outputLog();
 437                           }
 438                           applet.init();
 439                       }
 440 
 441                       //Need the default(fallback) font to be created in this AppContext
 442                       Font f = getFont();
 443                       if (f == null ||
 444                           "dialog".equals(f.getFamily().toLowerCase(Locale.ENGLISH)) &&
 445                           f.getSize() == 12 && f.getStyle() == Font.PLAIN) {
 446                           setFont(new Font(Font.DIALOG, Font.PLAIN, 12));
 447                       }
 448 
 449                       doInit = true;    // allow restarts
 450 
 451                       // Validate the applet in event dispatch thread
 452                       // to avoid deadlock.
 453                       try {
 454                           final AppletPanel p = this;
 455                           Runnable r = new Runnable() {
 456                               @Override
 457                               public void run() {
 458                                   p.validate();
 459                               }
 460                           };
 461                           AWTAccessor.getEventQueueAccessor().invokeAndWait(applet, r);
 462                       }
 463                       catch(InterruptedException ie) {
 464                       }
 465                       catch(InvocationTargetException ite) {
 466                       }
 467 
 468                       status = APPLET_INIT;
 469                       showAppletStatus("inited");
 470                       break;
 471 
 472                   case APPLET_START:
 473                   {
 474                       if (status != APPLET_INIT && status != APPLET_STOP) {
 475                           showAppletStatus("notinited");
 476                           break;
 477                       }
 478                       applet.resize(currentAppletSize);
 479                       applet.start();
 480 
 481                       // Validate and show the applet in event dispatch thread
 482                       // to avoid deadlock.
 483                       try {
 484                           final AppletPanel p = this;
 485                           final Applet a = applet;
 486                           Runnable r = new Runnable() {
 487                               @Override
 488                               public void run() {
 489                                   p.validate();
 490                                   a.setVisible(true);
 491 
 492                                   // Fix for BugTraq ID 4041703.
 493                                   // Set the default focus for an applet.
 494                                   if (hasInitialFocus()) {
 495                                       setDefaultFocus();
 496                                   }
 497                               }
 498                           };
 499                           AWTAccessor.getEventQueueAccessor().invokeAndWait(applet, r);
 500                       }
 501                       catch(InterruptedException ie) {
 502                       }
 503                       catch(InvocationTargetException ite) {
 504                       }
 505 
 506                       status = APPLET_START;
 507                       showAppletStatus("started");
 508                       break;
 509                   }
 510 
 511                 case APPLET_STOP:
 512                     if (status != APPLET_START) {
 513                         showAppletStatus("notstarted");
 514                         break;
 515                     }
 516                     status = APPLET_STOP;
 517 
 518                     // Hide the applet in event dispatch thread
 519                     // to avoid deadlock.
 520                     try {
 521                         final Applet a = applet;
 522                         Runnable r = new Runnable() {
 523                             @Override
 524                             public void run() {
 525                                 a.setVisible(false);
 526                             }
 527                         };
 528                         AWTAccessor.getEventQueueAccessor().invokeAndWait(applet, r);
 529                     }
 530                     catch(InterruptedException ie) {
 531                     }
 532                     catch(InvocationTargetException ite) {
 533                     }
 534 
 535 
 536                     // During Applet.stop(), any AccessControlException on an involved Class remains in
 537                     // the "memory" of the AppletClassLoader.  If the same instance of the ClassLoader is
 538                     // reused, the same exception will occur during class loading.  Set the AppletClassLoader's
 539                     // exceptionStatusSet flag to allow recognition of what had happened
 540                     // when reusing AppletClassLoader object.
 541                     try {
 542                         applet.stop();
 543                     } catch (java.security.AccessControlException e) {
 544                         setExceptionStatus(e);
 545                         // rethrow exception to be handled as it normally would be.
 546                         throw e;
 547                     }
 548                     showAppletStatus("stopped");
 549                     break;
 550 
 551                 case APPLET_DESTROY:
 552                     if (status != APPLET_STOP && status != APPLET_INIT) {
 553                         showAppletStatus("notstopped");
 554                         break;
 555                     }
 556                     status = APPLET_DESTROY;
 557 
 558                     // During Applet.destroy(), any AccessControlException on an involved Class remains in
 559                     // the "memory" of the AppletClassLoader.  If the same instance of the ClassLoader is
 560                     // reused, the same exception will occur during class loading.  Set the AppletClassLoader's
 561                     // exceptionStatusSet flag to allow recognition of what had happened
 562                     // when reusing AppletClassLoader object.
 563                     try {
 564                         applet.destroy();
 565                     } catch (java.security.AccessControlException e) {
 566                         setExceptionStatus(e);
 567                         // rethrow exception to be handled as it normally would be.
 568                         throw e;
 569                     }
 570                     showAppletStatus("destroyed");
 571                     break;
 572 
 573                 case APPLET_DISPOSE:
 574                     if (status != APPLET_DESTROY && status != APPLET_LOAD) {
 575                         showAppletStatus("notdestroyed");
 576                         break;
 577                     }
 578                     status = APPLET_DISPOSE;
 579 
 580                     try {
 581                         final Applet a = applet;
 582                         Runnable r = new Runnable() {
 583                             @Override
 584                             public void run() {
 585                                 remove(a);
 586                             }
 587                         };
 588                         AWTAccessor.getEventQueueAccessor().invokeAndWait(applet, r);
 589                     }
 590                     catch(InterruptedException ie)
 591                     {
 592                     }
 593                     catch(InvocationTargetException ite)
 594                     {
 595                     }
 596                     applet = null;
 597                     showAppletStatus("disposed");
 598                     disposed = true;
 599                     break;
 600 
 601                 case APPLET_QUIT:
 602                     return;
 603                 }
 604             } catch (Exception e) {
 605                 status = APPLET_ERROR;
 606                 if (e.getMessage() != null) {
 607                     showAppletStatus("exception2", e.getClass().getName(),
 608                                      e.getMessage());
 609                 } else {
 610                     showAppletStatus("exception", e.getClass().getName());
 611                 }
 612                 showAppletException(e);
 613             } catch (ThreadDeath e) {
 614                 showAppletStatus("death");
 615                 return;
 616             } catch (Error e) {
 617                 status = APPLET_ERROR;
 618                 if (e.getMessage() != null) {
 619                     showAppletStatus("error2", e.getClass().getName(),
 620                                      e.getMessage());
 621                 } else {
 622                     showAppletStatus("error", e.getClass().getName());
 623                 }
 624                 showAppletException(e);
 625             }
 626             clearLoadAbortRequest();
 627         }
 628     }
 629 
 630     /**
 631      * Gets most recent focus owner component associated with the given window.
 632      * It does that without calling Window.getMostRecentFocusOwner since it
 633      * provides its own logic contradicting with setDefautlFocus. Instead, it
 634      * calls KeyboardFocusManager directly.
 635      */
 636     private Component getMostRecentFocusOwnerForWindow(Window w) {
 637         Method meth = AccessController.doPrivileged(
 638             new PrivilegedAction<Method>() {
 639                 @Override
 640                 public Method run() {
 641                     Method meth = null;
 642                     try {
 643                         meth = KeyboardFocusManager.class.getDeclaredMethod(
 644                                 "getMostRecentFocusOwner",
 645                                 new Class<?>[]{Window.class});
 646                         meth.setAccessible(true);
 647                     } catch (Exception e) {
 648                         // Must never happen
 649                         e.printStackTrace();
 650                     }
 651                     return meth;
 652                 }
 653             });
 654         if (meth != null) {
 655             // Meth refers static method
 656             try {
 657                 return (Component)meth.invoke(null, new Object[] {w});
 658             } catch (Exception e) {
 659                 // Must never happen
 660                 e.printStackTrace();
 661             }
 662         }
 663         // Will get here if exception was thrown or meth is null
 664         return w.getMostRecentFocusOwner();
 665     }
 666 
 667     /*
 668      * Fix for BugTraq ID 4041703.
 669      * Set the focus to a reasonable default for an Applet.
 670      */
 671     private void setDefaultFocus() {
 672         Component toFocus = null;
 673         Container parent = getParent();
 674 
 675         if(parent != null) {
 676             if (parent instanceof Window) {
 677                 toFocus = getMostRecentFocusOwnerForWindow((Window)parent);
 678                 if (toFocus == parent || toFocus == null) {
 679                     toFocus = parent.getFocusTraversalPolicy().
 680                         getInitialComponent((Window)parent);
 681                 }
 682             } else if (parent.isFocusCycleRoot()) {
 683                 toFocus = parent.getFocusTraversalPolicy().
 684                     getDefaultComponent(parent);
 685             }
 686         }
 687 
 688         if (toFocus != null) {
 689             if (parent instanceof EmbeddedFrame) {
 690                 // JDK-8056915: Try to request focus to the embedder first and
 691                 // activate the embedded frame through it
 692                 if (!((EmbeddedFrame) parent).requestFocusToEmbedder()) {
 693                     // Otherwise activate the embedded frame directly
 694                     ((EmbeddedFrame) parent).synthesizeWindowActivation(true);
 695                 }
 696             }
 697             // EmbeddedFrame might have focus before the applet was added.
 698             // Thus after its activation the most recent focus owner will be
 699             // restored. We need the applet's initial focusabled component to
 700             // be focused here.
 701             toFocus.requestFocusInWindow();
 702         }
 703     }
 704 
 705     /**
 706      * Load the applet into memory.
 707      * Runs in a seperate (and interruptible) thread from the rest of the
 708      * applet event processing so that it can be gracefully interrupted from
 709      * things like HotJava.
 710      */
 711     @SuppressWarnings("deprecation")
 712     private void runLoader() {
 713         if (status != APPLET_DISPOSE) {
 714             showAppletStatus("notdisposed");
 715             return;
 716         }
 717 
 718         dispatchAppletEvent(APPLET_LOADING, null);
 719 
 720         // REMIND -- might be cool to visually indicate loading here --
 721         // maybe do animation?
 722         status = APPLET_LOAD;
 723 
 724         // Create a class loader
 725         loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey());
 726 
 727         // Load the archives if present.
 728         // REMIND - this probably should be done in a separate thread,
 729         // or at least the additional archives (epll).
 730 
 731         String code = getCode();
 732 
 733         // setup applet AppContext
 734         // this must be called before loadJarFiles
 735         setupAppletAppContext();
 736 
 737         try {
 738             loadJarFiles(loader);
 739             applet = createApplet(loader);
 740         } catch (ClassNotFoundException e) {
 741             status = APPLET_ERROR;
 742             showAppletStatus("notfound", code);
 743             showAppletLog("notfound", code);
 744             showAppletException(e);
 745             return;
 746         } catch (InstantiationException e) {
 747             status = APPLET_ERROR;
 748             showAppletStatus("nocreate", code);
 749             showAppletLog("nocreate", code);
 750             showAppletException(e);
 751             return;
 752         } catch (IllegalAccessException e) {
 753             status = APPLET_ERROR;
 754             showAppletStatus("noconstruct", code);
 755             showAppletLog("noconstruct", code);
 756             showAppletException(e);
 757             // sbb -- I added a return here
 758             return;
 759         } catch (Exception e) {
 760             status = APPLET_ERROR;
 761             showAppletStatus("exception", e.getMessage());
 762             showAppletException(e);
 763             return;
 764         } catch (ThreadDeath e) {
 765             status = APPLET_ERROR;
 766             showAppletStatus("death");
 767             return;
 768         } catch (Error e) {
 769             status = APPLET_ERROR;
 770             showAppletStatus("error", e.getMessage());
 771             showAppletException(e);
 772             return;
 773         } finally {
 774             // notify that loading is no longer going on
 775             dispatchAppletEvent(APPLET_LOADING_COMPLETED, null);
 776         }
 777 
 778         // Fixed #4508194: NullPointerException thrown during
 779         // quick page switch
 780         //
 781         if (applet != null)
 782         {
 783             // Stick it in the frame
 784             applet.setStub(this);
 785             applet.hide();
 786             add("Center", applet);
 787             showAppletStatus("loaded");
 788             validate();
 789         }
 790     }
 791 
 792     protected Applet createApplet(final AppletClassLoader loader) throws ClassNotFoundException,
 793                                                                          IllegalAccessException, IOException, InstantiationException, InterruptedException {
 794         final String serName = getSerializedObject();
 795         String code = getCode();
 796 
 797         if (code != null && serName != null) {
 798             System.err.println(amh.getMessage("runloader.err"));
 799 //          return null;
 800             throw new InstantiationException("Either \"code\" or \"object\" should be specified, but not both.");
 801         }
 802         if (code == null && serName == null) {
 803             String msg = "nocode";
 804             status = APPLET_ERROR;
 805             showAppletStatus(msg);
 806             showAppletLog(msg);
 807             repaint();
 808         }
 809         if (code != null) {
 810             applet = (Applet)loader.loadCode(code).newInstance();
 811             doInit = true;
 812         } else {
 813             // serName is not null;
 814             try (InputStream is = AccessController.doPrivileged(
 815                     (PrivilegedAction<InputStream>)() -> loader.getResourceAsStream(serName));
 816                  ObjectInputStream ois = new AppletObjectInputStream(is, loader)) {
 817 
 818                 applet = (Applet) ois.readObject();
 819                 doInit = false; // skip over the first init
 820             }
 821         }
 822 
 823         // Determine the JDK level that the applet targets.
 824         // This is critical for enabling certain backward
 825         // compatibility switch if an applet is a JDK 1.1
 826         // applet. [stanley.ho]
 827         findAppletJDKLevel(applet);
 828 
 829         if (Thread.interrupted()) {
 830             try {
 831                 status = APPLET_DISPOSE; // APPLET_ERROR?
 832                 applet = null;
 833                 // REMIND: This may not be exactly the right thing: the
 834                 // status is set by the stop button and not necessarily
 835                 // here.
 836                 showAppletStatus("death");
 837             } finally {
 838                 Thread.currentThread().interrupt(); // resignal interrupt
 839             }
 840             return null;
 841         }
 842         return applet;
 843     }
 844 
 845     protected void loadJarFiles(AppletClassLoader loader) throws IOException,
 846                                                                  InterruptedException {
 847         // Load the archives if present.
 848         // REMIND - this probably should be done in a separate thread,
 849         // or at least the additional archives (epll).
 850         String jarFiles = getJarFiles();
 851 
 852         if (jarFiles != null) {
 853             StringTokenizer st = new StringTokenizer(jarFiles, ",", false);
 854             while(st.hasMoreTokens()) {
 855                 String tok = st.nextToken().trim();
 856                 try {
 857                     loader.addJar(tok);
 858                 } catch (IllegalArgumentException e) {
 859                     // bad archive name
 860                     continue;
 861                 }
 862             }
 863         }
 864     }
 865 
 866     /**
 867      * Request that the loading of the applet be stopped.
 868      */
 869     protected synchronized void stopLoading() {
 870         // REMIND: fill in the body
 871         if (loaderThread != null) {
 872             //System.out.println("Interrupting applet loader thread: " + loaderThread);
 873             loaderThread.interrupt();
 874         } else {
 875             setLoadAbortRequest();
 876         }
 877     }
 878 
 879 
 880     protected synchronized boolean okToLoad() {
 881         return !loadAbortRequest;
 882     }
 883 
 884     protected synchronized void clearLoadAbortRequest() {
 885         loadAbortRequest = false;
 886     }
 887 
 888     protected synchronized void setLoadAbortRequest() {
 889         loadAbortRequest = true;
 890     }
 891 
 892 
 893     private synchronized void setLoaderThread(Thread loaderThread) {
 894         this.loaderThread = loaderThread;
 895     }
 896 
 897     /**
 898      * Return true when the applet has been started.
 899      */
 900     @Override
 901     public boolean isActive() {
 902         return status == APPLET_START;
 903     }
 904 
 905 
 906     private EventQueue appEvtQ = null;
 907     /**
 908      * Is called when the applet wants to be resized.
 909      */
 910     @Override
 911     public void appletResize(int width, int height) {
 912         currentAppletSize.width = width;
 913         currentAppletSize.height = height;
 914         final Dimension currentSize = new Dimension(currentAppletSize.width,
 915                                                     currentAppletSize.height);
 916 
 917         if(loader != null) {
 918             AppContext appCtxt = loader.getAppContext();
 919             if(appCtxt != null)
 920                 appEvtQ = (java.awt.EventQueue)appCtxt.get(AppContext.EVENT_QUEUE_KEY);
 921         }
 922 
 923         final AppletPanel ap = this;
 924         if (appEvtQ != null){
 925             appEvtQ.postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
 926                                                   new Runnable() {
 927                                                       @Override
 928                                                       public void run() {
 929                                                           if (ap != null) {
 930                                                               ap.dispatchAppletEvent(
 931                                                                       APPLET_RESIZE,
 932                                                                       currentSize);
 933                                                           }
 934                                                       }
 935                                                   }));
 936         }
 937     }
 938 
 939     @Override
 940     public void setBounds(int x, int y, int width, int height) {
 941         super.setBounds(x, y, width, height);
 942         currentAppletSize.width = width;
 943         currentAppletSize.height = height;
 944     }
 945 
 946     public Applet getApplet() {
 947         return applet;
 948     }
 949 
 950     /**
 951      * Status line. Called by the AppletPanel to provide
 952      * feedback on the Applet's state.
 953      */
 954     protected void showAppletStatus(String status) {
 955         getAppletContext().showStatus(amh.getMessage(status));
 956     }
 957 
 958     protected void showAppletStatus(String status, Object arg) {
 959         getAppletContext().showStatus(amh.getMessage(status, arg));
 960     }
 961     protected void showAppletStatus(String status, Object arg1, Object arg2) {
 962         getAppletContext().showStatus(amh.getMessage(status, arg1, arg2));
 963     }
 964 
 965     /**
 966      * Called by the AppletPanel to print to the log.
 967      */
 968     protected void showAppletLog(String msg) {
 969         System.out.println(amh.getMessage(msg));
 970     }
 971 
 972     protected void showAppletLog(String msg, Object arg) {
 973         System.out.println(amh.getMessage(msg, arg));
 974     }
 975 
 976     /**
 977      * Called by the AppletPanel to provide
 978      * feedback when an exception has happened.
 979      */
 980     protected void showAppletException(Throwable t) {
 981         t.printStackTrace();
 982         repaint();
 983     }
 984 
 985     /**
 986      * Get caching key for classloader cache
 987      */
 988     public String getClassLoaderCacheKey()
 989     {
 990         /**
 991          * Fixed #4501142: Classloader sharing policy doesn't
 992          * take "archive" into account. This will be overridden
 993          * by Java Plug-in.                     [stanleyh]
 994          */
 995         return getCodeBase().toString();
 996     }
 997 
 998     /**
 999      * The class loaders
1000      */
1001     private static HashMap<String, AppletClassLoader> classloaders = new HashMap<>();
1002 
1003     /**
1004      * Flush a class loader.
1005      */
1006     public static synchronized void flushClassLoader(String key) {
1007         classloaders.remove(key);
1008     }
1009 
1010     /**
1011      * Flush all class loaders.
1012      */
1013     public static synchronized void flushClassLoaders() {
1014         classloaders = new HashMap<>();
1015     }
1016 
1017     /**
1018      * This method actually creates an AppletClassLoader.
1019      *
1020      * It can be override by subclasses (such as the Plug-in)
1021      * to provide different classloaders.
1022      */
1023     protected AppletClassLoader createClassLoader(final URL codebase) {
1024         return new AppletClassLoader(codebase);
1025     }
1026 
1027     /**
1028      * Get a class loader. Create in a restricted context
1029      */
1030     synchronized AppletClassLoader getClassLoader(final URL codebase, final String key) {
1031         AppletClassLoader c = classloaders.get(key);
1032         if (c == null) {
1033             AccessControlContext acc =
1034                 getAccessControlContext(codebase);
1035             c = AccessController.doPrivileged(
1036                     new PrivilegedAction<AppletClassLoader>() {
1037                         @Override
1038                         public AppletClassLoader run() {
1039                             AppletClassLoader ac = createClassLoader(codebase);
1040                             /* Should the creation of the classloader be
1041                              * within the class synchronized block?  Since
1042                              * this class is used by the plugin, take care
1043                              * to avoid deadlocks, or specialize
1044                              * AppletPanel within the plugin.  It may take
1045                              * an arbitrary amount of time to create a
1046                              * class loader (involving getting Jar files
1047                              * etc.) and may block unrelated applets from
1048                              * finishing createAppletThread (due to the
1049                              * class synchronization). If
1050                              * createAppletThread does not finish quickly,
1051                              * the applet cannot process other messages,
1052                              * particularly messages such as destroy
1053                              * (which timeout when called from the browser).
1054                              */
1055                             synchronized (getClass()) {
1056                                 AppletClassLoader res = classloaders.get(key);
1057                                 if (res == null) {
1058                                     classloaders.put(key, ac);
1059                                     return ac;
1060                                 } else {
1061                                     return res;
1062                                 }
1063                             }
1064                         }
1065                     },acc);
1066         }
1067         return c;
1068     }
1069 
1070     /**
1071      * get the context for the AppletClassLoader we are creating.
1072      * the context is granted permission to create the class loader,
1073      * connnect to the codebase, and whatever else the policy grants
1074      * to all codebases.
1075      */
1076     private AccessControlContext getAccessControlContext(final URL codebase) {
1077 
1078         PermissionCollection perms = AccessController.doPrivileged(
1079                 new PrivilegedAction<PermissionCollection>() {
1080                     @Override
1081                     public PermissionCollection run() {
1082                         Policy p = java.security.Policy.getPolicy();
1083                         if (p != null) {
1084                             return p.getPermissions(new CodeSource(null,
1085                                                                    (java.security.cert.Certificate[]) null));
1086                         } else {
1087                             return null;
1088                         }
1089                     }
1090                 });
1091 
1092         if (perms == null)
1093             perms = new Permissions();
1094 
1095         //XXX: this is needed to be able to create the classloader itself!
1096 
1097         perms.add(SecurityConstants.CREATE_CLASSLOADER_PERMISSION);
1098 
1099         Permission p;
1100         java.net.URLConnection urlConnection = null;
1101         try {
1102             urlConnection = codebase.openConnection();
1103             p = urlConnection.getPermission();
1104         } catch (java.io.IOException ioe) {
1105             p = null;
1106         }
1107 
1108         if (p != null)
1109             perms.add(p);
1110 
1111         if (p instanceof FilePermission) {
1112 
1113             String path = p.getName();
1114 
1115             int endIndex = path.lastIndexOf(File.separatorChar);
1116 
1117             if (endIndex != -1) {
1118                 path = path.substring(0, endIndex+1);
1119 
1120                 if (path.endsWith(File.separator)) {
1121                     path += "-";
1122                 }
1123                 perms.add(new FilePermission(path,
1124                                              SecurityConstants.FILE_READ_ACTION));
1125             }
1126         } else {
1127             URL locUrl = codebase;
1128             if (urlConnection instanceof JarURLConnection) {
1129                 locUrl = ((JarURLConnection)urlConnection).getJarFileURL();
1130             }
1131             String host = locUrl.getHost();
1132             if (host != null && (host.length() > 0))
1133                 perms.add(new SocketPermission(host,
1134                                                SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION));
1135         }
1136 
1137         ProtectionDomain domain =
1138             new ProtectionDomain(new CodeSource(codebase,
1139                                                 (java.security.cert.Certificate[]) null), perms);
1140         AccessControlContext acc =
1141             new AccessControlContext(new ProtectionDomain[] { domain });
1142 
1143         return acc;
1144     }
1145 
1146     public Thread getAppletHandlerThread() {
1147         return handler;
1148     }
1149 
1150     public int getAppletWidth() {
1151         return currentAppletSize.width;
1152     }
1153 
1154     public int getAppletHeight() {
1155         return currentAppletSize.height;
1156     }
1157 
1158     public static void changeFrameAppContext(Frame frame, AppContext newAppContext)
1159     {
1160         // Fixed #4754451: Applet can have methods running on main
1161         // thread event queue.
1162         //
1163         // The cause of this bug is that the frame of the applet
1164         // is created in main thread group. Thus, when certain
1165         // AWT/Swing events are generated, the events will be
1166         // dispatched through the wrong event dispatch thread.
1167         //
1168         // To fix this, we rearrange the AppContext with the frame,
1169         // so the proper event queue will be looked up.
1170         //
1171         // Swing also maintains a Frame list for the AppContext,
1172         // so we will have to rearrange it as well.
1173 
1174         // Check if frame's AppContext has already been set properly
1175         AppContext oldAppContext = SunToolkit.targetToAppContext(frame);
1176 
1177         if (oldAppContext == newAppContext)
1178             return;
1179 
1180         // Synchronization on Window.class is needed for locking the
1181         // critical section of the window list in AppContext.
1182         synchronized (Window.class)
1183         {
1184             WeakReference<Window> weakRef = null;
1185             // Remove frame from the Window list in wrong AppContext
1186             {
1187                 // Lookup current frame's AppContext
1188                 @SuppressWarnings("unchecked")
1189                 Vector<WeakReference<Window>> windowList =
1190                     (Vector<WeakReference<Window>>)oldAppContext.get(Window.class);
1191                 if (windowList != null) {
1192                     for (WeakReference<Window> ref : windowList) {
1193                         if (ref.get() == frame) {
1194                             weakRef = ref;
1195                             break;
1196                         }
1197                     }
1198                     // Remove frame from wrong AppContext
1199                     if (weakRef != null)
1200                         windowList.remove(weakRef);
1201                 }
1202             }
1203 
1204             // Put the frame into the applet's AppContext map
1205             SunToolkit.insertTargetMapping(frame, newAppContext);
1206 
1207             // Insert frame into the Window list in the applet's AppContext map
1208             {
1209                 @SuppressWarnings("unchecked")
1210                 Vector<WeakReference<Window>> windowList =
1211                     (Vector<WeakReference<Window>>)newAppContext.get(Window.class);
1212                 if (windowList == null) {
1213                     windowList = new Vector<WeakReference<Window>>();
1214                     newAppContext.put(Window.class, windowList);
1215                 }
1216                 // use the same weakRef here as it is used elsewhere
1217                 windowList.add(weakRef);
1218             }
1219         }
1220     }
1221 
1222     // Flag to indicate if applet is targeted for JDK 1.1.
1223     private boolean jdk11Applet = false;
1224 
1225     // Flag to indicate if applet is targeted for JDK 1.2.
1226     private boolean jdk12Applet = false;
1227 
1228     /**
1229      * Determine JDK level of an applet.
1230      */
1231     private void findAppletJDKLevel(Applet applet)
1232     {
1233         // To determine the JDK level of an applet, the
1234         // most reliable way is to check the major version
1235         // of the applet class file.
1236 
1237         // synchronized on applet class object, so calling from
1238         // different instances of the same applet will be
1239         // serialized.
1240         Class<?> appletClass = applet.getClass();
1241 
1242         synchronized(appletClass)  {
1243             // Determine if the JDK level of an applet has been
1244             // checked before.
1245             Boolean jdk11Target = loader.isJDK11Target(appletClass);
1246             Boolean jdk12Target = loader.isJDK12Target(appletClass);
1247 
1248             // if applet JDK level has been checked before, retrieve
1249             // value and return.
1250             if (jdk11Target != null || jdk12Target != null) {
1251                 jdk11Applet = (jdk11Target == null) ? false : jdk11Target.booleanValue();
1252                 jdk12Applet = (jdk12Target == null) ? false : jdk12Target.booleanValue();
1253                 return;
1254             }
1255 
1256             String name = appletClass.getName();
1257 
1258             // first convert any '.' to '/'
1259             name = name.replace('.', '/');
1260 
1261             // append .class
1262             final String resourceName = name + ".class";
1263 
1264             byte[] classHeader = new byte[8];
1265 
1266             try (InputStream is = AccessController.doPrivileged(
1267                     (PrivilegedAction<InputStream>) () -> loader.getResourceAsStream(resourceName))) {
1268 
1269                 // Read the first 8 bytes of the class file
1270                 int byteRead = is.read(classHeader, 0, 8);
1271 
1272                 // return if the header is not read in entirely
1273                 // for some reasons.
1274                 if (byteRead != 8)
1275                     return;
1276             }
1277             catch (IOException e)   {
1278                 return;
1279             }
1280 
1281             // Check major version in class file header
1282             int major_version = readShort(classHeader, 6);
1283 
1284             // Major version in class file is as follows:
1285             //   45 - JDK 1.1
1286             //   46 - JDK 1.2
1287             //   47 - JDK 1.3
1288             //   48 - JDK 1.4
1289             //   49 - JDK 1.5
1290             if (major_version < 46)
1291                 jdk11Applet = true;
1292             else if (major_version == 46)
1293                 jdk12Applet = true;
1294 
1295             // Store applet JDK level in AppContext for later lookup,
1296             // e.g. page switch.
1297             loader.setJDK11Target(appletClass, jdk11Applet);
1298             loader.setJDK12Target(appletClass, jdk12Applet);
1299         }
1300     }
1301 
1302     /**
1303      * Return true if applet is targeted to JDK 1.1.
1304      */
1305     protected boolean isJDK11Applet()   {
1306         return jdk11Applet;
1307     }
1308 
1309     /**
1310      * Return true if applet is targeted to JDK1.2.
1311      */
1312     protected boolean isJDK12Applet()   {
1313         return jdk12Applet;
1314     }
1315 
1316     /**
1317      * Read short from byte array.
1318      */
1319     private int readShort(byte[] b, int off)    {
1320         int hi = readByte(b[off]);
1321         int lo = readByte(b[off + 1]);
1322         return (hi << 8) | lo;
1323     }
1324 
1325     private int readByte(byte b) {
1326         return ((int)b) & 0xFF;
1327     }
1328 
1329 
1330     private static AppletMessageHandler amh = new AppletMessageHandler("appletpanel");
1331 }